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
ldm/invoke/server.py ADDED
@@ -0,0 +1,282 @@
1
+ import argparse
2
+ import json
3
+ import copy
4
+ import base64
5
+ import mimetypes
6
+ import os
7
+ from ldm.invoke.args import Args, metadata_dumps
8
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
9
+ from ldm.invoke.pngwriter import PngWriter
10
+ from threading import Event
11
+
12
+ def build_opt(post_data, seed, gfpgan_model_exists):
13
+ opt = Args()
14
+ opt.parse_args() # initialize defaults
15
+ setattr(opt, 'prompt', post_data['prompt'])
16
+ setattr(opt, 'init_img', post_data['initimg'])
17
+ setattr(opt, 'strength', float(post_data['strength']))
18
+ setattr(opt, 'iterations', int(post_data['iterations']))
19
+ setattr(opt, 'steps', int(post_data['steps']))
20
+ setattr(opt, 'width', int(post_data['width']))
21
+ setattr(opt, 'height', int(post_data['height']))
22
+ setattr(opt, 'seamless', 'seamless' in post_data)
23
+ setattr(opt, 'fit', 'fit' in post_data)
24
+ setattr(opt, 'mask', 'mask' in post_data)
25
+ setattr(opt, 'invert_mask', 'invert_mask' in post_data)
26
+ setattr(opt, 'cfg_scale', float(post_data['cfg_scale']))
27
+ setattr(opt, 'sampler_name', post_data['sampler_name'])
28
+
29
+ # embiggen not practical at this point because we have no way of feeding images back into img2img
30
+ # however, this code is here against that eventuality
31
+ setattr(opt, 'embiggen', None)
32
+ setattr(opt, 'embiggen_tiles', None)
33
+ setattr(opt, 'embiggen_strength', None)
34
+
35
+ setattr(opt, 'facetool_strength', float(post_data['facetool_strength']) if gfpgan_model_exists else 0)
36
+ setattr(opt, 'upscale', [int(post_data['upscale_level']), float(post_data['upscale_strength'])] if post_data['upscale_level'] != '' else None)
37
+ setattr(opt, 'progress_images', 'progress_images' in post_data)
38
+ setattr(opt, 'progress_latents', 'progress_latents' in post_data)
39
+ setattr(opt, 'seed', None if int(post_data['seed']) == -1 else int(post_data['seed']))
40
+ setattr(opt, 'threshold', float(post_data['threshold']))
41
+ setattr(opt, 'perlin', float(post_data['perlin']))
42
+ setattr(opt, 'hires_fix', 'hires_fix' in post_data)
43
+ setattr(opt, 'variation_amount', float(post_data['variation_amount']) if int(post_data['seed']) != -1 else 0)
44
+ setattr(opt, 'with_variations', [])
45
+ setattr(opt, 'embiggen', None)
46
+ setattr(opt, 'embiggen_tiles', None)
47
+
48
+ broken = False
49
+ if int(post_data['seed']) != -1 and post_data['with_variations'] != '':
50
+ for part in post_data['with_variations'].split(','):
51
+ seed_and_weight = part.split(':')
52
+ if len(seed_and_weight) != 2:
53
+ print(f'could not parse WITH_variation part "{part}"')
54
+ broken = True
55
+ break
56
+ try:
57
+ seed = int(seed_and_weight[0])
58
+ weight = float(seed_and_weight[1])
59
+ except ValueError:
60
+ print(f'could not parse with_variation part "{part}"')
61
+ broken = True
62
+ break
63
+ opt.with_variations.append([seed, weight])
64
+
65
+ if broken:
66
+ raise CanceledException
67
+
68
+ if len(opt.with_variations) == 0:
69
+ opt.with_variations = None
70
+
71
+ return opt
72
+
73
+ class CanceledException(Exception):
74
+ pass
75
+
76
+ class DreamServer(BaseHTTPRequestHandler):
77
+ model = None
78
+ outdir = None
79
+ canceled = Event()
80
+
81
+ def do_GET(self):
82
+ if self.path == "/":
83
+ self.send_response(200)
84
+ self.send_header("Content-type", "text/html")
85
+ self.end_headers()
86
+ with open("./static/legacy_web/index.html", "rb") as content:
87
+ self.wfile.write(content.read())
88
+ elif self.path == "/config.js":
89
+ # unfortunately this import can't be at the top level, since that would cause a circular import
90
+ self.send_response(200)
91
+ self.send_header("Content-type", "application/javascript")
92
+ self.end_headers()
93
+ config = {
94
+ 'gfpgan_model_exists': self.gfpgan_model_exists
95
+ }
96
+ self.wfile.write(bytes("let config = " + json.dumps(config) + ";\n", "utf-8"))
97
+ elif self.path == "/run_log.json":
98
+ self.send_response(200)
99
+ self.send_header("Content-type", "application/json")
100
+ self.end_headers()
101
+ output = []
102
+
103
+ log_file = os.path.join(self.outdir, "legacy_web_log.txt")
104
+ if os.path.exists(log_file):
105
+ with open(log_file, "r") as log:
106
+ for line in log:
107
+ url, config = line.split(": {", maxsplit=1)
108
+ config = json.loads("{" + config)
109
+ config["url"] = url.lstrip(".")
110
+ if os.path.exists(url):
111
+ output.append(config)
112
+
113
+ self.wfile.write(bytes(json.dumps({"run_log": output}), "utf-8"))
114
+ elif self.path == "/cancel":
115
+ self.canceled.set()
116
+ self.send_response(200)
117
+ self.send_header("Content-type", "application/json")
118
+ self.end_headers()
119
+ self.wfile.write(bytes('{}', 'utf8'))
120
+ else:
121
+ path_dir = os.path.dirname(self.path)
122
+ out_dir = os.path.realpath(self.outdir.rstrip('/'))
123
+ if self.path.startswith('/static/legacy_web/'):
124
+ path = '.' + self.path
125
+ elif out_dir.replace('\\', '/').endswith(path_dir):
126
+ file = os.path.basename(self.path)
127
+ path = os.path.join(self.outdir,file)
128
+ else:
129
+ self.send_response(404)
130
+ return
131
+ mime_type = mimetypes.guess_type(path)[0]
132
+ if mime_type is not None:
133
+ self.send_response(200)
134
+ self.send_header("Content-type", mime_type)
135
+ self.end_headers()
136
+ with open(path, "rb") as content:
137
+ self.wfile.write(content.read())
138
+ else:
139
+ self.send_response(404)
140
+
141
+ def do_POST(self):
142
+ self.send_response(200)
143
+ self.send_header("Content-type", "application/json")
144
+ self.end_headers()
145
+
146
+ # unfortunately this import can't be at the top level, since that would cause a circular import
147
+
148
+ content_length = int(self.headers['Content-Length'])
149
+ post_data = json.loads(self.rfile.read(content_length))
150
+ opt = build_opt(post_data, self.model.seed, self.gfpgan_model_exists)
151
+
152
+ self.canceled.clear()
153
+ # In order to handle upscaled images, the PngWriter needs to maintain state
154
+ # across images generated by each call to prompt2img(), so we define it in
155
+ # the outer scope of image_done()
156
+ config = post_data.copy() # Shallow copy
157
+ config['initimg'] = config.pop('initimg_name', '')
158
+
159
+ images_generated = 0 # helps keep track of when upscaling is started
160
+ images_upscaled = 0 # helps keep track of when upscaling is completed
161
+ pngwriter = PngWriter(self.outdir)
162
+
163
+ prefix = pngwriter.unique_prefix()
164
+ # if upscaling is requested, then this will be called twice, once when
165
+ # the images are first generated, and then again when after upscaling
166
+ # is complete. The upscaling replaces the original file, so the second
167
+ # entry should not be inserted into the image list.
168
+ # LS: This repeats code in dream.py
169
+ def image_done(image, seed, upscaled=False, first_seed=None):
170
+ name = f'{prefix}.{seed}.png'
171
+ iter_opt = copy.copy(opt)
172
+ if opt.variation_amount > 0:
173
+ this_variation = [[seed, opt.variation_amount]]
174
+ if opt.with_variations is None:
175
+ iter_opt.with_variations = this_variation
176
+ else:
177
+ iter_opt.with_variations = opt.with_variations + this_variation
178
+ iter_opt.variation_amount = 0
179
+ formatted_prompt = opt.dream_prompt_str(seed=seed)
180
+ path = pngwriter.save_image_and_prompt_to_png(
181
+ image,
182
+ dream_prompt = formatted_prompt,
183
+ metadata = metadata_dumps(iter_opt,
184
+ seeds = [seed],
185
+ model_hash = self.model.model_hash
186
+ ),
187
+ name = name,
188
+ )
189
+
190
+ if int(config['seed']) == -1:
191
+ config['seed'] = seed
192
+ # Append post_data to log, but only once!
193
+ if not upscaled:
194
+ with open(os.path.join(self.outdir, "legacy_web_log.txt"), "a") as log:
195
+ log.write(f"{path}: {json.dumps(config)}\n")
196
+
197
+ self.wfile.write(bytes(json.dumps(
198
+ {'event': 'result', 'url': path, 'seed': seed, 'config': config}
199
+ ) + '\n',"utf-8"))
200
+
201
+ # control state of the "postprocessing..." message
202
+ upscaling_requested = opt.upscale or opt.facetool_strength > 0
203
+ nonlocal images_generated # NB: Is this bad python style? It is typical usage in a perl closure.
204
+ nonlocal images_upscaled # NB: Is this bad python style? It is typical usage in a perl closure.
205
+ if upscaled:
206
+ images_upscaled += 1
207
+ else:
208
+ images_generated += 1
209
+ if upscaling_requested:
210
+ action = None
211
+ if images_generated >= opt.iterations:
212
+ if images_upscaled < opt.iterations:
213
+ action = 'upscaling-started'
214
+ else:
215
+ action = 'upscaling-done'
216
+ if action:
217
+ x = images_upscaled + 1
218
+ self.wfile.write(bytes(json.dumps(
219
+ {'event': action, 'processed_file_cnt': f'{x}/{opt.iterations}'}
220
+ ) + '\n',"utf-8"))
221
+
222
+ step_writer = PngWriter(os.path.join(self.outdir, "intermediates"))
223
+ step_index = 1
224
+ def image_progress(sample, step):
225
+ if self.canceled.is_set():
226
+ self.wfile.write(bytes(json.dumps({'event':'canceled'}) + '\n', 'utf-8'))
227
+ raise CanceledException
228
+ path = None
229
+ # since rendering images is moderately expensive, only render every 5th image
230
+ # and don't bother with the last one, since it'll render anyway
231
+ nonlocal step_index
232
+
233
+ wants_progress_latents = opt.progress_latents
234
+ wants_progress_image = opt.progress_image and step % 5 == 0
235
+
236
+ if (wants_progress_image | wants_progress_latents) and step < opt.steps - 1:
237
+ image = self.model.sample_to_image(sample) if wants_progress_image \
238
+ else self.model.sample_to_lowres_estimated_image(sample)
239
+ step_index_padded = str(step_index).rjust(len(str(opt.steps)), '0')
240
+ name = f'{prefix}.{opt.seed}.{step_index_padded}.png'
241
+ metadata = f'{opt.prompt} -S{opt.seed} [intermediate]'
242
+ path = step_writer.save_image_and_prompt_to_png(image, dream_prompt=metadata, name=name)
243
+ step_index += 1
244
+ self.wfile.write(bytes(json.dumps(
245
+ {'event': 'step', 'step': step + 1, 'url': path}
246
+ ) + '\n',"utf-8"))
247
+
248
+ try:
249
+ if opt.init_img is None:
250
+ # Run txt2img
251
+ self.model.prompt2image(**vars(opt), step_callback=image_progress, image_callback=image_done)
252
+ else:
253
+ # Decode initimg as base64 to temp file
254
+ with open("./img2img-tmp.png", "wb") as f:
255
+ initimg = opt.init_img.split(",")[1] # Ignore mime type
256
+ f.write(base64.b64decode(initimg))
257
+ opt1 = argparse.Namespace(**vars(opt))
258
+ opt1.init_img = "./img2img-tmp.png"
259
+
260
+ try:
261
+ # Run img2img
262
+ self.model.prompt2image(**vars(opt1), step_callback=image_progress, image_callback=image_done)
263
+ finally:
264
+ # Remove the temp file
265
+ os.remove("./img2img-tmp.png")
266
+ except CanceledException:
267
+ print(f"Canceled.")
268
+ return
269
+ except Exception as e:
270
+ print("Error happened")
271
+ print(e)
272
+ self.wfile.write(bytes(json.dumps(
273
+ {'event': 'error',
274
+ 'message': str(e),
275
+ 'type': e.__class__.__name__}
276
+ ) + '\n',"utf-8"))
277
+ raise e
278
+
279
+
280
+ class ThreadingDreamServer(ThreadingHTTPServer):
281
+ def __init__(self, server_address):
282
+ super(ThreadingDreamServer, self).__init__(server_address, DreamServer)
@@ -0,0 +1,246 @@
1
+ import argparse
2
+ import json
3
+ import base64
4
+ import mimetypes
5
+ import os
6
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
7
+ from ldm.invoke.pngwriter import PngWriter, PromptFormatter
8
+ from threading import Event
9
+
10
+ def build_opt(post_data, seed, gfpgan_model_exists):
11
+ opt = argparse.Namespace()
12
+ setattr(opt, 'prompt', post_data['prompt'])
13
+ setattr(opt, 'init_img', post_data['initimg'])
14
+ setattr(opt, 'strength', float(post_data['strength']))
15
+ setattr(opt, 'iterations', int(post_data['iterations']))
16
+ setattr(opt, 'steps', int(post_data['steps']))
17
+ setattr(opt, 'width', int(post_data['width']))
18
+ setattr(opt, 'height', int(post_data['height']))
19
+ setattr(opt, 'seamless', 'seamless' in post_data)
20
+ setattr(opt, 'fit', 'fit' in post_data)
21
+ setattr(opt, 'mask', 'mask' in post_data)
22
+ setattr(opt, 'invert_mask', 'invert_mask' in post_data)
23
+ setattr(opt, 'cfg_scale', float(post_data['cfg_scale']))
24
+ setattr(opt, 'sampler_name', post_data['sampler_name'])
25
+ setattr(opt, 'gfpgan_strength', float(post_data['gfpgan_strength']) if gfpgan_model_exists else 0)
26
+ setattr(opt, 'upscale', [int(post_data['upscale_level']), float(post_data['upscale_strength'])] if post_data['upscale_level'] != '' else None)
27
+ setattr(opt, 'progress_images', 'progress_images' in post_data)
28
+ setattr(opt, 'seed', None if int(post_data['seed']) == -1 else int(post_data['seed']))
29
+ setattr(opt, 'variation_amount', float(post_data['variation_amount']) if int(post_data['seed']) != -1 else 0)
30
+ setattr(opt, 'with_variations', [])
31
+
32
+ broken = False
33
+ if int(post_data['seed']) != -1 and post_data['with_variations'] != '':
34
+ for part in post_data['with_variations'].split(','):
35
+ seed_and_weight = part.split(':')
36
+ if len(seed_and_weight) != 2:
37
+ print(f'could not parse with_variation part "{part}"')
38
+ broken = True
39
+ break
40
+ try:
41
+ seed = int(seed_and_weight[0])
42
+ weight = float(seed_and_weight[1])
43
+ except ValueError:
44
+ print(f'could not parse with_variation part "{part}"')
45
+ broken = True
46
+ break
47
+ opt.with_variations.append([seed, weight])
48
+
49
+ if broken:
50
+ raise CanceledException
51
+
52
+ if len(opt.with_variations) == 0:
53
+ opt.with_variations = None
54
+
55
+ return opt
56
+
57
+ class CanceledException(Exception):
58
+ pass
59
+
60
+ class DreamServer(BaseHTTPRequestHandler):
61
+ model = None
62
+ outdir = None
63
+ canceled = Event()
64
+
65
+ def do_GET(self):
66
+ if self.path == "/":
67
+ self.send_response(200)
68
+ self.send_header("Content-type", "text/html")
69
+ self.end_headers()
70
+ with open("./static/dream_web/index.html", "rb") as content:
71
+ self.wfile.write(content.read())
72
+ elif self.path == "/config.js":
73
+ # unfortunately this import can't be at the top level, since that would cause a circular import
74
+ from ldm.gfpgan.gfpgan_tools import gfpgan_model_exists
75
+ self.send_response(200)
76
+ self.send_header("Content-type", "application/javascript")
77
+ self.end_headers()
78
+ config = {
79
+ 'gfpgan_model_exists': gfpgan_model_exists
80
+ }
81
+ self.wfile.write(bytes("let config = " + json.dumps(config) + ";\n", "utf-8"))
82
+ elif self.path == "/run_log.json":
83
+ self.send_response(200)
84
+ self.send_header("Content-type", "application/json")
85
+ self.end_headers()
86
+ output = []
87
+
88
+ log_file = os.path.join(self.outdir, "dream_web_log.txt")
89
+ if os.path.exists(log_file):
90
+ with open(log_file, "r") as log:
91
+ for line in log:
92
+ url, config = line.split(": {", maxsplit=1)
93
+ config = json.loads("{" + config)
94
+ config["url"] = url.lstrip(".")
95
+ if os.path.exists(url):
96
+ output.append(config)
97
+
98
+ self.wfile.write(bytes(json.dumps({"run_log": output}), "utf-8"))
99
+ elif self.path == "/cancel":
100
+ self.canceled.set()
101
+ self.send_response(200)
102
+ self.send_header("Content-type", "application/json")
103
+ self.end_headers()
104
+ self.wfile.write(bytes('{}', 'utf8'))
105
+ else:
106
+ path = "." + self.path
107
+ cwd = os.path.realpath(os.getcwd())
108
+ is_in_cwd = os.path.commonprefix((os.path.realpath(path), cwd)) == cwd
109
+ if not (is_in_cwd and os.path.exists(path)):
110
+ self.send_response(404)
111
+ return
112
+ mime_type = mimetypes.guess_type(path)[0]
113
+ if mime_type is not None:
114
+ self.send_response(200)
115
+ self.send_header("Content-type", mime_type)
116
+ self.end_headers()
117
+ with open("." + self.path, "rb") as content:
118
+ self.wfile.write(content.read())
119
+ else:
120
+ self.send_response(404)
121
+
122
+ def do_POST(self):
123
+ self.send_response(200)
124
+ self.send_header("Content-type", "application/json")
125
+ self.end_headers()
126
+
127
+ # unfortunately this import can't be at the top level, since that would cause a circular import
128
+ # TODO temporarily commented out, import fails for some reason
129
+ # from ldm.gfpgan.gfpgan_tools import gfpgan_model_exists
130
+ gfpgan_model_exists = False
131
+
132
+ content_length = int(self.headers['Content-Length'])
133
+ post_data = json.loads(self.rfile.read(content_length))
134
+ opt = build_opt(post_data, self.model.seed, gfpgan_model_exists)
135
+
136
+ self.canceled.clear()
137
+ print(f">> Request to generate with prompt: {opt.prompt}")
138
+ # In order to handle upscaled images, the PngWriter needs to maintain state
139
+ # across images generated by each call to prompt2img(), so we define it in
140
+ # the outer scope of image_done()
141
+ config = post_data.copy() # Shallow copy
142
+ config['initimg'] = config.pop('initimg_name', '')
143
+
144
+ images_generated = 0 # helps keep track of when upscaling is started
145
+ images_upscaled = 0 # helps keep track of when upscaling is completed
146
+ pngwriter = PngWriter(self.outdir)
147
+
148
+ prefix = pngwriter.unique_prefix()
149
+ # if upscaling is requested, then this will be called twice, once when
150
+ # the images are first generated, and then again when after upscaling
151
+ # is complete. The upscaling replaces the original file, so the second
152
+ # entry should not be inserted into the image list.
153
+ def image_done(image, seed, upscaled=False, first_seed=-1, use_prefix=None):
154
+ print(f'First seed: {first_seed}')
155
+ name = f'{prefix}.{seed}.png'
156
+ iter_opt = argparse.Namespace(**vars(opt)) # copy
157
+ if opt.variation_amount > 0:
158
+ this_variation = [[seed, opt.variation_amount]]
159
+ if opt.with_variations is None:
160
+ iter_opt.with_variations = this_variation
161
+ else:
162
+ iter_opt.with_variations = opt.with_variations + this_variation
163
+ iter_opt.variation_amount = 0
164
+ elif opt.with_variations is None:
165
+ iter_opt.seed = seed
166
+ normalized_prompt = PromptFormatter(self.model, iter_opt).normalize_prompt()
167
+ path = pngwriter.save_image_and_prompt_to_png(image, f'{normalized_prompt} -S{iter_opt.seed}', name)
168
+
169
+ if int(config['seed']) == -1:
170
+ config['seed'] = seed
171
+ # Append post_data to log, but only once!
172
+ if not upscaled:
173
+ with open(os.path.join(self.outdir, "dream_web_log.txt"), "a") as log:
174
+ log.write(f"{path}: {json.dumps(config)}\n")
175
+
176
+ self.wfile.write(bytes(json.dumps(
177
+ {'event': 'result', 'url': path, 'seed': seed, 'config': config}
178
+ ) + '\n',"utf-8"))
179
+
180
+ # control state of the "postprocessing..." message
181
+ upscaling_requested = opt.upscale or opt.gfpgan_strength > 0
182
+ nonlocal images_generated # NB: Is this bad python style? It is typical usage in a perl closure.
183
+ nonlocal images_upscaled # NB: Is this bad python style? It is typical usage in a perl closure.
184
+ if upscaled:
185
+ images_upscaled += 1
186
+ else:
187
+ images_generated += 1
188
+ if upscaling_requested:
189
+ action = None
190
+ if images_generated >= opt.iterations:
191
+ if images_upscaled < opt.iterations:
192
+ action = 'upscaling-started'
193
+ else:
194
+ action = 'upscaling-done'
195
+ if action:
196
+ x = images_upscaled + 1
197
+ self.wfile.write(bytes(json.dumps(
198
+ {'event': action, 'processed_file_cnt': f'{x}/{opt.iterations}'}
199
+ ) + '\n',"utf-8"))
200
+
201
+ step_writer = PngWriter(os.path.join(self.outdir, "intermediates"))
202
+ step_index = 1
203
+ def image_progress(sample, step):
204
+ if self.canceled.is_set():
205
+ self.wfile.write(bytes(json.dumps({'event':'canceled'}) + '\n', 'utf-8'))
206
+ raise CanceledException
207
+ path = None
208
+ # since rendering images is moderately expensive, only render every 5th image
209
+ # and don't bother with the last one, since it'll render anyway
210
+ nonlocal step_index
211
+ if opt.progress_images and step % 5 == 0 and step < opt.steps - 1:
212
+ image = self.model.sample_to_image(sample)
213
+ name = f'{prefix}.{opt.seed}.{step_index}.png'
214
+ metadata = f'{opt.prompt} -S{opt.seed} [intermediate]'
215
+ path = step_writer.save_image_and_prompt_to_png(image, metadata, name)
216
+ step_index += 1
217
+ self.wfile.write(bytes(json.dumps(
218
+ {'event': 'step', 'step': step + 1, 'url': path}
219
+ ) + '\n',"utf-8"))
220
+
221
+ try:
222
+ if opt.init_img is None:
223
+ # Run txt2img
224
+ self.model.prompt2image(**vars(opt), step_callback=image_progress, image_callback=image_done)
225
+ else:
226
+ # Decode initimg as base64 to temp file
227
+ with open("./img2img-tmp.png", "wb") as f:
228
+ initimg = opt.init_img.split(",")[1] # Ignore mime type
229
+ f.write(base64.b64decode(initimg))
230
+ opt1 = argparse.Namespace(**vars(opt))
231
+ opt1.init_img = "./img2img-tmp.png"
232
+
233
+ try:
234
+ # Run img2img
235
+ self.model.prompt2image(**vars(opt1), step_callback=image_progress, image_callback=image_done)
236
+ finally:
237
+ # Remove the temp file
238
+ os.remove("./img2img-tmp.png")
239
+ except CanceledException:
240
+ print(f"Canceled.")
241
+ return
242
+
243
+
244
+ class ThreadingDreamServer(ThreadingHTTPServer):
245
+ def __init__(self, server_address):
246
+ super(ThreadingDreamServer, self).__init__(server_address, DreamServer)
ldm/invoke/txt2mask.py ADDED
@@ -0,0 +1,136 @@
1
+ '''Makes available the Txt2Mask class, which assists in the automatic
2
+ assignment of masks via text prompt using clipseg.
3
+
4
+ Here is typical usage:
5
+
6
+ from ldm.invoke.txt2mask import Txt2Mask, SegmentedGrayscale
7
+ from PIL import Image
8
+
9
+ txt2mask = Txt2Mask(self.device)
10
+ segmented = txt2mask.segment(Image.open('/path/to/img.png'),'a bagel')
11
+
12
+ # this will return a grayscale Image of the segmented data
13
+ grayscale = segmented.to_grayscale()
14
+
15
+ # this will return a semi-transparent image in which the
16
+ # selected object(s) are opaque and the rest is at various
17
+ # levels of transparency
18
+ transparent = segmented.to_transparent()
19
+
20
+ # this will return a masked image suitable for use in inpainting:
21
+ mask = segmented.to_mask(threshold=0.5)
22
+
23
+ The threshold used in the call to to_mask() selects pixels for use in
24
+ the mask that exceed the indicated confidence threshold. Values range
25
+ from 0.0 to 1.0. The higher the threshold, the more confident the
26
+ algorithm is. In limited testing, I have found that values around 0.5
27
+ work fine.
28
+ '''
29
+
30
+ import torch
31
+ import numpy as np
32
+ import os
33
+ from vendored.clipseg.models.clipseg import CLIPDensePredT
34
+ from einops import rearrange, repeat
35
+ from PIL import Image, ImageOps
36
+ from torchvision import transforms
37
+ from ldm.invoke.globals import Globals
38
+
39
+ CLIP_VERSION = 'ViT-B/16'
40
+ CLIPSEG_WEIGHTS = 'models/clipseg/clipseg_weights/rd64-uni.pth'
41
+ CLIPSEG_WEIGHTS_REFINED = 'models/clipseg/clipseg_weights/rd64-uni-refined.pth'
42
+ CLIPSEG_SIZE = 352
43
+
44
+ class SegmentedGrayscale(object):
45
+ def __init__(self, image:Image, heatmap:torch.Tensor):
46
+ self.heatmap = heatmap
47
+ self.image = image
48
+
49
+ def to_grayscale(self,invert:bool=False)->Image:
50
+ return self._rescale(Image.fromarray(np.uint8(255 - self.heatmap * 255 if invert else self.heatmap * 255)))
51
+
52
+ def to_mask(self,threshold:float=0.5)->Image:
53
+ discrete_heatmap = self.heatmap.lt(threshold).int()
54
+ return self._rescale(Image.fromarray(np.uint8(discrete_heatmap*255),mode='L'))
55
+
56
+ def to_transparent(self,invert:bool=False)->Image:
57
+ transparent_image = self.image.copy()
58
+ # For img2img, we want the selected regions to be transparent,
59
+ # but to_grayscale() returns the opposite. Thus invert.
60
+ gs = self.to_grayscale(not invert)
61
+ transparent_image.putalpha(gs)
62
+ return transparent_image
63
+
64
+ # unscales and uncrops the 352x352 heatmap so that it matches the image again
65
+ def _rescale(self, heatmap:Image)->Image:
66
+ size = self.image.width if (self.image.width > self.image.height) else self.image.height
67
+ resized_image = heatmap.resize(
68
+ (size,size),
69
+ resample=Image.Resampling.LANCZOS
70
+ )
71
+ return resized_image.crop((0,0,self.image.width,self.image.height))
72
+
73
+ class Txt2Mask(object):
74
+ '''
75
+ Create new Txt2Mask object. The optional device argument can be one of
76
+ 'cuda', 'mps' or 'cpu'.
77
+ '''
78
+ def __init__(self,device='cpu',refined=False):
79
+ print('>> Initializing clipseg model for text to mask inference')
80
+ self.device = device
81
+ self.model = CLIPDensePredT(version=CLIP_VERSION, reduce_dim=64, complex_trans_conv=refined)
82
+ self.model.eval()
83
+ # initially we keep everything in cpu to conserve space
84
+ self.model.to('cpu')
85
+ self.model.load_state_dict(torch.load(os.path.join(Globals.root,CLIPSEG_WEIGHTS_REFINED)
86
+ if refined
87
+ else os.path.join(Globals.root,CLIPSEG_WEIGHTS),
88
+ map_location=torch.device('cpu')), strict=False
89
+ )
90
+
91
+ @torch.no_grad()
92
+ def segment(self, image, prompt:str) -> SegmentedGrayscale:
93
+ '''
94
+ Given a prompt string such as "a bagel", tries to identify the object in the
95
+ provided image and returns a SegmentedGrayscale object in which the brighter
96
+ pixels indicate where the object is inferred to be.
97
+ '''
98
+ self._to_device(self.device)
99
+ prompts = [prompt] # right now we operate on just a single prompt at a time
100
+
101
+ transform = transforms.Compose([
102
+ transforms.ToTensor(),
103
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
104
+ transforms.Resize((CLIPSEG_SIZE, CLIPSEG_SIZE)), # must be multiple of 64...
105
+ ])
106
+
107
+ if type(image) is str:
108
+ image = Image.open(image).convert('RGB')
109
+
110
+ image = ImageOps.exif_transpose(image)
111
+ img = self._scale_and_crop(image)
112
+ img = transform(img).unsqueeze(0)
113
+
114
+ preds = self.model(img.repeat(len(prompts),1,1,1), prompts)[0]
115
+ heatmap = torch.sigmoid(preds[0][0]).cpu()
116
+ self._to_device('cpu')
117
+ return SegmentedGrayscale(image, heatmap)
118
+
119
+ def _to_device(self, device):
120
+ self.model.to(device)
121
+
122
+ def _scale_and_crop(self, image:Image)->Image:
123
+ scaled_image = Image.new('RGB',(CLIPSEG_SIZE,CLIPSEG_SIZE))
124
+ if image.width > image.height: # width is constraint
125
+ scale = CLIPSEG_SIZE / image.width
126
+ else:
127
+ scale = CLIPSEG_SIZE / image.height
128
+ scaled_image.paste(
129
+ image.resize(
130
+ (int(scale * image.width),
131
+ int(scale * image.height)
132
+ ),
133
+ resample=Image.Resampling.LANCZOS
134
+ ),box=(0,0)
135
+ )
136
+ return scaled_image