Spaces:
Running
on
Zero
Running
on
Zero
adamelliotfields
commited on
Commit
•
f70898c
1
Parent(s):
6360e64
LoRA adapters
Browse files- .gitignore +1 -0
- DOCS.md +29 -20
- app.css +4 -0
- app.py +58 -6
- cli.py +13 -5
- lib/__init__.py +2 -1
- lib/config.py +20 -3
- lib/inference.py +52 -15
- lib/utils.py +30 -0
- requirements.txt +5 -3
.gitignore
CHANGED
@@ -1,2 +1,3 @@
|
|
1 |
__pycache__/
|
2 |
.venv/
|
|
|
|
1 |
__pycache__/
|
2 |
.venv/
|
3 |
+
loras/
|
DOCS.md
CHANGED
@@ -10,7 +10,7 @@ Use `+` or `-` to increase the weight of a token. The weight grows exponentially
|
|
10 |
|
11 |
For groups of tokens, wrap them in parentheses and multiply by a float between 0 and 2. For example, `a (birthday cake)1.3 on a table` will increase the weight of both `birthday` and `cake` by 1.3x. This also means the entire scene will be more birthday-like, not just the cake. To counteract this, you can use `-` inside the parentheses on specific tokens, e.g., `a (birthday-- cake)1.3`, to reduce the birthday aspect.
|
12 |
|
13 |
-
|
14 |
|
15 |
| Compel | AUTOMATIC1111 |
|
16 |
| ----------- | ------------- |
|
@@ -21,25 +21,47 @@ Note that this is also the same syntax used in [InvokeAI](https://invoke-ai.gith
|
|
21 |
|
22 |
#### Arrays
|
23 |
|
24 |
-
Arrays allow you to generate multiple different images from a single prompt. For example, `
|
25 |
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
### Embeddings
|
29 |
|
30 |
Select one or more [textual inversion](https://huggingface.co/docs/diffusers/en/using-diffusers/textual_inversion_inference) embeddings:
|
31 |
|
32 |
* [`fast_negative`](https://civitai.com/models/71961?modelVersionId=94057): all-purpose (default)
|
33 |
-
* [`unrealistic_dream`](https://civitai.com/models/72437?modelVersionId=77173): realistic add-on (for RealisticVision)
|
34 |
* [`cyberrealistic_negative`](https://civitai.com/models/77976?modelVersionId=82745): realistic add-on (for CyberRealistic)
|
|
|
|
|
|
|
35 |
|
36 |
### Styles
|
37 |
|
38 |
[Styles](https://huggingface.co/spaces/adamelliotfields/diffusion/blob/main/data/styles.json) are prompt templates that wrap your positive and negative prompts. They were originally derived from the [twri/sdxl_prompt_styler](https://github.com/twri/sdxl_prompt_styler) Comfy node, but have since been entirely rewritten.
|
39 |
|
40 |
-
Start by framing a simple subject like `portrait of a young adult woman` or `landscape of a mountain range
|
41 |
-
|
42 |
-
> NB: Most styles work best with the Dreamshaper model; however, the "Enhance" style is meant to be universal. The "Photography" styles work especially well with the realistic models.
|
43 |
|
44 |
### Scale
|
45 |
|
@@ -47,19 +69,6 @@ Rescale up to 4x using [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN) wit
|
|
47 |
|
48 |
> NB: I find this Real-ESRGAN model to work well, so I do not use a _hi-res fix_.
|
49 |
|
50 |
-
### Models
|
51 |
-
|
52 |
-
Each model checkpoint has a different aesthetic:
|
53 |
-
|
54 |
-
* [Comfy-Org/stable-diffusion-v1-5](https://huggingface.co/Comfy-Org/stable-diffusion-v1-5-archive): base
|
55 |
-
* [cyberdelia/CyberRealistic_v5](https://huggingface.co/cyberdelia/CyberRealistic): photorealistic
|
56 |
-
* [Lykon/dreamshaper-8](https://huggingface.co/Lykon/dreamshaper-8): general purpose (default)
|
57 |
-
* [fluently/Fluently-v4](https://huggingface.co/fluently/Fluently-v4): general purpose
|
58 |
-
* [Linaqruf/anything-v3-1](https://huggingface.co/Linaqruf/anything-v3-1): anime
|
59 |
-
* [prompthero/openjourney-v4](https://huggingface.co/prompthero/openjourney-v4): Midjourney-like
|
60 |
-
* [SG161222/Realistic_Vision_v5.1](https://huggingface.co/SG161222/Realistic_Vision_V5.1_noVAE): photorealistic
|
61 |
-
* [XpucT/Deliberate_v6](https://huggingface.co/XpucT/Deliberate): general purpose
|
62 |
-
|
63 |
### Image-to-Image
|
64 |
|
65 |
The `🖼️ Image` tab enables the image-to-image and IP-Adapter pipelines. Either use the image input or select a generation from the gallery. To disable, simply clear the image input (the `x` overlay button).
|
|
|
10 |
|
11 |
For groups of tokens, wrap them in parentheses and multiply by a float between 0 and 2. For example, `a (birthday cake)1.3 on a table` will increase the weight of both `birthday` and `cake` by 1.3x. This also means the entire scene will be more birthday-like, not just the cake. To counteract this, you can use `-` inside the parentheses on specific tokens, e.g., `a (birthday-- cake)1.3`, to reduce the birthday aspect.
|
12 |
|
13 |
+
This is the same syntax used in [InvokeAI](https://invoke-ai.github.io/InvokeAI/features/PROMPTS/) and it differs from AUTOMATIC1111:
|
14 |
|
15 |
| Compel | AUTOMATIC1111 |
|
16 |
| ----------- | ------------- |
|
|
|
21 |
|
22 |
#### Arrays
|
23 |
|
24 |
+
Arrays allow you to generate multiple different images from a single prompt. For example, `an adult [[blonde,brunette]] [[man,woman]]` will expand into **4** different prompts. This implementation was inspired by [Fooocus](https://github.com/lllyasviel/Fooocus/pull/1503).
|
25 |
|
26 |
+
> NB: Make sure to set `Images` to the number of images you want to generate. Otherwise, only the first prompt will be used.
|
27 |
+
|
28 |
+
### Models
|
29 |
+
|
30 |
+
Each model checkpoint has a different aesthetic:
|
31 |
+
|
32 |
+
* [Comfy-Org/stable-diffusion-v1-5](https://huggingface.co/Comfy-Org/stable-diffusion-v1-5-archive): base
|
33 |
+
* [cyberdelia/CyberRealistic_V5](https://huggingface.co/cyberdelia/CyberRealistic): realistic
|
34 |
+
* [Lykon/dreamshaper-8](https://huggingface.co/Lykon/dreamshaper-8): general purpose (default)
|
35 |
+
* [fluently/Fluently-v4](https://huggingface.co/fluently/Fluently-v4): general purpose stylized
|
36 |
+
* [Linaqruf/anything-v3-1](https://huggingface.co/Linaqruf/anything-v3-1): anime
|
37 |
+
* [prompthero/openjourney-v4](https://huggingface.co/prompthero/openjourney-v4): Midjourney art style
|
38 |
+
* [SG161222/Realistic_Vision_V5](https://huggingface.co/SG161222/Realistic_Vision_V5.1_noVAE): realistic
|
39 |
+
* [XpucT/Deliberate_v6](https://huggingface.co/XpucT/Deliberate): general purpose stylized
|
40 |
+
|
41 |
+
### LoRA
|
42 |
+
|
43 |
+
Apply up to 2 LoRA (low-rank adaptation) adapters with adjustable strength:
|
44 |
+
|
45 |
+
* [Perfection Style](https://civitai.com/models/411088?modelVersionId=486099): attempts to improve aesthetics, use high strength
|
46 |
+
* [Detailed Style](https://civitai.com/models/421162?modelVersionId=486110): attempts to improve details, use low strength
|
47 |
+
|
48 |
+
> NB: The trigger words are automatically appended to the positive prompt for you.
|
49 |
|
50 |
### Embeddings
|
51 |
|
52 |
Select one or more [textual inversion](https://huggingface.co/docs/diffusers/en/using-diffusers/textual_inversion_inference) embeddings:
|
53 |
|
54 |
* [`fast_negative`](https://civitai.com/models/71961?modelVersionId=94057): all-purpose (default)
|
|
|
55 |
* [`cyberrealistic_negative`](https://civitai.com/models/77976?modelVersionId=82745): realistic add-on (for CyberRealistic)
|
56 |
+
* [`unrealistic_dream`](https://civitai.com/models/72437?modelVersionId=77173): realistic add-on (for RealisticVision)
|
57 |
+
|
58 |
+
> NB: The trigger token is automatically appended to the negative prompt for you.
|
59 |
|
60 |
### Styles
|
61 |
|
62 |
[Styles](https://huggingface.co/spaces/adamelliotfields/diffusion/blob/main/data/styles.json) are prompt templates that wrap your positive and negative prompts. They were originally derived from the [twri/sdxl_prompt_styler](https://github.com/twri/sdxl_prompt_styler) Comfy node, but have since been entirely rewritten.
|
63 |
|
64 |
+
Start by framing a simple subject like `portrait of a young adult woman` or `landscape of a mountain range` and experiment.
|
|
|
|
|
65 |
|
66 |
### Scale
|
67 |
|
|
|
69 |
|
70 |
> NB: I find this Real-ESRGAN model to work well, so I do not use a _hi-res fix_.
|
71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
### Image-to-Image
|
73 |
|
74 |
The `🖼️ Image` tab enables the image-to-image and IP-Adapter pipelines. Either use the image input or select a generation from the gallery. To disable, simply clear the image input (the `x` overlay button).
|
app.css
CHANGED
@@ -37,6 +37,10 @@
|
|
37 |
max-height: none;
|
38 |
}
|
39 |
|
|
|
|
|
|
|
|
|
40 |
.icon-button {
|
41 |
max-width: 42px;
|
42 |
}
|
|
|
37 |
max-height: none;
|
38 |
}
|
39 |
|
40 |
+
.gap-0, .gap-0 * {
|
41 |
+
gap: 0px;
|
42 |
+
}
|
43 |
+
|
44 |
.icon-button {
|
45 |
max-width: 42px;
|
46 |
}
|
app.py
CHANGED
@@ -1,10 +1,11 @@
|
|
1 |
import argparse
|
2 |
import json
|
|
|
3 |
import random
|
4 |
|
5 |
import gradio as gr
|
6 |
|
7 |
-
from lib import Config, async_call, download_repo_files, generate, read_file
|
8 |
|
9 |
# the CSS `content` attribute expects a string so we need to wrap the number in quotes
|
10 |
refresh_seed_js = """
|
@@ -130,9 +131,8 @@ with gr.Blocks(
|
|
130 |
with gr.TabItem("⚙️ Settings"):
|
131 |
with gr.Group():
|
132 |
negative_prompt = gr.Textbox(
|
133 |
-
value=
|
134 |
label="Negative Prompt",
|
135 |
-
placeholder="ugly, bad",
|
136 |
lines=2,
|
137 |
)
|
138 |
|
@@ -159,6 +159,7 @@ with gr.Blocks(
|
|
159 |
style = gr.Dropdown(
|
160 |
value=Config.STYLE,
|
161 |
label="Style",
|
|
|
162 |
choices=[("None", "none")]
|
163 |
+ [(styles[sid]["name"], sid) for sid in style_ids],
|
164 |
)
|
@@ -171,6 +172,44 @@ with gr.Blocks(
|
|
171 |
min_width=240,
|
172 |
)
|
173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
with gr.Row():
|
175 |
guidance_scale = gr.Slider(
|
176 |
value=Config.GUIDANCE_SCALE,
|
@@ -336,7 +375,7 @@ with gr.Blocks(
|
|
336 |
columns=2,
|
337 |
)
|
338 |
prompt = gr.Textbox(
|
339 |
-
placeholder="
|
340 |
autoscroll=False,
|
341 |
show_label=False,
|
342 |
label="Prompt",
|
@@ -448,6 +487,10 @@ with gr.Blocks(
|
|
448 |
image_prompt,
|
449 |
ip_image,
|
450 |
ip_face,
|
|
|
|
|
|
|
|
|
451 |
embeddings,
|
452 |
style,
|
453 |
seed,
|
@@ -475,10 +518,19 @@ if __name__ == "__main__":
|
|
475 |
args = parser.parse_args()
|
476 |
|
477 |
# download to hub cache
|
478 |
-
for repo_id, allow_patterns in Config.
|
479 |
-
print(f"Downloading {repo_id}...")
|
480 |
download_repo_files(repo_id, allow_patterns, token=Config.HF_TOKEN)
|
481 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
482 |
# https://www.gradio.app/docs/gradio/interface#interface-queue
|
483 |
demo.queue().launch(
|
484 |
server_name=args.server,
|
|
|
1 |
import argparse
|
2 |
import json
|
3 |
+
import os
|
4 |
import random
|
5 |
|
6 |
import gradio as gr
|
7 |
|
8 |
+
from lib import Config, async_call, download_civit_file, download_repo_files, generate, read_file
|
9 |
|
10 |
# the CSS `content` attribute expects a string so we need to wrap the number in quotes
|
11 |
refresh_seed_js = """
|
|
|
131 |
with gr.TabItem("⚙️ Settings"):
|
132 |
with gr.Group():
|
133 |
negative_prompt = gr.Textbox(
|
134 |
+
value="nsfw+",
|
135 |
label="Negative Prompt",
|
|
|
136 |
lines=2,
|
137 |
)
|
138 |
|
|
|
159 |
style = gr.Dropdown(
|
160 |
value=Config.STYLE,
|
161 |
label="Style",
|
162 |
+
min_width=240,
|
163 |
choices=[("None", "none")]
|
164 |
+ [(styles[sid]["name"], sid) for sid in style_ids],
|
165 |
)
|
|
|
172 |
min_width=240,
|
173 |
)
|
174 |
|
175 |
+
with gr.Row():
|
176 |
+
with gr.Group(elem_classes=["gap-0"]):
|
177 |
+
lora_1 = gr.Dropdown(
|
178 |
+
min_width=240,
|
179 |
+
label="LoRA #1",
|
180 |
+
value="none",
|
181 |
+
choices=[("None", "none")]
|
182 |
+
+ [
|
183 |
+
(lora["name"], lora_id)
|
184 |
+
for lora_id, lora in Config.CIVIT_LORAS.items()
|
185 |
+
],
|
186 |
+
)
|
187 |
+
lora_1_weight = gr.Slider(
|
188 |
+
value=0.0,
|
189 |
+
minimum=0.0,
|
190 |
+
maximum=1.0,
|
191 |
+
step=0.1,
|
192 |
+
show_label=False,
|
193 |
+
)
|
194 |
+
with gr.Group(elem_classes=["gap-0"]):
|
195 |
+
lora_2 = gr.Dropdown(
|
196 |
+
min_width=240,
|
197 |
+
label="LoRA #2",
|
198 |
+
value="none",
|
199 |
+
choices=[("None", "none")]
|
200 |
+
+ [
|
201 |
+
(lora["name"], lora_id)
|
202 |
+
for lora_id, lora in Config.CIVIT_LORAS.items()
|
203 |
+
],
|
204 |
+
)
|
205 |
+
lora_2_weight = gr.Slider(
|
206 |
+
value=0.0,
|
207 |
+
minimum=0.0,
|
208 |
+
maximum=1.0,
|
209 |
+
step=0.1,
|
210 |
+
show_label=False,
|
211 |
+
)
|
212 |
+
|
213 |
with gr.Row():
|
214 |
guidance_scale = gr.Slider(
|
215 |
value=Config.GUIDANCE_SCALE,
|
|
|
375 |
columns=2,
|
376 |
)
|
377 |
prompt = gr.Textbox(
|
378 |
+
placeholder="What do you want to see?",
|
379 |
autoscroll=False,
|
380 |
show_label=False,
|
381 |
label="Prompt",
|
|
|
487 |
image_prompt,
|
488 |
ip_image,
|
489 |
ip_face,
|
490 |
+
lora_1,
|
491 |
+
lora_1_weight,
|
492 |
+
lora_2,
|
493 |
+
lora_2_weight,
|
494 |
embeddings,
|
495 |
style,
|
496 |
seed,
|
|
|
518 |
args = parser.parse_args()
|
519 |
|
520 |
# download to hub cache
|
521 |
+
for repo_id, allow_patterns in Config.HF_MODELS.items():
|
|
|
522 |
download_repo_files(repo_id, allow_patterns, token=Config.HF_TOKEN)
|
523 |
|
524 |
+
# download civit loras
|
525 |
+
for lora_id, lora in Config.CIVIT_LORAS.items():
|
526 |
+
file_path = os.path.join(os.path.dirname(__file__), "loras")
|
527 |
+
download_civit_file(
|
528 |
+
lora_id,
|
529 |
+
lora["model_version_id"],
|
530 |
+
file_path=file_path,
|
531 |
+
token=Config.CIVIT_TOKEN,
|
532 |
+
)
|
533 |
+
|
534 |
# https://www.gradio.app/docs/gradio/interface#interface-queue
|
535 |
demo.queue().launch(
|
536 |
server_name=args.server,
|
cli.py
CHANGED
@@ -17,7 +17,7 @@ async def main():
|
|
17 |
parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
|
18 |
parser.add_argument("prompt", type=str, metavar="PROMPT")
|
19 |
parser.add_argument("-n", "--negative", type=str, metavar="STR", default="")
|
20 |
-
parser.add_argument("-e", "--
|
21 |
parser.add_argument("-s", "--seed", type=int, metavar="INT", default=Config.SEED)
|
22 |
parser.add_argument("-i", "--images", type=int, metavar="INT", default=1)
|
23 |
parser.add_argument("-f", "--filename", type=str, metavar="STR", default="image.png")
|
@@ -25,12 +25,16 @@ async def main():
|
|
25 |
parser.add_argument("-h", "--height", type=int, metavar="INT", default=Config.HEIGHT)
|
26 |
parser.add_argument("-m", "--model", type=str, metavar="STR", default=Config.MODEL)
|
27 |
parser.add_argument("-d", "--deepcache", type=int, metavar="INT", default=Config.DEEPCACHE_INTERVAL)
|
|
|
|
|
|
|
|
|
28 |
parser.add_argument("--scale", type=int, metavar="INT", choices=Config.SCALES, default=Config.SCALE)
|
29 |
parser.add_argument("--style", type=str, metavar="STR", default=Config.STYLE)
|
30 |
parser.add_argument("--scheduler", type=str, metavar="STR", default=Config.SCHEDULER)
|
31 |
parser.add_argument("--guidance", type=float, metavar="FLOAT", default=Config.GUIDANCE_SCALE)
|
32 |
parser.add_argument("--steps", type=int, metavar="INT", default=Config.INFERENCE_STEPS)
|
33 |
-
parser.add_argument("--strength", type=float, metavar="FLOAT", default=Config.DENOISING_STRENGTH)
|
34 |
parser.add_argument("--image", type=str, metavar="STR")
|
35 |
parser.add_argument("--ip-image", type=str, metavar="STR")
|
36 |
parser.add_argument("--ip-face", action="store_true")
|
@@ -48,7 +52,11 @@ async def main():
|
|
48 |
args.image,
|
49 |
args.ip_image,
|
50 |
args.ip_face,
|
51 |
-
args.
|
|
|
|
|
|
|
|
|
52 |
args.style,
|
53 |
args.seed,
|
54 |
args.model,
|
@@ -57,7 +65,7 @@ async def main():
|
|
57 |
args.height,
|
58 |
args.guidance,
|
59 |
args.steps,
|
60 |
-
args.
|
61 |
args.deepcache,
|
62 |
args.scale,
|
63 |
args.images,
|
@@ -66,7 +74,7 @@ async def main():
|
|
66 |
args.freeu,
|
67 |
args.clip_skip,
|
68 |
)
|
69 |
-
|
70 |
|
71 |
|
72 |
if __name__ == "__main__":
|
|
|
17 |
parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
|
18 |
parser.add_argument("prompt", type=str, metavar="PROMPT")
|
19 |
parser.add_argument("-n", "--negative", type=str, metavar="STR", default="")
|
20 |
+
parser.add_argument("-e", "--embeddings", type=str, metavar="STR", default="")
|
21 |
parser.add_argument("-s", "--seed", type=int, metavar="INT", default=Config.SEED)
|
22 |
parser.add_argument("-i", "--images", type=int, metavar="INT", default=1)
|
23 |
parser.add_argument("-f", "--filename", type=str, metavar="STR", default="image.png")
|
|
|
25 |
parser.add_argument("-h", "--height", type=int, metavar="INT", default=Config.HEIGHT)
|
26 |
parser.add_argument("-m", "--model", type=str, metavar="STR", default=Config.MODEL)
|
27 |
parser.add_argument("-d", "--deepcache", type=int, metavar="INT", default=Config.DEEPCACHE_INTERVAL)
|
28 |
+
parser.add_argument("--lora-1", type=str, metavar="STR", default="")
|
29 |
+
parser.add_argument("--lora-1-weight", type=float, metavar="FLOAT", default=0.0)
|
30 |
+
parser.add_argument("--lora-2", type=str, metavar="STR", default="")
|
31 |
+
parser.add_argument("--lora-2-weight", type=float, metavar="FLOAT", default=0.0)
|
32 |
parser.add_argument("--scale", type=int, metavar="INT", choices=Config.SCALES, default=Config.SCALE)
|
33 |
parser.add_argument("--style", type=str, metavar="STR", default=Config.STYLE)
|
34 |
parser.add_argument("--scheduler", type=str, metavar="STR", default=Config.SCHEDULER)
|
35 |
parser.add_argument("--guidance", type=float, metavar="FLOAT", default=Config.GUIDANCE_SCALE)
|
36 |
parser.add_argument("--steps", type=int, metavar="INT", default=Config.INFERENCE_STEPS)
|
37 |
+
parser.add_argument("--image-strength", type=float, metavar="FLOAT", default=Config.DENOISING_STRENGTH)
|
38 |
parser.add_argument("--image", type=str, metavar="STR")
|
39 |
parser.add_argument("--ip-image", type=str, metavar="STR")
|
40 |
parser.add_argument("--ip-face", action="store_true")
|
|
|
52 |
args.image,
|
53 |
args.ip_image,
|
54 |
args.ip_face,
|
55 |
+
args.lora_1,
|
56 |
+
args.lora_1_weight,
|
57 |
+
args.lora_2,
|
58 |
+
args.lora_2_weight,
|
59 |
+
args.embeddings.split(",") if args.embeddings else [],
|
60 |
args.style,
|
61 |
args.seed,
|
62 |
args.model,
|
|
|
65 |
args.height,
|
66 |
args.guidance,
|
67 |
args.steps,
|
68 |
+
args.image_strength,
|
69 |
args.deepcache,
|
70 |
args.scale,
|
71 |
args.images,
|
|
|
74 |
args.freeu,
|
75 |
args.clip_skip,
|
76 |
)
|
77 |
+
save_images(images, args.filename)
|
78 |
|
79 |
|
80 |
if __name__ == "__main__":
|
lib/__init__.py
CHANGED
@@ -2,13 +2,14 @@ from .config import Config
|
|
2 |
from .inference import generate
|
3 |
from .loader import Loader
|
4 |
from .upscaler import RealESRGAN
|
5 |
-
from .utils import async_call, download_repo_files, load_json, read_file
|
6 |
|
7 |
__all__ = [
|
8 |
"Config",
|
9 |
"Loader",
|
10 |
"RealESRGAN",
|
11 |
"async_call",
|
|
|
12 |
"download_repo_files",
|
13 |
"generate",
|
14 |
"load_json",
|
|
|
2 |
from .inference import generate
|
3 |
from .loader import Loader
|
4 |
from .upscaler import RealESRGAN
|
5 |
+
from .utils import async_call, download_civit_file, download_repo_files, load_json, read_file
|
6 |
|
7 |
__all__ = [
|
8 |
"Config",
|
9 |
"Loader",
|
10 |
"RealESRGAN",
|
11 |
"async_call",
|
12 |
+
"download_civit_file",
|
13 |
"download_repo_files",
|
14 |
"generate",
|
15 |
"load_json",
|
lib/config.py
CHANGED
@@ -14,7 +14,8 @@ from diffusers import (
|
|
14 |
|
15 |
Config = SimpleNamespace(
|
16 |
HF_TOKEN=os.environ.get("HF_TOKEN", None),
|
17 |
-
|
|
|
18 |
"Lykon/dreamshaper-8": [
|
19 |
"feature_extractor/preprocessor_config.json",
|
20 |
"safety_checker/config.json",
|
@@ -32,6 +33,22 @@ Config = SimpleNamespace(
|
|
32 |
"model_index.json",
|
33 |
],
|
34 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
MONO_FONTS=["monospace"],
|
36 |
SANS_FONTS=[
|
37 |
"sans-serif",
|
@@ -81,8 +98,8 @@ Config = SimpleNamespace(
|
|
81 |
"unrealistic_dream",
|
82 |
],
|
83 |
STYLE="enhance",
|
84 |
-
WIDTH=
|
85 |
-
HEIGHT=
|
86 |
NUM_IMAGES=1,
|
87 |
SEED=-1,
|
88 |
GUIDANCE_SCALE=5,
|
|
|
14 |
|
15 |
Config = SimpleNamespace(
|
16 |
HF_TOKEN=os.environ.get("HF_TOKEN", None),
|
17 |
+
CIVIT_TOKEN=os.environ.get("CIVIT_TOKEN", None),
|
18 |
+
HF_MODELS={
|
19 |
"Lykon/dreamshaper-8": [
|
20 |
"feature_extractor/preprocessor_config.json",
|
21 |
"safety_checker/config.json",
|
|
|
33 |
"model_index.json",
|
34 |
],
|
35 |
},
|
36 |
+
CIVIT_LORAS={
|
37 |
+
# https://civitai.com/models/411088?modelVersionId=486099
|
38 |
+
"perfection_style": {
|
39 |
+
"model_id": "411088",
|
40 |
+
"model_version_id": "486099",
|
41 |
+
"name": "Perfection Style",
|
42 |
+
"trigger": "perfection style",
|
43 |
+
},
|
44 |
+
# https://civitai.com/models/421162?modelVersionId=486110
|
45 |
+
"detailed_style": {
|
46 |
+
"model_id": "421162",
|
47 |
+
"model_version_id": "486110",
|
48 |
+
"name": "Detailed Style",
|
49 |
+
"trigger": "detailed style",
|
50 |
+
},
|
51 |
+
},
|
52 |
MONO_FONTS=["monospace"],
|
53 |
SANS_FONTS=[
|
54 |
"sans-serif",
|
|
|
98 |
"unrealistic_dream",
|
99 |
],
|
100 |
STYLE="enhance",
|
101 |
+
WIDTH=512,
|
102 |
+
HEIGHT=512,
|
103 |
NUM_IMAGES=1,
|
104 |
SEED=-1,
|
105 |
GUIDANCE_SCALE=5,
|
lib/inference.py
CHANGED
@@ -13,6 +13,7 @@ from compel.prompt_parser import PromptParser
|
|
13 |
from huggingface_hub.utils import HFValidationError, RepositoryNotFoundError
|
14 |
from PIL import Image
|
15 |
|
|
|
16 |
from .loader import Loader
|
17 |
from .utils import load_json
|
18 |
|
@@ -39,7 +40,7 @@ def parse_prompt_with_arrays(prompt: str) -> list[str]:
|
|
39 |
return prompts
|
40 |
|
41 |
|
42 |
-
def apply_style(positive_prompt, negative_prompt, style_id):
|
43 |
if style_id.lower() == "none":
|
44 |
return (positive_prompt, negative_prompt)
|
45 |
|
@@ -96,6 +97,10 @@ def generate(
|
|
96 |
image_prompt=None,
|
97 |
ip_image=None,
|
98 |
ip_face=False,
|
|
|
|
|
|
|
|
|
99 |
embeddings=[],
|
100 |
style=None,
|
101 |
seed=None,
|
@@ -176,7 +181,7 @@ def generate(
|
|
176 |
)
|
177 |
|
178 |
if loader.pipe is None:
|
179 |
-
raise Error(f"
|
180 |
|
181 |
pipe = loader.pipe
|
182 |
upscaler = None
|
@@ -186,9 +191,36 @@ def generate(
|
|
186 |
if scale == 4:
|
187 |
upscaler = loader.upscaler_4x
|
188 |
|
189 |
-
|
190 |
-
|
191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
for embedding in embeddings:
|
193 |
try:
|
194 |
# wrap embeddings in angle brackets
|
@@ -196,9 +228,8 @@ def generate(
|
|
196 |
pretrained_model_name_or_path=f"{embeddings_dir}/{embedding}.pt",
|
197 |
token=f"<{embedding}>",
|
198 |
)
|
199 |
-
embeddings_tokens.append(f"<{embedding}>")
|
200 |
except (EnvironmentError, HFValidationError, RepositoryNotFoundError):
|
201 |
-
raise Error(f"Invalid embedding:
|
202 |
|
203 |
# prompt embeds
|
204 |
compel = Compel(
|
@@ -212,7 +243,6 @@ def generate(
|
|
212 |
|
213 |
images = []
|
214 |
current_seed = seed
|
215 |
-
|
216 |
for i in range(num_images):
|
217 |
# seeded generator for each iteration
|
218 |
generator = torch.Generator(device=pipe.device).manual_seed(current_seed)
|
@@ -229,14 +259,18 @@ def generate(
|
|
229 |
if negative_styled.startswith("(), "):
|
230 |
negative_styled = negative_styled[4:]
|
231 |
|
232 |
-
|
233 |
-
|
|
|
|
|
|
|
234 |
|
|
|
235 |
positive_embeds, negative_embeds = compel.pad_conditioning_tensors_to_same_length(
|
236 |
[compel(positive_styled), compel(negative_styled)]
|
237 |
)
|
238 |
except PromptParser.ParsingException:
|
239 |
-
raise Error("
|
240 |
|
241 |
kwargs = {
|
242 |
"width": width,
|
@@ -244,8 +278,8 @@ def generate(
|
|
244 |
"generator": generator,
|
245 |
"prompt_embeds": positive_embeds,
|
246 |
"guidance_scale": guidance_scale,
|
247 |
-
"negative_prompt_embeds": negative_embeds,
|
248 |
"num_inference_steps": inference_steps,
|
|
|
249 |
"output_type": "np" if scale > 1 else "pil",
|
250 |
}
|
251 |
|
@@ -257,7 +291,7 @@ def generate(
|
|
257 |
kwargs["image"] = prepare_image(image_prompt, (width, height))
|
258 |
|
259 |
if IP_ADAPTER:
|
260 |
-
# don't resize full-face images
|
261 |
size = None if ip_face else (width, height)
|
262 |
kwargs["ip_adapter_image"] = prepare_image(ip_image, size)
|
263 |
|
@@ -268,9 +302,12 @@ def generate(
|
|
268 |
images.append((image, str(current_seed)))
|
269 |
current_seed += 1
|
270 |
except Exception as e:
|
271 |
-
raise Error(f"
|
272 |
finally:
|
273 |
-
|
|
|
|
|
|
|
274 |
CURRENT_STEP = 0
|
275 |
CURRENT_IMAGE += 1
|
276 |
|
|
|
13 |
from huggingface_hub.utils import HFValidationError, RepositoryNotFoundError
|
14 |
from PIL import Image
|
15 |
|
16 |
+
from .config import Config
|
17 |
from .loader import Loader
|
18 |
from .utils import load_json
|
19 |
|
|
|
40 |
return prompts
|
41 |
|
42 |
|
43 |
+
def apply_style(positive_prompt, negative_prompt, style_id="none"):
|
44 |
if style_id.lower() == "none":
|
45 |
return (positive_prompt, negative_prompt)
|
46 |
|
|
|
97 |
image_prompt=None,
|
98 |
ip_image=None,
|
99 |
ip_face=False,
|
100 |
+
lora_1=None,
|
101 |
+
lora_1_weight=0.0,
|
102 |
+
lora_2=None,
|
103 |
+
lora_2_weight=0.0,
|
104 |
embeddings=[],
|
105 |
style=None,
|
106 |
seed=None,
|
|
|
181 |
)
|
182 |
|
183 |
if loader.pipe is None:
|
184 |
+
raise Error(f"Error loading {model}")
|
185 |
|
186 |
pipe = loader.pipe
|
187 |
upscaler = None
|
|
|
191 |
if scale == 4:
|
192 |
upscaler = loader.upscaler_4x
|
193 |
|
194 |
+
# load loras
|
195 |
+
loras = []
|
196 |
+
weights = []
|
197 |
+
loras_and_weights = [(lora_1, lora_1_weight), (lora_2, lora_2_weight)]
|
198 |
+
loras_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "loras"))
|
199 |
+
for lora, weight in loras_and_weights:
|
200 |
+
if lora and lora.lower() != "none" and lora not in loras:
|
201 |
+
config = Config.CIVIT_LORAS.get(lora)
|
202 |
+
if config:
|
203 |
+
try:
|
204 |
+
pipe.load_lora_weights(
|
205 |
+
loras_dir,
|
206 |
+
adapter_name=lora,
|
207 |
+
weight_name=f"{lora}.{config['model_version_id']}.safetensors",
|
208 |
+
)
|
209 |
+
weights.append(weight)
|
210 |
+
loras.append(lora)
|
211 |
+
except Exception:
|
212 |
+
raise Error(f"Error loading {config['name']} LoRA")
|
213 |
+
|
214 |
+
# unload after generating or if there was an error
|
215 |
+
try:
|
216 |
+
if loras:
|
217 |
+
pipe.set_adapters(loras, adapter_weights=weights)
|
218 |
+
except Exception:
|
219 |
+
pipe.unload_lora_weights()
|
220 |
+
raise Error("Error setting LoRA weights")
|
221 |
+
|
222 |
+
# load embeddings
|
223 |
+
embeddings_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "embeddings"))
|
224 |
for embedding in embeddings:
|
225 |
try:
|
226 |
# wrap embeddings in angle brackets
|
|
|
228 |
pretrained_model_name_or_path=f"{embeddings_dir}/{embedding}.pt",
|
229 |
token=f"<{embedding}>",
|
230 |
)
|
|
|
231 |
except (EnvironmentError, HFValidationError, RepositoryNotFoundError):
|
232 |
+
raise Error(f"Invalid embedding: {embedding}")
|
233 |
|
234 |
# prompt embeds
|
235 |
compel = Compel(
|
|
|
243 |
|
244 |
images = []
|
245 |
current_seed = seed
|
|
|
246 |
for i in range(num_images):
|
247 |
# seeded generator for each iteration
|
248 |
generator = torch.Generator(device=pipe.device).manual_seed(current_seed)
|
|
|
259 |
if negative_styled.startswith("(), "):
|
260 |
negative_styled = negative_styled[4:]
|
261 |
|
262 |
+
for lora in loras:
|
263 |
+
positive_styled += f", {Config.CIVIT_LORAS[lora]['trigger']}"
|
264 |
+
|
265 |
+
for embedding in embeddings:
|
266 |
+
negative_styled += f", <{embedding}>"
|
267 |
|
268 |
+
# print prompts
|
269 |
positive_embeds, negative_embeds = compel.pad_conditioning_tensors_to_same_length(
|
270 |
[compel(positive_styled), compel(negative_styled)]
|
271 |
)
|
272 |
except PromptParser.ParsingException:
|
273 |
+
raise Error("Invalid prompt")
|
274 |
|
275 |
kwargs = {
|
276 |
"width": width,
|
|
|
278 |
"generator": generator,
|
279 |
"prompt_embeds": positive_embeds,
|
280 |
"guidance_scale": guidance_scale,
|
|
|
281 |
"num_inference_steps": inference_steps,
|
282 |
+
"negative_prompt_embeds": negative_embeds,
|
283 |
"output_type": "np" if scale > 1 else "pil",
|
284 |
}
|
285 |
|
|
|
291 |
kwargs["image"] = prepare_image(image_prompt, (width, height))
|
292 |
|
293 |
if IP_ADAPTER:
|
294 |
+
# don't resize full-face images since they are usually square crops
|
295 |
size = None if ip_face else (width, height)
|
296 |
kwargs["ip_adapter_image"] = prepare_image(ip_image, size)
|
297 |
|
|
|
302 |
images.append((image, str(current_seed)))
|
303 |
current_seed += 1
|
304 |
except Exception as e:
|
305 |
+
raise Error(f"{e}")
|
306 |
finally:
|
307 |
+
if embeddings:
|
308 |
+
pipe.unload_textual_inversion()
|
309 |
+
if loras:
|
310 |
+
pipe.unload_lora_weights()
|
311 |
CURRENT_STEP = 0
|
312 |
CURRENT_IMAGE += 1
|
313 |
|
lib/utils.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1 |
import functools
|
2 |
import inspect
|
3 |
import json
|
|
|
4 |
from typing import Callable, TypeVar
|
5 |
|
6 |
import anyio
|
|
|
7 |
from anyio import Semaphore
|
8 |
from huggingface_hub._snapshot_download import snapshot_download
|
9 |
from typing_extensions import ParamSpec
|
@@ -38,6 +40,34 @@ def download_repo_files(repo_id, allow_patterns, token=None):
|
|
38 |
)
|
39 |
|
40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
# like the original but supports args and kwargs instead of a dict
|
42 |
# https://github.com/huggingface/huggingface-inference-toolkit/blob/0.2.0/src/huggingface_inference_toolkit/async_utils.py
|
43 |
async def async_call(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
|
|
|
1 |
import functools
|
2 |
import inspect
|
3 |
import json
|
4 |
+
import os
|
5 |
from typing import Callable, TypeVar
|
6 |
|
7 |
import anyio
|
8 |
+
import httpx
|
9 |
from anyio import Semaphore
|
10 |
from huggingface_hub._snapshot_download import snapshot_download
|
11 |
from typing_extensions import ParamSpec
|
|
|
40 |
)
|
41 |
|
42 |
|
43 |
+
def download_civit_file(lora_id, version_id, file_path=".", token=None):
|
44 |
+
base_url = "https://civitai.com/api/download/models"
|
45 |
+
file = f"{file_path}/{lora_id}.{version_id}.safetensors"
|
46 |
+
|
47 |
+
if os.path.exists(file):
|
48 |
+
return
|
49 |
+
|
50 |
+
try:
|
51 |
+
params = {"token": token}
|
52 |
+
response = httpx.get(
|
53 |
+
f"{base_url}/{version_id}",
|
54 |
+
timeout=None,
|
55 |
+
params=params,
|
56 |
+
follow_redirects=True,
|
57 |
+
)
|
58 |
+
|
59 |
+
response.raise_for_status()
|
60 |
+
os.makedirs(file_path, exist_ok=True)
|
61 |
+
|
62 |
+
with open(file, "wb") as f:
|
63 |
+
f.write(response.content)
|
64 |
+
except httpx.HTTPStatusError as e:
|
65 |
+
print(e.request.url)
|
66 |
+
print(f"HTTPError: {e.response.status_code} {e.response.text}")
|
67 |
+
except httpx.RequestError as e:
|
68 |
+
print(f"RequestError: {e}")
|
69 |
+
|
70 |
+
|
71 |
# like the original but supports args and kwargs instead of a dict
|
72 |
# https://github.com/huggingface/huggingface-inference-toolkit/blob/0.2.0/src/huggingface_inference_toolkit/async_utils.py
|
73 |
async def async_call(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
|
requirements.txt
CHANGED
@@ -1,11 +1,13 @@
|
|
1 |
-
anyio==4.4.0
|
2 |
accelerate
|
3 |
-
|
4 |
compel==2.0.3
|
5 |
deepcache==0.1.1
|
6 |
diffusers==0.30.2
|
7 |
-
|
8 |
gradio==4.41.0
|
|
|
|
|
|
|
9 |
numpy==1.26.4
|
10 |
ruff==0.5.7
|
11 |
spaces
|
|
|
|
|
1 |
accelerate
|
2 |
+
anyio==4.4.0
|
3 |
compel==2.0.3
|
4 |
deepcache==0.1.1
|
5 |
diffusers==0.30.2
|
6 |
+
einops==0.8.0
|
7 |
gradio==4.41.0
|
8 |
+
h2
|
9 |
+
hf-transfer
|
10 |
+
httpx
|
11 |
numpy==1.26.4
|
12 |
ruff==0.5.7
|
13 |
spaces
|