InvokeAI 2.2.4.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. backend/__init__.py +0 -0
  2. backend/invoke_ai_web_server.py +1724 -0
  3. backend/modules/__init__.py +0 -0
  4. backend/modules/create_cmd_parser.py +55 -0
  5. backend/modules/get_canvas_generation_mode.py +117 -0
  6. backend/modules/parameters.py +71 -0
  7. backend/modules/parse_seed_weights.py +47 -0
  8. configs/INITIAL_MODELS.yaml +80 -0
  9. configs/models.yaml.example +29 -0
  10. configs/sd-concepts.txt +803 -0
  11. configs/stable-diffusion/v1-finetune.yaml +110 -0
  12. configs/stable-diffusion/v1-finetune_style.yaml +103 -0
  13. configs/stable-diffusion/v1-inference.yaml +79 -0
  14. configs/stable-diffusion/v1-inpainting-inference.yaml +79 -0
  15. configs/stable-diffusion/v1-m1-finetune.yaml +110 -0
  16. docs/assets/invoke-web-server-1.png +0 -0
  17. docs/assets/invoke_ai_banner.png +0 -0
  18. invokeai-2.2.4.5.data/data/assets/caution.png +0 -0
  19. invokeai-2.2.4.5.data/data/configs/INITIAL_MODELS.yaml +80 -0
  20. invokeai-2.2.4.5.data/data/configs/models.yaml.example +29 -0
  21. invokeai-2.2.4.5.data/data/configs/sd-concepts.txt +803 -0
  22. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-finetune.yaml +110 -0
  23. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-finetune_style.yaml +103 -0
  24. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-inference.yaml +79 -0
  25. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-inpainting-inference.yaml +79 -0
  26. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-m1-finetune.yaml +110 -0
  27. invokeai-2.2.4.5.data/data/frontend/dist/assets/Inter-Bold.790c108b.ttf +0 -0
  28. invokeai-2.2.4.5.data/data/frontend/dist/assets/Inter.b9a8e5e2.ttf +0 -0
  29. invokeai-2.2.4.5.data/data/frontend/dist/assets/favicon.0d253ced.ico +0 -0
  30. invokeai-2.2.4.5.data/data/frontend/dist/assets/index-legacy-b741077f.js +48 -0
  31. invokeai-2.2.4.5.data/data/frontend/dist/assets/index.0dadf5d0.css +1 -0
  32. invokeai-2.2.4.5.data/data/frontend/dist/assets/index.21ff81eb.js +625 -0
  33. invokeai-2.2.4.5.data/data/frontend/dist/assets/logo.13003d72.png +0 -0
  34. invokeai-2.2.4.5.data/data/frontend/dist/assets/polyfills-legacy-dde3a68a.js +4 -0
  35. invokeai-2.2.4.5.data/data/frontend/dist/assets/polyfills.1ff60148.js +1 -0
  36. invokeai-2.2.4.5.data/data/frontend/dist/index.html +23 -0
  37. invokeai-2.2.4.5.data/data/scripts/configure_invokeai.py +797 -0
  38. invokeai-2.2.4.5.data/data/scripts/create_root.py +43 -0
  39. invokeai-2.2.4.5.data/data/scripts/dream.py +10 -0
  40. invokeai-2.2.4.5.data/data/scripts/images2prompt.py +30 -0
  41. invokeai-2.2.4.5.data/data/scripts/invoke.py +5 -0
  42. invokeai-2.2.4.5.data/data/scripts/legacy_api.py +681 -0
  43. invokeai-2.2.4.5.data/data/scripts/load_models.py +12 -0
  44. invokeai-2.2.4.5.data/data/scripts/merge_embeddings.py +124 -0
  45. invokeai-2.2.4.5.data/data/scripts/preload_models.py +12 -0
  46. invokeai-2.2.4.5.data/data/scripts/sd-metadata.py +22 -0
  47. invokeai-2.2.4.5.dist-info/LICENSE +21 -0
  48. invokeai-2.2.4.5.dist-info/LICENSE-ModelWeights.txt +294 -0
  49. invokeai-2.2.4.5.dist-info/METADATA +288 -0
  50. invokeai-2.2.4.5.dist-info/RECORD +169 -0
  51. invokeai-2.2.4.5.dist-info/WHEEL +5 -0
  52. invokeai-2.2.4.5.dist-info/entry_points.txt +4 -0
  53. invokeai-2.2.4.5.dist-info/top_level.txt +6 -0
  54. invokeai-2.2.4.5.dist-info/zip-safe +1 -0
  55. ldm/__init__.py +0 -0
  56. ldm/data/__init__.py +0 -0
  57. ldm/data/base.py +31 -0
  58. ldm/data/imagenet.py +466 -0
  59. ldm/data/lsun.py +126 -0
  60. ldm/data/personalized.py +202 -0
  61. ldm/data/personalized_style.py +169 -0
  62. ldm/generate.py +1159 -0
  63. ldm/invoke/CLI.py +960 -0
  64. ldm/invoke/__init__.py +3 -0
  65. ldm/invoke/args.py +1257 -0
  66. ldm/invoke/concepts_lib.py +169 -0
  67. ldm/invoke/conditioning.py +289 -0
  68. ldm/invoke/devices.py +27 -0
  69. ldm/invoke/generator/__init__.py +4 -0
  70. ldm/invoke/generator/base.py +335 -0
  71. ldm/invoke/generator/embiggen.py +501 -0
  72. ldm/invoke/generator/img2img.py +97 -0
  73. ldm/invoke/generator/inpaint.py +358 -0
  74. ldm/invoke/generator/omnibus.py +175 -0
  75. ldm/invoke/generator/txt2img.py +88 -0
  76. ldm/invoke/generator/txt2img2img.py +182 -0
  77. ldm/invoke/globals.py +31 -0
  78. ldm/invoke/image_util.py +70 -0
  79. ldm/invoke/log.py +66 -0
  80. ldm/invoke/model_cache.py +449 -0
  81. ldm/invoke/patchmatch.py +44 -0
  82. ldm/invoke/pngwriter.py +113 -0
  83. ldm/invoke/prompt_parser.py +655 -0
  84. ldm/invoke/readline.py +442 -0
  85. ldm/invoke/restoration/__init__.py +4 -0
  86. ldm/invoke/restoration/base.py +38 -0
  87. ldm/invoke/restoration/codeformer.py +103 -0
  88. ldm/invoke/restoration/codeformer_arch.py +276 -0
  89. ldm/invoke/restoration/gfpgan.py +84 -0
  90. ldm/invoke/restoration/outcrop.py +108 -0
  91. ldm/invoke/restoration/outpaint.py +92 -0
  92. ldm/invoke/restoration/realesrgan.py +90 -0
  93. ldm/invoke/restoration/vqgan_arch.py +435 -0
  94. ldm/invoke/seamless.py +30 -0
  95. ldm/invoke/server.py +282 -0
  96. ldm/invoke/server_legacy.py +246 -0
  97. ldm/invoke/txt2mask.py +136 -0
  98. ldm/lr_scheduler.py +143 -0
  99. ldm/models/__init__.py +0 -0
  100. ldm/models/autoencoder.py +596 -0
  101. ldm/models/diffusion/__init__.py +0 -0
  102. ldm/models/diffusion/classifier.py +355 -0
  103. ldm/models/diffusion/cross_attention_control.py +443 -0
  104. ldm/models/diffusion/cross_attention_map_saving.py +95 -0
  105. ldm/models/diffusion/ddim.py +111 -0
  106. ldm/models/diffusion/ddpm.py +2263 -0
  107. ldm/models/diffusion/ksampler.py +312 -0
  108. ldm/models/diffusion/plms.py +146 -0
  109. ldm/models/diffusion/sampler.py +450 -0
  110. ldm/models/diffusion/shared_invokeai_diffusion.py +244 -0
  111. ldm/modules/__init__.py +0 -0
  112. ldm/modules/attention.py +274 -0
  113. ldm/modules/diffusionmodules/__init__.py +0 -0
  114. ldm/modules/diffusionmodules/model.py +894 -0
  115. ldm/modules/diffusionmodules/openaimodel.py +1036 -0
  116. ldm/modules/diffusionmodules/util.py +314 -0
  117. ldm/modules/distributions/__init__.py +0 -0
  118. ldm/modules/distributions/distributions.py +104 -0
  119. ldm/modules/ema.py +88 -0
  120. ldm/modules/embedding_manager.py +413 -0
  121. ldm/modules/encoders/__init__.py +0 -0
  122. ldm/modules/encoders/modules.py +772 -0
  123. ldm/modules/image_degradation/__init__.py +6 -0
  124. ldm/modules/image_degradation/bsrgan.py +846 -0
  125. ldm/modules/image_degradation/bsrgan_light.py +751 -0
  126. ldm/modules/image_degradation/utils_image.py +1024 -0
  127. ldm/modules/losses/__init__.py +1 -0
  128. ldm/modules/losses/contperceptual.py +175 -0
  129. ldm/modules/losses/vqperceptual.py +240 -0
  130. ldm/modules/x_transformer.py +752 -0
  131. ldm/simplet2i.py +13 -0
  132. ldm/util.py +264 -0
  133. scripts/configure_invokeai.py +797 -0
  134. scripts/create_root.py +43 -0
  135. scripts/dream.py +10 -0
  136. scripts/images2prompt.py +30 -0
  137. scripts/invoke.py +5 -0
  138. scripts/legacy_api.py +681 -0
  139. scripts/load_models.py +12 -0
  140. scripts/merge_embeddings.py +124 -0
  141. scripts/orig_scripts/img2img.py +296 -0
  142. scripts/orig_scripts/inpaint.py +98 -0
  143. scripts/orig_scripts/knn2img.py +397 -0
  144. scripts/orig_scripts/sample_diffusion.py +313 -0
  145. scripts/orig_scripts/train_searcher.py +147 -0
  146. scripts/orig_scripts/txt2img.py +324 -0
  147. scripts/preload_models.py +12 -0
  148. scripts/sd-metadata.py +22 -0
  149. vendored/clip/clip/__init__.py +1 -0
  150. vendored/clip/clip/bpe_simple_vocab_16e6.txt.gz +0 -0
  151. vendored/clip/clip/clip.py +237 -0
  152. vendored/clip/clip/model.py +436 -0
  153. vendored/clip/clip/simple_tokenizer.py +132 -0
  154. vendored/clip/hubconf.py +42 -0
  155. vendored/clip/setup.py +21 -0
  156. vendored/clip/tests/test_consistency.py +25 -0
  157. vendored/clipseg/datasets/coco_wrapper.py +99 -0
  158. vendored/clipseg/datasets/pascal_zeroshot.py +60 -0
  159. vendored/clipseg/datasets/pfe_dataset.py +129 -0
  160. vendored/clipseg/datasets/phrasecut.py +380 -0
  161. vendored/clipseg/datasets/utils.py +68 -0
  162. vendored/clipseg/evaluation_utils.py +292 -0
  163. vendored/clipseg/general_utils.py +272 -0
  164. vendored/clipseg/metrics.py +271 -0
  165. vendored/clipseg/models/clipseg.py +538 -0
  166. vendored/clipseg/models/vitseg.py +286 -0
  167. vendored/clipseg/score.py +453 -0
  168. vendored/clipseg/setup.py +30 -0
  169. vendored/clipseg/training.py +266 -0
@@ -0,0 +1,655 @@
1
+ import string
2
+ from typing import Union, Optional
3
+ import re
4
+ import pyparsing as pp
5
+ '''
6
+ This module parses prompt strings and produces tree-like structures that can be used generate and control the conditioning tensors.
7
+ weighted subprompts.
8
+
9
+ Useful class exports:
10
+
11
+ PromptParser - parses prompts
12
+
13
+ Useful function exports:
14
+
15
+ split_weighted_subpromopts() split subprompts, normalize and weight them
16
+ log_tokenization() print out colour-coded tokens and warn if truncated
17
+ '''
18
+
19
+ class Prompt():
20
+ """
21
+ Mid-level structure for storing the tree-like result of parsing a prompt. A Prompt may not represent the whole of
22
+ the singular user-defined "prompt string" (although it can) - for example, if the user specifies a Blend, the objects
23
+ that are to be blended together are stored individuall as Prompt objects.
24
+
25
+ Nesting makes this object not suitable for directly tokenizing; instead call flatten() on the containing Conjunction
26
+ to produce a FlattenedPrompt.
27
+ """
28
+ def __init__(self, parts: list):
29
+ for c in parts:
30
+ if type(c) is not Attention and not issubclass(type(c), BaseFragment) and type(c) is not pp.ParseResults:
31
+ raise PromptParser.ParsingException(f"Prompt cannot contain {type(c).__name__} ({c}), only {[c.__name__ for c in BaseFragment.__subclasses__()]} are allowed")
32
+ self.children = parts
33
+ def __repr__(self):
34
+ return f"Prompt:{self.children}"
35
+ def __eq__(self, other):
36
+ return type(other) is Prompt and other.children == self.children
37
+
38
+ class BaseFragment:
39
+ pass
40
+
41
+ class FlattenedPrompt():
42
+ """
43
+ A Prompt that has been passed through flatten(). Its children can be readily tokenized.
44
+ """
45
+ def __init__(self, parts: list=[]):
46
+ self.children = []
47
+ for part in parts:
48
+ self.append(part)
49
+
50
+ def append(self, fragment: Union[list, BaseFragment, tuple]):
51
+ # verify type correctness
52
+ if type(fragment) is list:
53
+ for x in fragment:
54
+ self.append(x)
55
+ elif issubclass(type(fragment), BaseFragment):
56
+ self.children.append(fragment)
57
+ elif type(fragment) is tuple:
58
+ # upgrade tuples to Fragments
59
+ if type(fragment[0]) is not str or (type(fragment[1]) is not float and type(fragment[1]) is not int):
60
+ raise PromptParser.ParsingException(
61
+ f"FlattenedPrompt cannot contain {fragment}, only Fragments or (str, float) tuples are allowed")
62
+ self.children.append(Fragment(fragment[0], fragment[1]))
63
+ else:
64
+ raise PromptParser.ParsingException(
65
+ f"FlattenedPrompt cannot contain {fragment}, only Fragments or (str, float) tuples are allowed")
66
+
67
+ @property
68
+ def is_empty(self):
69
+ return len(self.children) == 0 or \
70
+ (len(self.children) == 1 and len(self.children[0].text) == 0)
71
+
72
+ @property
73
+ def wants_cross_attention_control(self):
74
+ return any(
75
+ [issubclass(type(x), CrossAttentionControlledFragment) for x in self.children]
76
+ )
77
+
78
+ def __repr__(self):
79
+ return f"FlattenedPrompt:{self.children}"
80
+ def __eq__(self, other):
81
+ return type(other) is FlattenedPrompt and other.children == self.children
82
+
83
+
84
+ class Fragment(BaseFragment):
85
+ """
86
+ A Fragment is a chunk of plain text and an optional weight. The text should be passed as-is to the CLIP tokenizer.
87
+ """
88
+ def __init__(self, text: str, weight: float=1):
89
+ assert(type(text) is str)
90
+ if '\\"' in text or '\\(' in text or '\\)' in text:
91
+ #print("Fragment converting escaped \( \) \\\" into ( ) \"")
92
+ text = text.replace('\\(', '(').replace('\\)', ')').replace('\\"', '"')
93
+ self.text = text
94
+ self.weight = float(weight)
95
+
96
+ def __repr__(self):
97
+ return "Fragment:'"+self.text+"'@"+str(self.weight)
98
+ def __eq__(self, other):
99
+ return type(other) is Fragment \
100
+ and other.text == self.text \
101
+ and other.weight == self.weight
102
+
103
+ class Attention():
104
+ """
105
+ Nestable weight control for fragments. Each object in the children array may in turn be an Attention object;
106
+ weights should be considered to accumulate as the tree is traversed to deeper levels of nesting.
107
+
108
+ Do not traverse directly; instead obtain a FlattenedPrompt by calling Flatten() on a top-level Conjunction object.
109
+ """
110
+ def __init__(self, weight: float, children: list):
111
+ if type(weight) is not float:
112
+ raise PromptParser.ParsingException(
113
+ f"Attention weight must be float (got {type(weight).__name__} {weight})")
114
+ self.weight = weight
115
+ if type(children) is not list:
116
+ raise PromptParser.ParsingException(f"cannot make Attention with non-list of children (got {type(children)})")
117
+ assert(type(children) is list)
118
+ self.children = children
119
+ #print(f"A: requested attention '{children}' to {weight}")
120
+
121
+ def __repr__(self):
122
+ return f"Attention:{self.children} * {self.weight}"
123
+ def __eq__(self, other):
124
+ return type(other) is Attention and other.weight == self.weight and other.fragment == self.fragment
125
+
126
+ class CrossAttentionControlledFragment(BaseFragment):
127
+ pass
128
+
129
+ class CrossAttentionControlSubstitute(CrossAttentionControlledFragment):
130
+ """
131
+ A Cross-Attention Controlled ('prompt2prompt') fragment, for use inside a Prompt, Attention, or FlattenedPrompt.
132
+ Representing an "original" word sequence that supplies feature vectors for an initial diffusion operation, and an
133
+ "edited" word sequence, to which the attention maps produced by the "original" word sequence are applied. Intuitively,
134
+ the result should be an "edited" image that looks like the "original" image with concepts swapped.
135
+
136
+ eg "a cat sitting on a car" (original) -> "a smiling dog sitting on a car" (edited): the edited image should look
137
+ almost exactly the same as the original, but with a smiling dog rendered in place of the cat. The
138
+ CrossAttentionControlSubstitute object representing this swap may be confined to the tokens being swapped:
139
+ CrossAttentionControlSubstitute(original=[Fragment('cat')], edited=[Fragment('dog')])
140
+ or it may represent a larger portion of the token sequence:
141
+ CrossAttentionControlSubstitute(original=[Fragment('a cat sitting on a car')],
142
+ edited=[Fragment('a smiling dog sitting on a car')])
143
+
144
+ In either case expect it to be embedded in a Prompt or FlattenedPrompt:
145
+ FlattenedPrompt([
146
+ Fragment('a'),
147
+ CrossAttentionControlSubstitute(original=[Fragment('cat')], edited=[Fragment('dog')]),
148
+ Fragment('sitting on a car')
149
+ ])
150
+ """
151
+ def __init__(self, original: list, edited: list, options: dict=None):
152
+ self.original = original if len(original)>0 else [Fragment('')]
153
+ self.edited = edited if len(edited)>0 else [Fragment('')]
154
+
155
+ default_options = {
156
+ 's_start': 0.0,
157
+ 's_end': 0.2062994740159002, # ~= shape_freedom=0.5
158
+ 't_start': 0.0,
159
+ 't_end': 1.0
160
+ }
161
+ merged_options = default_options
162
+ if options is not None:
163
+ shape_freedom = options.pop('shape_freedom', None)
164
+ if shape_freedom is not None:
165
+ # high shape freedom = SD can do what it wants with the shape of the object
166
+ # high shape freedom => s_end = 0
167
+ # low shape freedom => s_end = 1
168
+ # shape freedom is in a "linear" space, while noticeable changes to s_end are typically closer around 0,
169
+ # and there is very little perceptible difference as s_end increases above 0.5
170
+ # so for shape_freedom = 0.5 we probably want s_end to be 0.2
171
+ # -> cube root and subtract from 1.0
172
+ merged_options['s_end'] = 1.0 - shape_freedom ** (1. / 3.)
173
+ #print('converted shape_freedom argument to', merged_options)
174
+ merged_options.update(options)
175
+
176
+ self.options = merged_options
177
+
178
+ def __repr__(self):
179
+ return f"CrossAttentionControlSubstitute:({self.original}->{self.edited} ({self.options})"
180
+ def __eq__(self, other):
181
+ return type(other) is CrossAttentionControlSubstitute \
182
+ and other.original == self.original \
183
+ and other.edited == self.edited \
184
+ and other.options == self.options
185
+
186
+
187
+ class CrossAttentionControlAppend(CrossAttentionControlledFragment):
188
+ def __init__(self, fragment: Fragment):
189
+ self.fragment = fragment
190
+ def __repr__(self):
191
+ return "CrossAttentionControlAppend:",self.fragment
192
+ def __eq__(self, other):
193
+ return type(other) is CrossAttentionControlAppend \
194
+ and other.fragment == self.fragment
195
+
196
+
197
+
198
+ class Conjunction():
199
+ """
200
+ Storage for one or more Prompts or Blends, each of which is to be separately diffused and then the results merged
201
+ by weighted sum in latent space.
202
+ """
203
+ def __init__(self, prompts: list, weights: list = None):
204
+ # force everything to be a Prompt
205
+ #print("making conjunction with", prompts, "types", [type(p).__name__ for p in prompts])
206
+ self.prompts = [x if (type(x) is Prompt
207
+ or type(x) is Blend
208
+ or type(x) is FlattenedPrompt)
209
+ else Prompt(x) for x in prompts]
210
+ self.weights = [1.0]*len(self.prompts) if (weights is None or len(weights)==0) else list(weights)
211
+ if len(self.weights) != len(self.prompts):
212
+ raise PromptParser.ParsingException(f"while parsing Conjunction: mismatched parts/weights counts {prompts}, {weights}")
213
+ self.type = 'AND'
214
+
215
+ def __repr__(self):
216
+ return f"Conjunction:{self.prompts} | weights {self.weights}"
217
+ def __eq__(self, other):
218
+ return type(other) is Conjunction \
219
+ and other.prompts == self.prompts \
220
+ and other.weights == self.weights
221
+
222
+
223
+ class Blend():
224
+ """
225
+ Stores a Blend of multiple Prompts. To apply, build feature vectors for each of the child Prompts and then perform a
226
+ weighted blend of the feature vectors to produce a single feature vector that is effectively a lerp between the
227
+ Prompts.
228
+ """
229
+ def __init__(self, prompts: list, weights: list[float], normalize_weights: bool=True):
230
+ #print("making Blend with prompts", prompts, "and weights", weights)
231
+ weights = [1.0]*len(prompts) if (weights is None or len(weights)==0) else list(weights)
232
+ if len(prompts) != len(weights):
233
+ raise PromptParser.ParsingException(f"while parsing Blend: mismatched prompts/weights counts {prompts}, {weights}")
234
+ for p in prompts:
235
+ if type(p) is not Prompt and type(p) is not FlattenedPrompt:
236
+ raise(PromptParser.ParsingException(f"{type(p)} cannot be added to a Blend, only Prompts or FlattenedPrompts"))
237
+ for f in p.children:
238
+ if isinstance(f, CrossAttentionControlSubstitute):
239
+ raise(PromptParser.ParsingException(f"while parsing Blend: sorry, you cannot do .swap() as part of a Blend"))
240
+
241
+ # upcast all lists to Prompt objects
242
+ self.prompts = [x if (type(x) is Prompt or type(x) is FlattenedPrompt)
243
+ else Prompt(x)
244
+ for x in prompts]
245
+ self.prompts = prompts
246
+ self.weights = weights
247
+ self.normalize_weights = normalize_weights
248
+
249
+ @property
250
+ def wants_cross_attention_control(self):
251
+ # blends cannot cross-attention control
252
+ return False
253
+
254
+
255
+ def __repr__(self):
256
+ return f"Blend:{self.prompts} | weights {' ' if self.normalize_weights else '(non-normalized) '}{self.weights}"
257
+ def __eq__(self, other):
258
+ return other.__repr__() == self.__repr__()
259
+
260
+
261
+ class PromptParser():
262
+
263
+ class ParsingException(Exception):
264
+ pass
265
+
266
+ class UnrecognizedOperatorException(ParsingException):
267
+ def __init__(self, operator:str):
268
+ super().__init__("Unrecognized operator: " + operator)
269
+
270
+ def __init__(self, attention_plus_base=1.1, attention_minus_base=0.9):
271
+
272
+ self.conjunction, self.prompt = build_parser_syntax(attention_plus_base, attention_minus_base)
273
+
274
+
275
+ def parse_conjunction(self, prompt: str) -> Conjunction:
276
+ '''
277
+ :param prompt: The prompt string to parse
278
+ :return: a Conjunction representing the parsed results.
279
+ '''
280
+ #print(f"!!parsing '{prompt}'")
281
+
282
+ if len(prompt.strip()) == 0:
283
+ return Conjunction(prompts=[FlattenedPrompt([('', 1.0)])], weights=[1.0])
284
+
285
+ root = self.conjunction.parse_string(prompt)
286
+ #print(f"'{prompt}' parsed to root", root)
287
+ #fused = fuse_fragments(parts)
288
+ #print("fused to", fused)
289
+
290
+ return self.flatten(root[0])
291
+
292
+ def parse_legacy_blend(self, text: str, skip_normalize: bool) -> Optional[Blend]:
293
+ weighted_subprompts = split_weighted_subprompts(text, skip_normalize=skip_normalize)
294
+ if len(weighted_subprompts) <= 1:
295
+ return None
296
+ strings = [x[0] for x in weighted_subprompts]
297
+ weights = [x[1] for x in weighted_subprompts]
298
+
299
+ parsed_conjunctions = [self.parse_conjunction(x) for x in strings]
300
+ flattened_prompts = [x.prompts[0] for x in parsed_conjunctions]
301
+
302
+ return Blend(prompts=flattened_prompts, weights=weights, normalize_weights=not skip_normalize)
303
+
304
+
305
+ def flatten(self, root: Conjunction, verbose = False) -> Conjunction:
306
+ """
307
+ Flattening a Conjunction traverses all of the nested tree-like structures in each of its Prompts or Blends,
308
+ producing from each of these walks a linear sequence of Fragment or CrossAttentionControlSubstitute objects
309
+ that can be readily tokenized without the need to walk a complex tree structure.
310
+
311
+ :param root: The Conjunction to flatten.
312
+ :return: A Conjunction containing the result of flattening each of the prompts in the passed-in root.
313
+ """
314
+
315
+ def fuse_fragments(items):
316
+ # print("fusing fragments in ", items)
317
+ result = []
318
+ for x in items:
319
+ if type(x) is CrossAttentionControlSubstitute:
320
+ original_fused = fuse_fragments(x.original)
321
+ edited_fused = fuse_fragments(x.edited)
322
+ result.append(CrossAttentionControlSubstitute(original_fused, edited_fused, options=x.options))
323
+ else:
324
+ last_weight = result[-1].weight \
325
+ if (len(result) > 0 and not issubclass(type(result[-1]), CrossAttentionControlledFragment)) \
326
+ else None
327
+ this_text = x.text
328
+ this_weight = x.weight
329
+ if last_weight is not None and last_weight == this_weight:
330
+ last_text = result[-1].text
331
+ result[-1] = Fragment(last_text + ' ' + this_text, last_weight)
332
+ else:
333
+ result.append(x)
334
+ return result
335
+
336
+ def flatten_internal(node, weight_scale, results, prefix):
337
+ verbose and print(prefix + "flattening", node, "...")
338
+ if type(node) is pp.ParseResults or type(node) is list:
339
+ for x in node:
340
+ results = flatten_internal(x, weight_scale, results, prefix+' pr ')
341
+ #print(prefix, " ParseResults expanded, results is now", results)
342
+ elif type(node) is Attention:
343
+ # if node.weight < 1:
344
+ # todo: inject a blend when flattening attention with weight <1"
345
+ for index,c in enumerate(node.children):
346
+ results = flatten_internal(c, weight_scale * node.weight, results, prefix + f" att{index} ")
347
+ elif type(node) is Fragment:
348
+ results += [Fragment(node.text, node.weight*weight_scale)]
349
+ elif type(node) is CrossAttentionControlSubstitute:
350
+ original = flatten_internal(node.original, weight_scale, [], prefix + ' CAo ')
351
+ edited = flatten_internal(node.edited, weight_scale, [], prefix + ' CAe ')
352
+ results += [CrossAttentionControlSubstitute(original, edited, options=node.options)]
353
+ elif type(node) is Blend:
354
+ flattened_subprompts = []
355
+ #print(" flattening blend with prompts", node.prompts, "weights", node.weights)
356
+ for prompt in node.prompts:
357
+ # prompt is a list
358
+ flattened_subprompts = flatten_internal(prompt, weight_scale, flattened_subprompts, prefix+'B ')
359
+ results += [Blend(prompts=flattened_subprompts, weights=node.weights, normalize_weights=node.normalize_weights)]
360
+ elif type(node) is Prompt:
361
+ #print(prefix + "about to flatten Prompt with children", node.children)
362
+ flattened_prompt = []
363
+ for child in node.children:
364
+ flattened_prompt = flatten_internal(child, weight_scale, flattened_prompt, prefix+'P ')
365
+ results += [FlattenedPrompt(parts=fuse_fragments(flattened_prompt))]
366
+ #print(prefix + "after flattening Prompt, results is", results)
367
+ else:
368
+ raise PromptParser.ParsingException(f"unhandled node type {type(node)} when flattening {node}")
369
+ verbose and print(prefix + "-> after flattening", type(node).__name__, "results is", results)
370
+ return results
371
+
372
+ verbose and print("flattening", root)
373
+
374
+ flattened_parts = []
375
+ for part in root.prompts:
376
+ flattened_parts += flatten_internal(part, 1.0, [], ' C| ')
377
+
378
+ verbose and print("flattened to", flattened_parts)
379
+
380
+ weights = root.weights
381
+ return Conjunction(flattened_parts, weights)
382
+
383
+
384
+
385
+
386
+ def build_parser_syntax(attention_plus_base: float, attention_minus_base: float):
387
+ def make_operator_object(x):
388
+ #print('making operator for', x)
389
+ target = x[0]
390
+ operator = x[1]
391
+ arguments = x[2]
392
+ if operator == '.attend':
393
+ weight_raw = arguments[0]
394
+ weight = 1.0
395
+ if type(weight_raw) is float or type(weight_raw) is int:
396
+ weight = weight_raw
397
+ elif type(weight_raw) is str:
398
+ base = attention_plus_base if weight_raw[0] == '+' else attention_minus_base
399
+ weight = pow(base, len(weight_raw))
400
+ return Attention(weight=weight, children=[x for x in x[0]])
401
+ elif operator == '.swap':
402
+ return CrossAttentionControlSubstitute(target, arguments, x.as_dict())
403
+ elif operator == '.blend':
404
+ prompts = [Prompt(p) for p in x[0]]
405
+ weights_raw = x[2]
406
+ normalize_weights = True
407
+ if len(weights_raw) > 0 and weights_raw[-1][0] == 'no_normalize':
408
+ normalize_weights = False
409
+ weights_raw = weights_raw[:-1]
410
+ weights = [float(w[0]) for w in weights_raw]
411
+ return Blend(prompts=prompts, weights=weights, normalize_weights=normalize_weights)
412
+ elif operator == '.and' or operator == '.add':
413
+ prompts = [Prompt(p) for p in x[0]]
414
+ weights = [float(w[0]) for w in x[2]]
415
+ return Conjunction(prompts=prompts, weights=weights)
416
+
417
+ raise PromptParser.UnrecognizedOperatorException(operator)
418
+
419
+ def parse_fragment_str(x, expression: pp.ParseExpression, in_quotes: bool = False, in_parens: bool = False):
420
+ #print(f"parsing fragment string for {x}")
421
+ fragment_string = x[0]
422
+ if len(fragment_string.strip()) == 0:
423
+ return Fragment('')
424
+
425
+ if in_quotes:
426
+ # escape unescaped quotes
427
+ fragment_string = fragment_string.replace('"', '\\"')
428
+
429
+ try:
430
+ result = (expression + pp.StringEnd()).parse_string(fragment_string)
431
+ #print("parsed to", result)
432
+ return result
433
+ except pp.ParseException as e:
434
+ #print("parse_fragment_str couldn't parse prompt string:", e)
435
+ raise
436
+
437
+ # meaningful symbols
438
+ lparen = pp.Literal("(").suppress()
439
+ rparen = pp.Literal(")").suppress()
440
+ quote = pp.Literal('"').suppress()
441
+ comma = pp.Literal(",").suppress()
442
+ dot = pp.Literal(".").suppress()
443
+ equals = pp.Literal("=").suppress()
444
+
445
+ escaped_lparen = pp.Literal('\\(')
446
+ escaped_rparen = pp.Literal('\\)')
447
+ escaped_quote = pp.Literal('\\"')
448
+ escaped_comma = pp.Literal('\\,')
449
+ escaped_dot = pp.Literal('\\.')
450
+ escaped_plus = pp.Literal('\\+')
451
+ escaped_minus = pp.Literal('\\-')
452
+ escaped_equals = pp.Literal('\\=')
453
+
454
+ syntactic_symbols = {
455
+ '(': escaped_lparen,
456
+ ')': escaped_rparen,
457
+ '"': escaped_quote,
458
+ ',': escaped_comma,
459
+ '.': escaped_dot,
460
+ '+': escaped_plus,
461
+ '-': escaped_minus,
462
+ '=': escaped_equals,
463
+ }
464
+ syntactic_chars = "".join(syntactic_symbols.keys())
465
+
466
+ # accepts int or float notation, always maps to float
467
+ number = pp.pyparsing_common.real | \
468
+ pp.Combine(pp.Optional("-")+pp.Word(pp.nums)).set_parse_action(pp.token_map(float))
469
+
470
+ # for options
471
+ keyword = pp.Word(pp.alphanums + '_')
472
+
473
+ # a word that absolutely does not contain any meaningful syntax
474
+ non_syntax_word = pp.Combine(pp.OneOrMore(pp.MatchFirst([
475
+ pp.Or(syntactic_symbols.values()),
476
+ pp.one_of(['-', '+']) + pp.NotAny(pp.White() | pp.Char(syntactic_chars) | pp.StringEnd()),
477
+ # build character-by-character
478
+ pp.CharsNotIn(string.whitespace + syntactic_chars, exact=1)
479
+ ])))
480
+ non_syntax_word.set_parse_action(lambda x: [Fragment(t) for t in x])
481
+ non_syntax_word.set_name('non_syntax_word')
482
+ non_syntax_word.set_debug(False)
483
+
484
+ # a word that can contain any character at all - greedily consumes syntax, so use with care
485
+ free_word = pp.CharsNotIn(string.whitespace).set_parse_action(lambda x: Fragment(x[0]))
486
+ free_word.set_name('free_word')
487
+ free_word.set_debug(False)
488
+
489
+
490
+ # ok here we go. forward declare some things..
491
+ attention = pp.Forward()
492
+ cross_attention_substitute = pp.Forward()
493
+ parenthesized_fragment = pp.Forward()
494
+ quoted_fragment = pp.Forward()
495
+
496
+ # the types of things that can go into a fragment, consisting of syntax-full and/or strictly syntax-free components
497
+ fragment_part_expressions = [
498
+ attention,
499
+ cross_attention_substitute,
500
+ parenthesized_fragment,
501
+ quoted_fragment,
502
+ non_syntax_word
503
+ ]
504
+ # a fragment that is permitted to contain commas
505
+ fragment_including_commas = pp.ZeroOrMore(pp.MatchFirst(
506
+ fragment_part_expressions + [
507
+ pp.Literal(',').set_parse_action(lambda x: Fragment(x[0]))
508
+ ]
509
+ ))
510
+ # a fragment that is not permitted to contain commas
511
+ fragment_excluding_commas = pp.ZeroOrMore(pp.MatchFirst(
512
+ fragment_part_expressions
513
+ ))
514
+
515
+ # a fragment in double quotes (may be nested)
516
+ quoted_fragment << pp.QuotedString(quote_char='"', esc_char=None, esc_quote='\\"')
517
+ quoted_fragment.set_parse_action(lambda x: parse_fragment_str(x, fragment_including_commas, in_quotes=True))
518
+
519
+ # a fragment inside parentheses (may be nested)
520
+ parenthesized_fragment << (lparen + fragment_including_commas + rparen)
521
+ parenthesized_fragment.set_name('parenthesized_fragment')
522
+ parenthesized_fragment.set_debug(False)
523
+
524
+ # a string of the form (<keyword>=<float|keyword> | <float> | <keyword>) where keyword is alphanumeric + '_'
525
+ option = pp.Group(pp.MatchFirst([
526
+ keyword + equals + (number | keyword), # option=value
527
+ number.copy().set_parse_action(pp.token_map(str)), # weight
528
+ keyword # flag
529
+ ]))
530
+ # options for an operator, eg "s_start=0.1, 0.3, no_normalize"
531
+ options = pp.Dict(pp.Optional(pp.delimited_list(option)))
532
+ options.set_name('options')
533
+ options.set_debug(False)
534
+
535
+ # a fragment which can be used as the target for an operator - either quoted or in parentheses, or a bare vanilla word
536
+ potential_operator_target = (quoted_fragment | parenthesized_fragment | non_syntax_word)
537
+
538
+ # a fragment whose weight has been increased or decreased by a given amount
539
+ attention_weight_operator = pp.Word('+') | pp.Word('-') | number
540
+ attention_explicit = (
541
+ pp.Group(potential_operator_target)
542
+ + pp.Literal('.attend')
543
+ + lparen
544
+ + pp.Group(attention_weight_operator)
545
+ + rparen
546
+ )
547
+ attention_explicit.set_parse_action(make_operator_object)
548
+ attention_implicit = (
549
+ pp.Group(potential_operator_target)
550
+ + pp.NotAny(pp.White()) # do not permit whitespace between term and operator
551
+ + pp.Group(attention_weight_operator)
552
+ )
553
+ attention_implicit.set_parse_action(lambda x: make_operator_object([x[0], '.attend', x[1]]))
554
+ attention << (attention_explicit | attention_implicit)
555
+ attention.set_name('attention')
556
+ attention.set_debug(False)
557
+
558
+ # cross-attention control by swapping one fragment for another
559
+ cross_attention_substitute << (
560
+ pp.Group(potential_operator_target).set_name('ca-target').set_debug(False)
561
+ + pp.Literal(".swap").set_name('ca-operator').set_debug(False)
562
+ + lparen
563
+ + pp.Group(fragment_excluding_commas).set_name('ca-replacement').set_debug(False)
564
+ + pp.Optional(comma + options).set_name('ca-options').set_debug(False)
565
+ + rparen
566
+ )
567
+ cross_attention_substitute.set_name('cross_attention_substitute')
568
+ cross_attention_substitute.set_debug(False)
569
+ cross_attention_substitute.set_parse_action(make_operator_object)
570
+
571
+
572
+ # an entire self-contained prompt, which can be used in a Blend or Conjunction
573
+ prompt = pp.ZeroOrMore(pp.MatchFirst([
574
+ cross_attention_substitute,
575
+ attention,
576
+ quoted_fragment,
577
+ parenthesized_fragment,
578
+ free_word,
579
+ pp.White().suppress()
580
+ ]))
581
+ quoted_prompt = quoted_fragment.copy().set_parse_action(lambda x: parse_fragment_str(x, prompt, in_quotes=True))
582
+
583
+
584
+ # a blend/lerp between the feature vectors for two or more prompts
585
+ blend = (
586
+ lparen
587
+ + pp.Group(pp.delimited_list(pp.Group(potential_operator_target | quoted_prompt), min=1)).set_name('bl-target').set_debug(False)
588
+ + rparen
589
+ + pp.Literal(".blend").set_name('bl-operator').set_debug(False)
590
+ + lparen
591
+ + pp.Group(options).set_name('bl-options').set_debug(False)
592
+ + rparen
593
+ )
594
+ blend.set_name('blend')
595
+ blend.set_debug(False)
596
+ blend.set_parse_action(make_operator_object)
597
+
598
+ # an operator to direct stable diffusion to step multiple times, once for each target, and then add the results together with different weights
599
+ explicit_conjunction = (
600
+ lparen
601
+ + pp.Group(pp.delimited_list(pp.Group(potential_operator_target | quoted_prompt), min=1)).set_name('cj-target').set_debug(False)
602
+ + rparen
603
+ + pp.one_of([".and", ".add"]).set_name('cj-operator').set_debug(False)
604
+ + lparen
605
+ + pp.Group(options).set_name('cj-options').set_debug(False)
606
+ + rparen
607
+ )
608
+ explicit_conjunction.set_name('explicit_conjunction')
609
+ explicit_conjunction.set_debug(False)
610
+ explicit_conjunction.set_parse_action(make_operator_object)
611
+
612
+ # by default a prompt consists of a Conjunction with a single term
613
+ implicit_conjunction = (blend | pp.Group(prompt)) + pp.StringEnd()
614
+ implicit_conjunction.set_parse_action(lambda x: Conjunction(x))
615
+
616
+ conjunction = (explicit_conjunction | implicit_conjunction)
617
+
618
+ return conjunction, prompt
619
+
620
+
621
+ def split_weighted_subprompts(text, skip_normalize=False)->list:
622
+ """
623
+ Legacy blend parsing.
624
+
625
+ grabs all text up to the first occurrence of ':'
626
+ uses the grabbed text as a sub-prompt, and takes the value following ':' as weight
627
+ if ':' has no value defined, defaults to 1.0
628
+ repeats until no text remaining
629
+ """
630
+ prompt_parser = re.compile("""
631
+ (?P<prompt> # capture group for 'prompt'
632
+ (?:\\\:|[^:])+ # match one or more non ':' characters or escaped colons '\:'
633
+ ) # end 'prompt'
634
+ (?: # non-capture group
635
+ :+ # match one or more ':' characters
636
+ (?P<weight> # capture group for 'weight'
637
+ -?\d+(?:\.\d+)? # match positive or negative integer or decimal number
638
+ )? # end weight capture group, make optional
639
+ \s* # strip spaces after weight
640
+ | # OR
641
+ $ # else, if no ':' then match end of line
642
+ ) # end non-capture group
643
+ """, re.VERBOSE)
644
+ parsed_prompts = [(match.group("prompt").replace("\\:", ":"), float(
645
+ match.group("weight") or 1)) for match in re.finditer(prompt_parser, text)]
646
+ if skip_normalize:
647
+ return parsed_prompts
648
+ weight_sum = sum(map(lambda x: x[1], parsed_prompts))
649
+ if weight_sum == 0:
650
+ print(
651
+ "* Warning: Subprompt weights add up to zero. Discarding and using even weights instead.")
652
+ equal_weight = 1 / max(len(parsed_prompts), 1)
653
+ return [(x[0], equal_weight) for x in parsed_prompts]
654
+ return [(x[0], x[1] / weight_sum) for x in parsed_prompts]
655
+