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,1724 @@
1
+ import eventlet
2
+ import glob
3
+ import os
4
+ import shutil
5
+ import mimetypes
6
+ import traceback
7
+ import math
8
+ import io
9
+ import base64
10
+ import os
11
+ import json
12
+
13
+ from werkzeug.utils import secure_filename
14
+ from flask import Flask, redirect, send_from_directory, request, make_response
15
+ from flask_socketio import SocketIO
16
+ from PIL import Image, ImageOps
17
+ from PIL.Image import Image as ImageType
18
+ from uuid import uuid4
19
+ from threading import Event
20
+
21
+ from ldm.generate import Generate
22
+ from ldm.invoke.args import Args, APP_ID, APP_VERSION, calculate_init_img_hash
23
+ from ldm.invoke.conditioning import get_tokens_for_prompt, get_prompt_structure
24
+ from ldm.invoke.globals import Globals
25
+ from ldm.invoke.pngwriter import PngWriter, retrieve_metadata
26
+ from ldm.invoke.prompt_parser import split_weighted_subprompts, Blend
27
+ from ldm.invoke.generator.inpaint import infill_methods
28
+
29
+ from backend.modules.parameters import parameters_to_command
30
+ from backend.modules.get_canvas_generation_mode import (
31
+ get_canvas_generation_mode,
32
+ )
33
+
34
+ # Loading Arguments
35
+ opt = Args()
36
+ args = opt.parse_args()
37
+
38
+ # Set the root directory for static files and relative paths
39
+ args.root_dir = os.path.expanduser(args.root_dir or "..")
40
+ if not os.path.isabs(args.outdir):
41
+ args.outdir = os.path.join(args.root_dir, args.outdir)
42
+
43
+ # normalize the config directory relative to root
44
+ if not os.path.isabs(opt.conf):
45
+ opt.conf = os.path.normpath(os.path.join(Globals.root,opt.conf))
46
+
47
+ class InvokeAIWebServer:
48
+ def __init__(self, generate: Generate, gfpgan, codeformer, esrgan) -> None:
49
+ self.host = args.host
50
+ self.port = args.port
51
+
52
+ self.generate = generate
53
+ self.gfpgan = gfpgan
54
+ self.codeformer = codeformer
55
+ self.esrgan = esrgan
56
+
57
+ self.canceled = Event()
58
+ self.ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg"}
59
+
60
+ def allowed_file(self, filename: str) -> bool:
61
+ return (
62
+ "." in filename
63
+ and filename.rsplit(".", 1)[1].lower() in self.ALLOWED_EXTENSIONS
64
+ )
65
+
66
+ def run(self):
67
+ self.setup_app()
68
+ self.setup_flask()
69
+
70
+ def setup_flask(self):
71
+ # Fix missing mimetypes on Windows
72
+ mimetypes.add_type("application/javascript", ".js")
73
+ mimetypes.add_type("text/css", ".css")
74
+ # Socket IO
75
+ logger = True if args.web_verbose else False
76
+ engineio_logger = True if args.web_verbose else False
77
+ max_http_buffer_size = 10000000
78
+
79
+ socketio_args = {
80
+ "logger": logger,
81
+ "engineio_logger": engineio_logger,
82
+ "max_http_buffer_size": max_http_buffer_size,
83
+ "ping_interval": (50, 50),
84
+ "ping_timeout": 60,
85
+ }
86
+
87
+ if opt.cors:
88
+ socketio_args["cors_allowed_origins"] = opt.cors
89
+
90
+ frontend_path = self.find_frontend()
91
+ self.app = Flask(
92
+ __name__, static_url_path="", static_folder=frontend_path
93
+ )
94
+
95
+ self.socketio = SocketIO(self.app, **socketio_args)
96
+
97
+ # Keep Server Alive Route
98
+ @self.app.route("/flaskwebgui-keep-server-alive")
99
+ def keep_alive():
100
+ return {"message": "Server Running"}
101
+
102
+ # Outputs Route
103
+ self.app.config["OUTPUTS_FOLDER"] = os.path.abspath(args.outdir)
104
+
105
+ @self.app.route("/outputs/<path:file_path>")
106
+ def outputs(file_path):
107
+ return send_from_directory(self.app.config["OUTPUTS_FOLDER"], file_path)
108
+
109
+ # Base Route
110
+ @self.app.route("/")
111
+ def serve():
112
+ if args.web_develop:
113
+ return redirect("http://127.0.0.1:5173")
114
+ else:
115
+ return send_from_directory(self.app.static_folder, "index.html")
116
+
117
+ @self.app.route("/upload", methods=["POST"])
118
+ def upload():
119
+ try:
120
+ data = json.loads(request.form["data"])
121
+ filename = ""
122
+ # check if the post request has the file part
123
+ if "file" in request.files:
124
+ file = request.files["file"]
125
+ # If the user does not select a file, the browser submits an
126
+ # empty file without a filename.
127
+ if file.filename == "":
128
+ return make_response("No file selected", 400)
129
+ filename = file.filename
130
+ elif "dataURL" in data:
131
+ file = dataURL_to_bytes(data["dataURL"])
132
+ if "filename" not in data or data["filename"] == "":
133
+ return make_response("No filename provided", 400)
134
+ filename = data["filename"]
135
+ else:
136
+ return make_response("No file or dataURL", 400)
137
+
138
+ kind = data["kind"]
139
+
140
+ if kind == "init":
141
+ path = self.init_image_path
142
+ elif kind == "temp":
143
+ path = self.temp_image_path
144
+ elif kind == "result":
145
+ path = self.result_path
146
+ elif kind == "mask":
147
+ path = self.mask_image_path
148
+ else:
149
+ return make_response(f"Invalid upload kind: {kind}", 400)
150
+
151
+ if not self.allowed_file(filename):
152
+ return make_response(
153
+ f'Invalid file type, must be one of: {", ".join(self.ALLOWED_EXTENSIONS)}',
154
+ 400,
155
+ )
156
+
157
+ secured_filename = secure_filename(filename)
158
+
159
+ uuid = uuid4().hex
160
+ truncated_uuid = uuid[:8]
161
+
162
+ split = os.path.splitext(secured_filename)
163
+ name = f"{split[0]}.{truncated_uuid}{split[1]}"
164
+
165
+ file_path = os.path.join(path, name)
166
+
167
+ if "dataURL" in data:
168
+ with open(file_path, "wb") as f:
169
+ f.write(file)
170
+ else:
171
+ file.save(file_path)
172
+
173
+ mtime = os.path.getmtime(file_path)
174
+
175
+ pil_image = Image.open(file_path)
176
+
177
+ if "cropVisible" in data and data["cropVisible"] == True:
178
+ visible_image_bbox = pil_image.getbbox()
179
+ pil_image = pil_image.crop(visible_image_bbox)
180
+ pil_image.save(file_path)
181
+
182
+ (width, height) = pil_image.size
183
+
184
+ thumbnail_path = save_thumbnail(
185
+ pil_image, os.path.basename(file_path), self.thumbnail_image_path
186
+ )
187
+
188
+ response = {
189
+ "url": self.get_url_from_image_path(file_path),
190
+ "thumbnail": self.get_url_from_image_path(thumbnail_path),
191
+ "mtime": mtime,
192
+ "width": width,
193
+ "height": height,
194
+ }
195
+
196
+ return make_response(response, 200)
197
+
198
+ except Exception as e:
199
+ self.socketio.emit("error", {"message": (str(e))})
200
+ print("\n")
201
+
202
+ traceback.print_exc()
203
+ print("\n")
204
+ return make_response("Error uploading file", 500)
205
+
206
+ self.load_socketio_listeners(self.socketio)
207
+
208
+ if args.gui:
209
+ print(">> Launching Invoke AI GUI")
210
+ try:
211
+ from flaskwebgui import FlaskUI
212
+
213
+ FlaskUI(
214
+ app=self.app,
215
+ socketio=self.socketio,
216
+ server="flask_socketio",
217
+ width=1600,
218
+ height=1000,
219
+ port=self.port
220
+ ).run()
221
+ except KeyboardInterrupt:
222
+ import sys
223
+
224
+ sys.exit(0)
225
+ else:
226
+ useSSL = args.certfile or args.keyfile
227
+ print(">> Started Invoke AI Web Server!")
228
+ if self.host == "0.0.0.0":
229
+ print(
230
+ f"Point your browser at http{'s' if useSSL else ''}://localhost:{self.port} or use the host's DNS name or IP address."
231
+ )
232
+ else:
233
+ print(
234
+ ">> Default host address now 127.0.0.1 (localhost). Use --host 0.0.0.0 to bind any address."
235
+ )
236
+ print(
237
+ f">> Point your browser at http{'s' if useSSL else ''}://{self.host}:{self.port}"
238
+ )
239
+ if not useSSL:
240
+ self.socketio.run(app=self.app, host=self.host, port=self.port)
241
+ else:
242
+ self.socketio.run(
243
+ app=self.app,
244
+ host=self.host,
245
+ port=self.port,
246
+ certfile=args.certfile,
247
+ keyfile=args.keyfile,
248
+ )
249
+
250
+ def find_frontend(self):
251
+ my_dir = os.path.dirname(__file__)
252
+ # LS: setup.py seems to put the frontend in different places on different systems, so
253
+ # this is fragile and needs to be replaced with a better way of finding the front end.
254
+ for candidate in (os.path.join(my_dir,'..','frontend','dist'), # pip install -e .
255
+ os.path.join(my_dir,'../../../../frontend','dist'), # pip install . (Linux, Mac)
256
+ os.path.join(my_dir,'../../../frontend','dist'), # pip install . (Windows)
257
+ ):
258
+ if os.path.exists(candidate):
259
+ return candidate
260
+ assert "Frontend files cannot be found. Cannot continue"
261
+
262
+ def setup_app(self):
263
+ self.result_url = "outputs/"
264
+ self.init_image_url = "outputs/init-images/"
265
+ self.mask_image_url = "outputs/mask-images/"
266
+ self.intermediate_url = "outputs/intermediates/"
267
+ self.temp_image_url = "outputs/temp-images/"
268
+ self.thumbnail_image_url = "outputs/thumbnails/"
269
+ # location for "finished" images
270
+ self.result_path = args.outdir
271
+ # temporary path for intermediates
272
+ self.intermediate_path = os.path.join(self.result_path, "intermediates/")
273
+ # path for user-uploaded init images and masks
274
+ self.init_image_path = os.path.join(self.result_path, "init-images/")
275
+ self.mask_image_path = os.path.join(self.result_path, "mask-images/")
276
+ # path for temp images e.g. gallery generations which are not committed
277
+ self.temp_image_path = os.path.join(self.result_path, "temp-images/")
278
+ # path for thumbnail images
279
+ self.thumbnail_image_path = os.path.join(self.result_path, "thumbnails/")
280
+ # txt log
281
+ self.log_path = os.path.join(self.result_path, "invoke_log.txt")
282
+ # make all output paths
283
+ [
284
+ os.makedirs(path, exist_ok=True)
285
+ for path in [
286
+ self.result_path,
287
+ self.intermediate_path,
288
+ self.init_image_path,
289
+ self.mask_image_path,
290
+ self.temp_image_path,
291
+ self.thumbnail_image_path,
292
+ ]
293
+ ]
294
+
295
+ def load_socketio_listeners(self, socketio):
296
+ @socketio.on("requestSystemConfig")
297
+ def handle_request_capabilities():
298
+ print(f">> System config requested")
299
+ config = self.get_system_config()
300
+ config["model_list"] = self.generate.model_cache.list_models()
301
+ config["infill_methods"] = infill_methods()
302
+ socketio.emit("systemConfig", config)
303
+
304
+ @socketio.on('searchForModels')
305
+ def handle_search_models(search_folder: str):
306
+ try:
307
+ if not search_folder:
308
+ socketio.emit(
309
+ "foundModels",
310
+ {'search_folder': None, 'found_models': None},
311
+ )
312
+ else:
313
+ search_folder, found_models = self.generate.model_cache.search_models(search_folder)
314
+ socketio.emit(
315
+ "foundModels",
316
+ {'search_folder': search_folder, 'found_models': found_models},
317
+ )
318
+ except Exception as e:
319
+ self.socketio.emit("error", {"message": (str(e))})
320
+ print("\n")
321
+
322
+ traceback.print_exc()
323
+ print("\n")
324
+
325
+ @socketio.on("addNewModel")
326
+ def handle_add_model(new_model_config: dict):
327
+ try:
328
+ model_name = new_model_config['name']
329
+ del new_model_config['name']
330
+ model_attributes = new_model_config
331
+ update = False
332
+ current_model_list = self.generate.model_cache.list_models()
333
+ if model_name in current_model_list:
334
+ update = True
335
+
336
+ print(f">> Adding New Model: {model_name}")
337
+
338
+ self.generate.model_cache.add_model(
339
+ model_name=model_name, model_attributes=model_attributes, clobber=True)
340
+ self.generate.model_cache.commit(opt.conf)
341
+
342
+ new_model_list = self.generate.model_cache.list_models()
343
+ socketio.emit(
344
+ "newModelAdded",
345
+ {"new_model_name": model_name,
346
+ "model_list": new_model_list, 'update': update},
347
+ )
348
+ print(f">> New Model Added: {model_name}")
349
+ except Exception as e:
350
+ self.socketio.emit("error", {"message": (str(e))})
351
+ print("\n")
352
+
353
+ traceback.print_exc()
354
+ print("\n")
355
+
356
+ @socketio.on("deleteModel")
357
+ def handle_delete_model(model_name: str):
358
+ try:
359
+ print(f">> Deleting Model: {model_name}")
360
+ self.generate.model_cache.del_model(model_name)
361
+ self.generate.model_cache.commit(opt.conf)
362
+ updated_model_list = self.generate.model_cache.list_models()
363
+ socketio.emit(
364
+ "modelDeleted",
365
+ {"deleted_model_name": model_name,
366
+ "model_list": updated_model_list},
367
+ )
368
+ print(f">> Model Deleted: {model_name}")
369
+ except Exception as e:
370
+ self.socketio.emit("error", {"message": (str(e))})
371
+ print("\n")
372
+
373
+ traceback.print_exc()
374
+ print("\n")
375
+
376
+ @socketio.on("requestModelChange")
377
+ def handle_set_model(model_name: str):
378
+ try:
379
+ print(f">> Model change requested: {model_name}")
380
+ model = self.generate.set_model(model_name)
381
+ model_list = self.generate.model_cache.list_models()
382
+ if model is None:
383
+ socketio.emit(
384
+ "modelChangeFailed",
385
+ {"model_name": model_name, "model_list": model_list},
386
+ )
387
+ else:
388
+ socketio.emit(
389
+ "modelChanged",
390
+ {"model_name": model_name, "model_list": model_list},
391
+ )
392
+ except Exception as e:
393
+ self.socketio.emit("error", {"message": (str(e))})
394
+ print("\n")
395
+
396
+ traceback.print_exc()
397
+ print("\n")
398
+
399
+ @socketio.on("requestEmptyTempFolder")
400
+ def empty_temp_folder():
401
+ try:
402
+ temp_files = glob.glob(os.path.join(self.temp_image_path, "*"))
403
+ for f in temp_files:
404
+ try:
405
+ os.remove(f)
406
+ thumbnail_path = os.path.join(
407
+ self.thumbnail_image_path,
408
+ os.path.splitext(os.path.basename(f))[0] + ".webp",
409
+ )
410
+ os.remove(thumbnail_path)
411
+ except Exception as e:
412
+ socketio.emit("error", {"message": f"Unable to delete {f}: {str(e)}"})
413
+ pass
414
+
415
+ socketio.emit("tempFolderEmptied")
416
+ except Exception as e:
417
+ self.socketio.emit("error", {"message": (str(e))})
418
+ print("\n")
419
+
420
+ traceback.print_exc()
421
+ print("\n")
422
+
423
+ @socketio.on("requestSaveStagingAreaImageToGallery")
424
+ def save_temp_image_to_gallery(url):
425
+ try:
426
+ image_path = self.get_image_path_from_url(url)
427
+ new_path = os.path.join(self.result_path, os.path.basename(image_path))
428
+ shutil.copy2(image_path, new_path)
429
+
430
+ if os.path.splitext(new_path)[1] == ".png":
431
+ metadata = retrieve_metadata(new_path)
432
+ else:
433
+ metadata = {}
434
+
435
+ pil_image = Image.open(new_path)
436
+
437
+ (width, height) = pil_image.size
438
+
439
+ thumbnail_path = save_thumbnail(
440
+ pil_image, os.path.basename(new_path), self.thumbnail_image_path
441
+ )
442
+
443
+ image_array = [
444
+ {
445
+ "url": self.get_url_from_image_path(new_path),
446
+ "thumbnail": self.get_url_from_image_path(thumbnail_path),
447
+ "mtime": os.path.getmtime(new_path),
448
+ "metadata": metadata,
449
+ "width": width,
450
+ "height": height,
451
+ "category": "result",
452
+ }
453
+ ]
454
+
455
+ socketio.emit(
456
+ "galleryImages",
457
+ {"images": image_array, "category": "result"},
458
+ )
459
+
460
+ except Exception as e:
461
+ self.socketio.emit("error", {"message": (str(e))})
462
+ print("\n")
463
+
464
+ traceback.print_exc()
465
+ print("\n")
466
+
467
+ @socketio.on("requestLatestImages")
468
+ def handle_request_latest_images(category, latest_mtime):
469
+ try:
470
+ base_path = (
471
+ self.result_path if category == "result" else self.init_image_path
472
+ )
473
+
474
+ paths = []
475
+
476
+ for ext in ("*.png", "*.jpg", "*.jpeg"):
477
+ paths.extend(glob.glob(os.path.join(base_path, ext)))
478
+
479
+ image_paths = sorted(
480
+ paths, key=lambda x: os.path.getmtime(x), reverse=True
481
+ )
482
+
483
+ image_paths = list(
484
+ filter(
485
+ lambda x: os.path.getmtime(x) > latest_mtime,
486
+ image_paths,
487
+ )
488
+ )
489
+
490
+ image_array = []
491
+
492
+ for path in image_paths:
493
+ try:
494
+ if os.path.splitext(path)[1] == ".png":
495
+ metadata = retrieve_metadata(path)
496
+ else:
497
+ metadata = {}
498
+
499
+ pil_image = Image.open(path)
500
+ (width, height) = pil_image.size
501
+
502
+ thumbnail_path = save_thumbnail(
503
+ pil_image, os.path.basename(path), self.thumbnail_image_path
504
+ )
505
+
506
+ image_array.append(
507
+ {
508
+ "url": self.get_url_from_image_path(path),
509
+ "thumbnail": self.get_url_from_image_path(
510
+ thumbnail_path
511
+ ),
512
+ "mtime": os.path.getmtime(path),
513
+ "metadata": metadata.get("sd-metadata"),
514
+ "dreamPrompt": metadata.get("Dream"),
515
+ "width": width,
516
+ "height": height,
517
+ "category": category,
518
+ }
519
+ )
520
+ except Exception as e:
521
+ socketio.emit("error", {"message": f"Unable to load {path}: {str(e)}"})
522
+ pass
523
+
524
+ socketio.emit(
525
+ "galleryImages",
526
+ {"images": image_array, "category": category},
527
+ )
528
+ except Exception as e:
529
+ self.socketio.emit("error", {"message": (str(e))})
530
+ print("\n")
531
+
532
+ traceback.print_exc()
533
+ print("\n")
534
+
535
+ @socketio.on("requestImages")
536
+ def handle_request_images(category, earliest_mtime=None):
537
+ try:
538
+ page_size = 50
539
+
540
+ base_path = (
541
+ self.result_path if category == "result" else self.init_image_path
542
+ )
543
+
544
+ paths = []
545
+ for ext in ("*.png", "*.jpg", "*.jpeg"):
546
+ paths.extend(glob.glob(os.path.join(base_path, ext)))
547
+
548
+ image_paths = sorted(
549
+ paths, key=lambda x: os.path.getmtime(x), reverse=True
550
+ )
551
+
552
+ if earliest_mtime:
553
+ image_paths = list(
554
+ filter(
555
+ lambda x: os.path.getmtime(x) < earliest_mtime,
556
+ image_paths,
557
+ )
558
+ )
559
+
560
+ areMoreImagesAvailable = len(image_paths) >= page_size
561
+ image_paths = image_paths[slice(0, page_size)]
562
+
563
+ image_array = []
564
+ for path in image_paths:
565
+ try:
566
+ if os.path.splitext(path)[1] == ".png":
567
+ metadata = retrieve_metadata(path)
568
+ else:
569
+ metadata = {}
570
+
571
+ pil_image = Image.open(path)
572
+ (width, height) = pil_image.size
573
+
574
+ thumbnail_path = save_thumbnail(
575
+ pil_image, os.path.basename(path), self.thumbnail_image_path
576
+ )
577
+
578
+ image_array.append(
579
+ {
580
+ "url": self.get_url_from_image_path(path),
581
+ "thumbnail": self.get_url_from_image_path(
582
+ thumbnail_path
583
+ ),
584
+ "mtime": os.path.getmtime(path),
585
+ "metadata": metadata.get("sd-metadata"),
586
+ "dreamPrompt": metadata.get("Dream"),
587
+ "width": width,
588
+ "height": height,
589
+ "category": category,
590
+ }
591
+ )
592
+ except Exception as e:
593
+ print(f">> Unable to load {path}")
594
+ socketio.emit("error", {"message": f"Unable to load {path}: {str(e)}"})
595
+ pass
596
+
597
+ socketio.emit(
598
+ "galleryImages",
599
+ {
600
+ "images": image_array,
601
+ "areMoreImagesAvailable": areMoreImagesAvailable,
602
+ "category": category,
603
+ },
604
+ )
605
+ except Exception as e:
606
+ self.socketio.emit("error", {"message": (str(e))})
607
+ print("\n")
608
+
609
+ traceback.print_exc()
610
+ print("\n")
611
+
612
+ @socketio.on("generateImage")
613
+ def handle_generate_image_event(
614
+ generation_parameters, esrgan_parameters, facetool_parameters
615
+ ):
616
+ try:
617
+ # truncate long init_mask/init_img base64 if needed
618
+ printable_parameters = {
619
+ **generation_parameters,
620
+ }
621
+
622
+ if "init_img" in generation_parameters:
623
+ printable_parameters["init_img"] = (
624
+ printable_parameters["init_img"][:64] + "..."
625
+ )
626
+
627
+ if "init_mask" in generation_parameters:
628
+ printable_parameters["init_mask"] = (
629
+ printable_parameters["init_mask"][:64] + "..."
630
+ )
631
+
632
+ print(
633
+ f">> Image generation requested: {printable_parameters}\nESRGAN parameters: {esrgan_parameters}\nFacetool parameters: {facetool_parameters}"
634
+ )
635
+ self.generate_images(
636
+ generation_parameters,
637
+ esrgan_parameters,
638
+ facetool_parameters,
639
+ )
640
+ except Exception as e:
641
+ self.socketio.emit("error", {"message": (str(e))})
642
+ print("\n")
643
+
644
+ traceback.print_exc()
645
+ print("\n")
646
+
647
+ @socketio.on("runPostprocessing")
648
+ def handle_run_postprocessing(original_image, postprocessing_parameters):
649
+ try:
650
+ print(
651
+ f'>> Postprocessing requested for "{original_image["url"]}": {postprocessing_parameters}'
652
+ )
653
+
654
+ progress = Progress()
655
+
656
+ socketio.emit("progressUpdate", progress.to_formatted_dict())
657
+ eventlet.sleep(0)
658
+
659
+ original_image_path = self.get_image_path_from_url(
660
+ original_image["url"]
661
+ )
662
+
663
+ image = Image.open(original_image_path)
664
+
665
+ try:
666
+ seed = original_image["metadata"]["image"]["seed"]
667
+ except (KeyError) as e:
668
+ seed = "unknown_seed"
669
+ pass
670
+
671
+ if postprocessing_parameters["type"] == "esrgan":
672
+ progress.set_current_status("common:statusUpscalingESRGAN")
673
+ elif postprocessing_parameters["type"] == "gfpgan":
674
+ progress.set_current_status("common:statusRestoringFacesGFPGAN")
675
+ elif postprocessing_parameters["type"] == "codeformer":
676
+ progress.set_current_status("common:statusRestoringFacesCodeFormer")
677
+
678
+ socketio.emit("progressUpdate", progress.to_formatted_dict())
679
+ eventlet.sleep(0)
680
+
681
+ if postprocessing_parameters["type"] == "esrgan":
682
+ image = self.esrgan.process(
683
+ image=image,
684
+ upsampler_scale=postprocessing_parameters["upscale"][0],
685
+ strength=postprocessing_parameters["upscale"][1],
686
+ seed=seed,
687
+ )
688
+ elif postprocessing_parameters["type"] == "gfpgan":
689
+ image = self.gfpgan.process(
690
+ image=image,
691
+ strength=postprocessing_parameters["facetool_strength"],
692
+ seed=seed,
693
+ )
694
+ elif postprocessing_parameters["type"] == "codeformer":
695
+ image = self.codeformer.process(
696
+ image=image,
697
+ strength=postprocessing_parameters["facetool_strength"],
698
+ fidelity=postprocessing_parameters["codeformer_fidelity"],
699
+ seed=seed,
700
+ device="cpu"
701
+ if str(self.generate.device) == "mps"
702
+ else self.generate.device,
703
+ )
704
+ else:
705
+ raise TypeError(
706
+ f'{postprocessing_parameters["type"]} is not a valid postprocessing type'
707
+ )
708
+
709
+ progress.set_current_status("common:statusSavingImage")
710
+ socketio.emit("progressUpdate", progress.to_formatted_dict())
711
+ eventlet.sleep(0)
712
+
713
+ postprocessing_parameters["seed"] = seed
714
+ metadata = self.parameters_to_post_processed_image_metadata(
715
+ parameters=postprocessing_parameters,
716
+ original_image_path=original_image_path,
717
+ )
718
+
719
+ command = parameters_to_command(postprocessing_parameters)
720
+
721
+ (width, height) = image.size
722
+
723
+ path = self.save_result_image(
724
+ image,
725
+ command,
726
+ metadata,
727
+ self.result_path,
728
+ postprocessing=postprocessing_parameters["type"],
729
+ )
730
+
731
+ thumbnail_path = save_thumbnail(
732
+ image, os.path.basename(path), self.thumbnail_image_path
733
+ )
734
+
735
+ self.write_log_message(
736
+ f'[Postprocessed] "{original_image_path}" > "{path}": {postprocessing_parameters}'
737
+ )
738
+
739
+ progress.mark_complete()
740
+ socketio.emit("progressUpdate", progress.to_formatted_dict())
741
+ eventlet.sleep(0)
742
+
743
+ socketio.emit(
744
+ "postprocessingResult",
745
+ {
746
+ "url": self.get_url_from_image_path(path),
747
+ "thumbnail": self.get_url_from_image_path(thumbnail_path),
748
+ "mtime": os.path.getmtime(path),
749
+ "metadata": metadata,
750
+ "dreamPrompt": command,
751
+ "width": width,
752
+ "height": height,
753
+ },
754
+ )
755
+ except Exception as e:
756
+ self.socketio.emit("error", {"message": (str(e))})
757
+ print("\n")
758
+
759
+ traceback.print_exc()
760
+ print("\n")
761
+
762
+ @socketio.on("cancel")
763
+ def handle_cancel():
764
+ print(f">> Cancel processing requested")
765
+ self.canceled.set()
766
+
767
+ # TODO: I think this needs a safety mechanism.
768
+ @socketio.on("deleteImage")
769
+ def handle_delete_image(url, thumbnail, uuid, category):
770
+ try:
771
+ print(f'>> Delete requested "{url}"')
772
+ from send2trash import send2trash
773
+
774
+ path = self.get_image_path_from_url(url)
775
+ thumbnail_path = self.get_image_path_from_url(thumbnail)
776
+
777
+ send2trash(path)
778
+ send2trash(thumbnail_path)
779
+
780
+ socketio.emit(
781
+ "imageDeleted",
782
+ {"url": url, "uuid": uuid, "category": category},
783
+ )
784
+ except Exception as e:
785
+ self.socketio.emit("error", {"message": (str(e))})
786
+ print("\n")
787
+
788
+ traceback.print_exc()
789
+ print("\n")
790
+
791
+ # App Functions
792
+ def get_system_config(self):
793
+ model_list: dict = self.generate.model_cache.list_models()
794
+ active_model_name = None
795
+
796
+ for model_name, model_dict in model_list.items():
797
+ if model_dict["status"] == "active":
798
+ active_model_name = model_name
799
+
800
+ return {
801
+ "model": "stable diffusion",
802
+ "model_weights": active_model_name,
803
+ "model_hash": self.generate.model_hash,
804
+ "app_id": APP_ID,
805
+ "app_version": APP_VERSION,
806
+ }
807
+
808
+ def generate_images(
809
+ self, generation_parameters, esrgan_parameters, facetool_parameters
810
+ ):
811
+ try:
812
+ self.canceled.clear()
813
+
814
+ step_index = 1
815
+ prior_variations = (
816
+ generation_parameters["with_variations"]
817
+ if "with_variations" in generation_parameters
818
+ else []
819
+ )
820
+
821
+ actual_generation_mode = generation_parameters["generation_mode"]
822
+ original_bounding_box = None
823
+
824
+ progress = Progress(generation_parameters=generation_parameters)
825
+
826
+ self.socketio.emit("progressUpdate", progress.to_formatted_dict())
827
+ eventlet.sleep(0)
828
+
829
+ """
830
+ TODO:
831
+ If a result image is used as an init image, and then deleted, we will want to be
832
+ able to use it as an init image in the future. Need to handle this case.
833
+ """
834
+
835
+ """
836
+ Prepare for generation based on generation_mode
837
+ """
838
+ if generation_parameters["generation_mode"] == "unifiedCanvas":
839
+ """
840
+ generation_parameters["init_img"] is a base64 image
841
+ generation_parameters["init_mask"] is a base64 image
842
+
843
+ So we need to convert each into a PIL Image.
844
+ """
845
+
846
+ truncated_outpaint_image_b64 = generation_parameters["init_img"][:64]
847
+ truncated_outpaint_mask_b64 = generation_parameters["init_mask"][:64]
848
+
849
+ init_img_url = generation_parameters["init_img"]
850
+
851
+ original_bounding_box = generation_parameters["bounding_box"].copy()
852
+
853
+ initial_image = dataURL_to_image(
854
+ generation_parameters["init_img"]
855
+ ).convert("RGBA")
856
+
857
+ """
858
+ The outpaint image and mask are pre-cropped by the UI, so the bounding box we pass
859
+ to the generator should be:
860
+ {
861
+ "x": 0,
862
+ "y": 0,
863
+ "width": original_bounding_box["width"],
864
+ "height": original_bounding_box["height"]
865
+ }
866
+ """
867
+
868
+ generation_parameters["bounding_box"]["x"] = 0
869
+ generation_parameters["bounding_box"]["y"] = 0
870
+
871
+ # Convert mask dataURL to an image and convert to greyscale
872
+ mask_image = dataURL_to_image(
873
+ generation_parameters["init_mask"]
874
+ ).convert("L")
875
+
876
+ actual_generation_mode = get_canvas_generation_mode(
877
+ initial_image, mask_image
878
+ )
879
+
880
+ """
881
+ Apply the mask to the init image, creating a "mask" image with
882
+ transparency where inpainting should occur. This is the kind of
883
+ mask that prompt2image() needs.
884
+ """
885
+ alpha_mask = initial_image.copy()
886
+ alpha_mask.putalpha(mask_image)
887
+
888
+ generation_parameters["init_img"] = initial_image
889
+ generation_parameters["init_mask"] = alpha_mask
890
+
891
+ # Remove the unneeded parameters for whichever mode we are doing
892
+ if actual_generation_mode == "inpainting":
893
+ generation_parameters.pop("seam_size", None)
894
+ generation_parameters.pop("seam_blur", None)
895
+ generation_parameters.pop("seam_strength", None)
896
+ generation_parameters.pop("seam_steps", None)
897
+ generation_parameters.pop("tile_size", None)
898
+ generation_parameters.pop("force_outpaint", None)
899
+ elif actual_generation_mode == "img2img":
900
+ generation_parameters["height"] = original_bounding_box["height"]
901
+ generation_parameters["width"] = original_bounding_box["width"]
902
+ generation_parameters.pop("init_mask", None)
903
+ generation_parameters.pop("seam_size", None)
904
+ generation_parameters.pop("seam_blur", None)
905
+ generation_parameters.pop("seam_strength", None)
906
+ generation_parameters.pop("seam_steps", None)
907
+ generation_parameters.pop("tile_size", None)
908
+ generation_parameters.pop("force_outpaint", None)
909
+ generation_parameters.pop("infill_method", None)
910
+ elif actual_generation_mode == "txt2img":
911
+ generation_parameters["height"] = original_bounding_box["height"]
912
+ generation_parameters["width"] = original_bounding_box["width"]
913
+ generation_parameters.pop("strength", None)
914
+ generation_parameters.pop("fit", None)
915
+ generation_parameters.pop("init_img", None)
916
+ generation_parameters.pop("init_mask", None)
917
+ generation_parameters.pop("seam_size", None)
918
+ generation_parameters.pop("seam_blur", None)
919
+ generation_parameters.pop("seam_strength", None)
920
+ generation_parameters.pop("seam_steps", None)
921
+ generation_parameters.pop("tile_size", None)
922
+ generation_parameters.pop("force_outpaint", None)
923
+ generation_parameters.pop("infill_method", None)
924
+
925
+ elif generation_parameters["generation_mode"] == "img2img":
926
+ init_img_url = generation_parameters["init_img"]
927
+ init_img_path = self.get_image_path_from_url(init_img_url)
928
+ generation_parameters["init_img"] = Image.open(init_img_path).convert('RGB')
929
+
930
+ def image_progress(sample, step):
931
+ if self.canceled.is_set():
932
+ raise CanceledException
933
+
934
+ nonlocal step_index
935
+ nonlocal generation_parameters
936
+ nonlocal progress
937
+
938
+ generation_messages = {
939
+ "txt2img": "common:statusGeneratingTextToImage",
940
+ "img2img": "common:statusGeneratingImageToImage",
941
+ "inpainting": "common:statusGeneratingInpainting",
942
+ "outpainting": "common:statusGeneratingOutpainting",
943
+ }
944
+
945
+ progress.set_current_step(step + 1)
946
+ progress.set_current_status(
947
+ f"{generation_messages[actual_generation_mode]}"
948
+ )
949
+ progress.set_current_status_has_steps(True)
950
+
951
+ if (
952
+ generation_parameters["progress_images"]
953
+ and step % generation_parameters["save_intermediates"] == 0
954
+ and step < generation_parameters["steps"] - 1
955
+ ):
956
+ image = self.generate.sample_to_image(sample)
957
+ metadata = self.parameters_to_generated_image_metadata(
958
+ generation_parameters
959
+ )
960
+ command = parameters_to_command(generation_parameters)
961
+
962
+ (width, height) = image.size
963
+
964
+ path = self.save_result_image(
965
+ image,
966
+ command,
967
+ metadata,
968
+ self.intermediate_path,
969
+ step_index=step_index,
970
+ postprocessing=False,
971
+ )
972
+
973
+ step_index += 1
974
+ self.socketio.emit(
975
+ "intermediateResult",
976
+ {
977
+ "url": self.get_url_from_image_path(path),
978
+ "mtime": os.path.getmtime(path),
979
+ "metadata": metadata,
980
+ "width": width,
981
+ "height": height,
982
+ "generationMode": generation_parameters["generation_mode"],
983
+ "boundingBox": original_bounding_box,
984
+ },
985
+ )
986
+
987
+
988
+ if generation_parameters["progress_latents"]:
989
+ image = self.generate.sample_to_lowres_estimated_image(sample)
990
+ (width, height) = image.size
991
+ width *= 8
992
+ height *= 8
993
+ img_base64 = image_to_dataURL(image)
994
+ self.socketio.emit(
995
+ "intermediateResult",
996
+ {
997
+ "url": img_base64,
998
+ "isBase64": True,
999
+ "mtime": 0,
1000
+ "metadata": {},
1001
+ "width": width,
1002
+ "height": height,
1003
+ "generationMode": generation_parameters["generation_mode"],
1004
+ "boundingBox": original_bounding_box,
1005
+ },
1006
+ )
1007
+
1008
+ self.socketio.emit("progressUpdate", progress.to_formatted_dict())
1009
+ eventlet.sleep(0)
1010
+
1011
+ def image_done(image, seed, first_seed, attention_maps_image=None):
1012
+ if self.canceled.is_set():
1013
+ raise CanceledException
1014
+
1015
+ nonlocal generation_parameters
1016
+ nonlocal esrgan_parameters
1017
+ nonlocal facetool_parameters
1018
+ nonlocal progress
1019
+
1020
+ step_index = 1
1021
+ nonlocal prior_variations
1022
+
1023
+ """
1024
+ Tidy up after generation based on generation_mode
1025
+ """
1026
+ # paste the inpainting image back onto the original
1027
+ if generation_parameters["generation_mode"] == "inpainting":
1028
+ image = paste_image_into_bounding_box(
1029
+ Image.open(init_img_path),
1030
+ image,
1031
+ **generation_parameters["bounding_box"],
1032
+ )
1033
+
1034
+ progress.set_current_status("common:statusGenerationComplete")
1035
+
1036
+ self.socketio.emit("progressUpdate", progress.to_formatted_dict())
1037
+ eventlet.sleep(0)
1038
+
1039
+ all_parameters = generation_parameters
1040
+ postprocessing = False
1041
+
1042
+ if (
1043
+ "variation_amount" in all_parameters
1044
+ and all_parameters["variation_amount"] > 0
1045
+ ):
1046
+ first_seed = first_seed or seed
1047
+ this_variation = [[seed, all_parameters["variation_amount"]]]
1048
+ all_parameters["with_variations"] = (
1049
+ prior_variations + this_variation
1050
+ )
1051
+ all_parameters["seed"] = first_seed
1052
+ elif "with_variations" in all_parameters:
1053
+ all_parameters["seed"] = first_seed
1054
+ else:
1055
+ all_parameters["seed"] = seed
1056
+
1057
+ if self.canceled.is_set():
1058
+ raise CanceledException
1059
+
1060
+ if esrgan_parameters:
1061
+ progress.set_current_status("common:statusUpscaling")
1062
+ progress.set_current_status_has_steps(False)
1063
+ self.socketio.emit("progressUpdate", progress.to_formatted_dict())
1064
+ eventlet.sleep(0)
1065
+
1066
+ image = self.esrgan.process(
1067
+ image=image,
1068
+ upsampler_scale=esrgan_parameters["level"],
1069
+ strength=esrgan_parameters["strength"],
1070
+ seed=seed,
1071
+ )
1072
+
1073
+ postprocessing = True
1074
+ all_parameters["upscale"] = [
1075
+ esrgan_parameters["level"],
1076
+ esrgan_parameters["strength"],
1077
+ ]
1078
+
1079
+ if self.canceled.is_set():
1080
+ raise CanceledException
1081
+
1082
+ if facetool_parameters:
1083
+ if facetool_parameters["type"] == "gfpgan":
1084
+ progress.set_current_status("common:statusRestoringFacesGFPGAN")
1085
+ elif facetool_parameters["type"] == "codeformer":
1086
+ progress.set_current_status("common:statusRestoringFacesCodeFormer")
1087
+
1088
+ progress.set_current_status_has_steps(False)
1089
+ self.socketio.emit("progressUpdate", progress.to_formatted_dict())
1090
+ eventlet.sleep(0)
1091
+
1092
+ if facetool_parameters["type"] == "gfpgan":
1093
+ image = self.gfpgan.process(
1094
+ image=image,
1095
+ strength=facetool_parameters["strength"],
1096
+ seed=seed,
1097
+ )
1098
+ elif facetool_parameters["type"] == "codeformer":
1099
+ image = self.codeformer.process(
1100
+ image=image,
1101
+ strength=facetool_parameters["strength"],
1102
+ fidelity=facetool_parameters["codeformer_fidelity"],
1103
+ seed=seed,
1104
+ device="cpu"
1105
+ if str(self.generate.device) == "mps"
1106
+ else self.generate.device,
1107
+ )
1108
+ all_parameters["codeformer_fidelity"] = facetool_parameters[
1109
+ "codeformer_fidelity"
1110
+ ]
1111
+
1112
+ postprocessing = True
1113
+ all_parameters["facetool_strength"] = facetool_parameters[
1114
+ "strength"
1115
+ ]
1116
+ all_parameters["facetool_type"] = facetool_parameters["type"]
1117
+
1118
+ progress.set_current_status("common:statusSavingImage")
1119
+ self.socketio.emit("progressUpdate", progress.to_formatted_dict())
1120
+ eventlet.sleep(0)
1121
+
1122
+ # restore the stashed URLS and discard the paths, we are about to send the result to client
1123
+ all_parameters["init_img"] = (
1124
+ init_img_url
1125
+ if generation_parameters["generation_mode"] == "img2img"
1126
+ else ""
1127
+ )
1128
+
1129
+ if "init_mask" in all_parameters:
1130
+ all_parameters["init_mask"] = "" # TODO: store the mask in metadata
1131
+
1132
+ if generation_parameters["generation_mode"] == "unifiedCanvas":
1133
+ all_parameters["bounding_box"] = original_bounding_box
1134
+
1135
+ metadata = self.parameters_to_generated_image_metadata(all_parameters)
1136
+
1137
+ command = parameters_to_command(all_parameters)
1138
+
1139
+ (width, height) = image.size
1140
+
1141
+ generated_image_outdir = (
1142
+ self.result_path
1143
+ if generation_parameters["generation_mode"]
1144
+ in ["txt2img", "img2img"]
1145
+ else self.temp_image_path
1146
+ )
1147
+
1148
+ path = self.save_result_image(
1149
+ image,
1150
+ command,
1151
+ metadata,
1152
+ generated_image_outdir,
1153
+ postprocessing=postprocessing,
1154
+ )
1155
+
1156
+ thumbnail_path = save_thumbnail(
1157
+ image, os.path.basename(path), self.thumbnail_image_path
1158
+ )
1159
+
1160
+ print(f'>> Image generated: "{path}"')
1161
+ self.write_log_message(f'[Generated] "{path}": {command}')
1162
+
1163
+ if progress.total_iterations > progress.current_iteration:
1164
+ progress.set_current_step(1)
1165
+ progress.set_current_status("common:statusIterationComplete")
1166
+ progress.set_current_status_has_steps(False)
1167
+ else:
1168
+ progress.mark_complete()
1169
+
1170
+ self.socketio.emit("progressUpdate", progress.to_formatted_dict())
1171
+ eventlet.sleep(0)
1172
+
1173
+ parsed_prompt, _ = get_prompt_structure(generation_parameters["prompt"])
1174
+ tokens = None if type(parsed_prompt) is Blend else \
1175
+ get_tokens_for_prompt(self.generate.model, parsed_prompt)
1176
+ attention_maps_image_base64_url = None if attention_maps_image is None \
1177
+ else image_to_dataURL(attention_maps_image)
1178
+
1179
+ self.socketio.emit(
1180
+ "generationResult",
1181
+ {
1182
+ "url": self.get_url_from_image_path(path),
1183
+ "thumbnail": self.get_url_from_image_path(thumbnail_path),
1184
+ "mtime": os.path.getmtime(path),
1185
+ "metadata": metadata,
1186
+ "dreamPrompt": command,
1187
+ "width": width,
1188
+ "height": height,
1189
+ "boundingBox": original_bounding_box,
1190
+ "generationMode": generation_parameters["generation_mode"],
1191
+ "attentionMaps": attention_maps_image_base64_url,
1192
+ "tokens": tokens,
1193
+ },
1194
+ )
1195
+ eventlet.sleep(0)
1196
+
1197
+ progress.set_current_iteration(progress.current_iteration + 1)
1198
+
1199
+ print(generation_parameters)
1200
+
1201
+ self.generate.prompt2image(
1202
+ **generation_parameters,
1203
+ step_callback=image_progress,
1204
+ image_callback=image_done
1205
+ )
1206
+
1207
+ except KeyboardInterrupt:
1208
+ self.socketio.emit("processingCanceled")
1209
+ raise
1210
+ except CanceledException:
1211
+ self.socketio.emit("processingCanceled")
1212
+ pass
1213
+ except Exception as e:
1214
+ print(e)
1215
+ self.socketio.emit("error", {"message": (str(e))})
1216
+ print("\n")
1217
+
1218
+ traceback.print_exc()
1219
+ print("\n")
1220
+
1221
+ def parameters_to_generated_image_metadata(self, parameters):
1222
+ try:
1223
+ # top-level metadata minus `image` or `images`
1224
+ metadata = self.get_system_config()
1225
+ # remove any image keys not mentioned in RFC #266
1226
+ rfc266_img_fields = [
1227
+ "type",
1228
+ "postprocessing",
1229
+ "sampler",
1230
+ "prompt",
1231
+ "seed",
1232
+ "variations",
1233
+ "steps",
1234
+ "cfg_scale",
1235
+ "threshold",
1236
+ "perlin",
1237
+ "step_number",
1238
+ "width",
1239
+ "height",
1240
+ "extra",
1241
+ "seamless",
1242
+ "hires_fix",
1243
+ ]
1244
+
1245
+ rfc_dict = {}
1246
+
1247
+ for item in parameters.items():
1248
+ key, value = item
1249
+ if key in rfc266_img_fields:
1250
+ rfc_dict[key] = value
1251
+
1252
+ postprocessing = []
1253
+
1254
+ rfc_dict["type"] = parameters["generation_mode"]
1255
+
1256
+ # 'postprocessing' is either null or an
1257
+ if "facetool_strength" in parameters:
1258
+ facetool_parameters = {
1259
+ "type": str(parameters["facetool_type"]),
1260
+ "strength": float(parameters["facetool_strength"]),
1261
+ }
1262
+
1263
+ if parameters["facetool_type"] == "codeformer":
1264
+ facetool_parameters["fidelity"] = float(
1265
+ parameters["codeformer_fidelity"]
1266
+ )
1267
+
1268
+ postprocessing.append(facetool_parameters)
1269
+
1270
+ if "upscale" in parameters:
1271
+ postprocessing.append(
1272
+ {
1273
+ "type": "esrgan",
1274
+ "scale": int(parameters["upscale"][0]),
1275
+ "strength": float(parameters["upscale"][1]),
1276
+ }
1277
+ )
1278
+
1279
+ rfc_dict["postprocessing"] = (
1280
+ postprocessing if len(postprocessing) > 0 else None
1281
+ )
1282
+
1283
+ # semantic drift
1284
+ rfc_dict["sampler"] = parameters["sampler_name"]
1285
+
1286
+ # display weighted subprompts (liable to change)
1287
+ subprompts = split_weighted_subprompts(
1288
+ parameters["prompt"], skip_normalize=True
1289
+ )
1290
+ subprompts = [{"prompt": x[0], "weight": x[1]} for x in subprompts]
1291
+ rfc_dict["prompt"] = subprompts
1292
+
1293
+ # 'variations' should always exist and be an array, empty or consisting of {'seed': seed, 'weight': weight} pairs
1294
+ variations = []
1295
+
1296
+ if "with_variations" in parameters:
1297
+ variations = [
1298
+ {"seed": x[0], "weight": x[1]}
1299
+ for x in parameters["with_variations"]
1300
+ ]
1301
+
1302
+ rfc_dict["variations"] = variations
1303
+
1304
+ print(parameters)
1305
+
1306
+ if rfc_dict["type"] == "img2img":
1307
+ rfc_dict["strength"] = parameters["strength"]
1308
+ rfc_dict["fit"] = parameters["fit"] # TODO: Noncompliant
1309
+ rfc_dict["orig_hash"] = calculate_init_img_hash(
1310
+ self.get_image_path_from_url(parameters["init_img"])
1311
+ )
1312
+ rfc_dict["init_image_path"] = parameters[
1313
+ "init_img"
1314
+ ] # TODO: Noncompliant
1315
+
1316
+ metadata["image"] = rfc_dict
1317
+
1318
+ return metadata
1319
+
1320
+ except Exception as e:
1321
+ self.socketio.emit("error", {"message": (str(e))})
1322
+ print("\n")
1323
+
1324
+ traceback.print_exc()
1325
+ print("\n")
1326
+
1327
+ def parameters_to_post_processed_image_metadata(
1328
+ self, parameters, original_image_path
1329
+ ):
1330
+ try:
1331
+ current_metadata = retrieve_metadata(original_image_path)["sd-metadata"]
1332
+ postprocessing_metadata = {}
1333
+
1334
+ """
1335
+ if we don't have an original image metadata to reconstruct,
1336
+ need to record the original image and its hash
1337
+ """
1338
+ if "image" not in current_metadata:
1339
+ current_metadata["image"] = {}
1340
+
1341
+ orig_hash = calculate_init_img_hash(
1342
+ self.get_image_path_from_url(original_image_path)
1343
+ )
1344
+
1345
+ postprocessing_metadata["orig_path"] = (original_image_path,)
1346
+ postprocessing_metadata["orig_hash"] = orig_hash
1347
+
1348
+ if parameters["type"] == "esrgan":
1349
+ postprocessing_metadata["type"] = "esrgan"
1350
+ postprocessing_metadata["scale"] = parameters["upscale"][0]
1351
+ postprocessing_metadata["strength"] = parameters["upscale"][1]
1352
+ elif parameters["type"] == "gfpgan":
1353
+ postprocessing_metadata["type"] = "gfpgan"
1354
+ postprocessing_metadata["strength"] = parameters["facetool_strength"]
1355
+ elif parameters["type"] == "codeformer":
1356
+ postprocessing_metadata["type"] = "codeformer"
1357
+ postprocessing_metadata["strength"] = parameters["facetool_strength"]
1358
+ postprocessing_metadata["fidelity"] = parameters["codeformer_fidelity"]
1359
+
1360
+ else:
1361
+ raise TypeError(f"Invalid type: {parameters['type']}")
1362
+
1363
+ if "postprocessing" in current_metadata["image"] and isinstance(
1364
+ current_metadata["image"]["postprocessing"], list
1365
+ ):
1366
+ current_metadata["image"]["postprocessing"].append(
1367
+ postprocessing_metadata
1368
+ )
1369
+ else:
1370
+ current_metadata["image"]["postprocessing"] = [postprocessing_metadata]
1371
+
1372
+ return current_metadata
1373
+
1374
+ except Exception as e:
1375
+ self.socketio.emit("error", {"message": (str(e))})
1376
+ print("\n")
1377
+
1378
+ traceback.print_exc()
1379
+ print("\n")
1380
+
1381
+ def save_result_image(
1382
+ self,
1383
+ image,
1384
+ command,
1385
+ metadata,
1386
+ output_dir,
1387
+ step_index=None,
1388
+ postprocessing=False,
1389
+ ):
1390
+ try:
1391
+ pngwriter = PngWriter(output_dir)
1392
+
1393
+ number_prefix = pngwriter.unique_prefix()
1394
+
1395
+ uuid = uuid4().hex
1396
+ truncated_uuid = uuid[:8]
1397
+
1398
+ seed = "unknown_seed"
1399
+
1400
+ if "image" in metadata:
1401
+ if "seed" in metadata["image"]:
1402
+ seed = metadata["image"]["seed"]
1403
+
1404
+ filename = f"{number_prefix}.{truncated_uuid}.{seed}"
1405
+
1406
+ if step_index:
1407
+ filename += f".{step_index}"
1408
+ if postprocessing:
1409
+ filename += f".postprocessed"
1410
+
1411
+ filename += ".png"
1412
+
1413
+ path = pngwriter.save_image_and_prompt_to_png(
1414
+ image=image,
1415
+ dream_prompt=command,
1416
+ metadata=metadata,
1417
+ name=filename,
1418
+ )
1419
+
1420
+ return os.path.abspath(path)
1421
+
1422
+ except Exception as e:
1423
+ self.socketio.emit("error", {"message": (str(e))})
1424
+ print("\n")
1425
+
1426
+ traceback.print_exc()
1427
+ print("\n")
1428
+
1429
+ def make_unique_init_image_filename(self, name):
1430
+ try:
1431
+ uuid = uuid4().hex
1432
+ split = os.path.splitext(name)
1433
+ name = f"{split[0]}.{uuid}{split[1]}"
1434
+ return name
1435
+ except Exception as e:
1436
+ self.socketio.emit("error", {"message": (str(e))})
1437
+ print("\n")
1438
+
1439
+ traceback.print_exc()
1440
+ print("\n")
1441
+
1442
+ def calculate_real_steps(self, steps, strength, has_init_image):
1443
+ import math
1444
+
1445
+ return math.floor(strength * steps) if has_init_image else steps
1446
+
1447
+ def write_log_message(self, message):
1448
+ """Logs the filename and parameters used to generate or process that image to log file"""
1449
+ try:
1450
+ message = f"{message}\n"
1451
+ with open(self.log_path, "a", encoding="utf-8") as file:
1452
+ file.writelines(message)
1453
+
1454
+ except Exception as e:
1455
+ self.socketio.emit("error", {"message": (str(e))})
1456
+ print("\n")
1457
+
1458
+ traceback.print_exc()
1459
+ print("\n")
1460
+
1461
+ def get_image_path_from_url(self, url):
1462
+ """Given a url to an image used by the client, returns the absolute file path to that image"""
1463
+ try:
1464
+ if "init-images" in url:
1465
+ return os.path.abspath(
1466
+ os.path.join(self.init_image_path, os.path.basename(url))
1467
+ )
1468
+ elif "mask-images" in url:
1469
+ return os.path.abspath(
1470
+ os.path.join(self.mask_image_path, os.path.basename(url))
1471
+ )
1472
+ elif "intermediates" in url:
1473
+ return os.path.abspath(
1474
+ os.path.join(self.intermediate_path, os.path.basename(url))
1475
+ )
1476
+ elif "temp-images" in url:
1477
+ return os.path.abspath(
1478
+ os.path.join(self.temp_image_path, os.path.basename(url))
1479
+ )
1480
+ elif "thumbnails" in url:
1481
+ return os.path.abspath(
1482
+ os.path.join(self.thumbnail_image_path, os.path.basename(url))
1483
+ )
1484
+ else:
1485
+ return os.path.abspath(
1486
+ os.path.join(self.result_path, os.path.basename(url))
1487
+ )
1488
+ except Exception as e:
1489
+ self.socketio.emit("error", {"message": (str(e))})
1490
+ print("\n")
1491
+
1492
+ traceback.print_exc()
1493
+ print("\n")
1494
+
1495
+ def get_url_from_image_path(self, path):
1496
+ """Given an absolute file path to an image, returns the URL that the client can use to load the image"""
1497
+ try:
1498
+ if "init-images" in path:
1499
+ return os.path.join(self.init_image_url, os.path.basename(path))
1500
+ elif "mask-images" in path:
1501
+ return os.path.join(self.mask_image_url, os.path.basename(path))
1502
+ elif "intermediates" in path:
1503
+ return os.path.join(self.intermediate_url, os.path.basename(path))
1504
+ elif "temp-images" in path:
1505
+ return os.path.join(self.temp_image_url, os.path.basename(path))
1506
+ elif "thumbnails" in path:
1507
+ return os.path.join(self.thumbnail_image_url, os.path.basename(path))
1508
+ else:
1509
+ return os.path.join(self.result_url, os.path.basename(path))
1510
+ except Exception as e:
1511
+ self.socketio.emit("error", {"message": (str(e))})
1512
+ print("\n")
1513
+
1514
+ traceback.print_exc()
1515
+ print("\n")
1516
+
1517
+ def save_file_unique_uuid_name(self, bytes, name, path):
1518
+ try:
1519
+ uuid = uuid4().hex
1520
+ truncated_uuid = uuid[:8]
1521
+
1522
+ split = os.path.splitext(name)
1523
+ name = f"{split[0]}.{truncated_uuid}{split[1]}"
1524
+
1525
+ file_path = os.path.join(path, name)
1526
+
1527
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
1528
+
1529
+ newFile = open(file_path, "wb")
1530
+ newFile.write(bytes)
1531
+
1532
+ return file_path
1533
+ except Exception as e:
1534
+ self.socketio.emit("error", {"message": (str(e))})
1535
+ print("\n")
1536
+
1537
+ traceback.print_exc()
1538
+ print("\n")
1539
+
1540
+
1541
+ class Progress:
1542
+ def __init__(self, generation_parameters=None):
1543
+ self.current_step = 1
1544
+ self.total_steps = (
1545
+ self._calculate_real_steps(
1546
+ steps=generation_parameters["steps"],
1547
+ strength=generation_parameters["strength"]
1548
+ if "strength" in generation_parameters
1549
+ else None,
1550
+ has_init_image="init_img" in generation_parameters,
1551
+ )
1552
+ if generation_parameters
1553
+ else 1
1554
+ )
1555
+ self.current_iteration = 1
1556
+ self.total_iterations = (
1557
+ generation_parameters["iterations"] if generation_parameters else 1
1558
+ )
1559
+ self.current_status = "common:statusPreparing"
1560
+ self.is_processing = True
1561
+ self.current_status_has_steps = False
1562
+ self.has_error = False
1563
+
1564
+ def set_current_step(self, current_step):
1565
+ self.current_step = current_step
1566
+
1567
+ def set_total_steps(self, total_steps):
1568
+ self.total_steps = total_steps
1569
+
1570
+ def set_current_iteration(self, current_iteration):
1571
+ self.current_iteration = current_iteration
1572
+
1573
+ def set_total_iterations(self, total_iterations):
1574
+ self.total_iterations = total_iterations
1575
+
1576
+ def set_current_status(self, current_status):
1577
+ self.current_status = current_status
1578
+
1579
+ def set_is_processing(self, is_processing):
1580
+ self.is_processing = is_processing
1581
+
1582
+ def set_current_status_has_steps(self, current_status_has_steps):
1583
+ self.current_status_has_steps = current_status_has_steps
1584
+
1585
+ def set_has_error(self, has_error):
1586
+ self.has_error = has_error
1587
+
1588
+ def mark_complete(self):
1589
+ self.current_status = "common:statusProcessingComplete"
1590
+ self.current_step = 0
1591
+ self.total_steps = 0
1592
+ self.current_iteration = 0
1593
+ self.total_iterations = 0
1594
+ self.is_processing = False
1595
+
1596
+ def to_formatted_dict(
1597
+ self,
1598
+ ):
1599
+ return {
1600
+ "currentStep": self.current_step,
1601
+ "totalSteps": self.total_steps,
1602
+ "currentIteration": self.current_iteration,
1603
+ "totalIterations": self.total_iterations,
1604
+ "currentStatus": self.current_status,
1605
+ "isProcessing": self.is_processing,
1606
+ "currentStatusHasSteps": self.current_status_has_steps,
1607
+ "hasError": self.has_error,
1608
+ }
1609
+
1610
+ def _calculate_real_steps(self, steps, strength, has_init_image):
1611
+ return math.floor(strength * steps) if has_init_image else steps
1612
+
1613
+
1614
+ class CanceledException(Exception):
1615
+ pass
1616
+
1617
+
1618
+ """
1619
+ Returns a copy an image, cropped to a bounding box.
1620
+ """
1621
+
1622
+
1623
+ def copy_image_from_bounding_box(
1624
+ image: ImageType, x: int, y: int, width: int, height: int
1625
+ ) -> ImageType:
1626
+ with image as im:
1627
+ bounds = (x, y, x + width, y + height)
1628
+ im_cropped = im.crop(bounds)
1629
+ return im_cropped
1630
+
1631
+
1632
+ """
1633
+ Converts a base64 image dataURL into an image.
1634
+ The dataURL is split on the first commma.
1635
+ """
1636
+
1637
+
1638
+ def dataURL_to_image(dataURL: str) -> ImageType:
1639
+ image = Image.open(
1640
+ io.BytesIO(
1641
+ base64.decodebytes(
1642
+ bytes(
1643
+ dataURL.split(",", 1)[1],
1644
+ "utf-8",
1645
+ )
1646
+ )
1647
+ )
1648
+ )
1649
+ return image
1650
+
1651
+ """
1652
+ Converts an image into a base64 image dataURL.
1653
+ """
1654
+
1655
+ def image_to_dataURL(image: ImageType) -> str:
1656
+ buffered = io.BytesIO()
1657
+ image.save(buffered, format="PNG")
1658
+ image_base64 = "data:image/png;base64," + base64.b64encode(
1659
+ buffered.getvalue()
1660
+ ).decode("UTF-8")
1661
+ return image_base64
1662
+
1663
+
1664
+
1665
+ """
1666
+ Converts a base64 image dataURL into bytes.
1667
+ The dataURL is split on the first commma.
1668
+ """
1669
+
1670
+
1671
+ def dataURL_to_bytes(dataURL: str) -> bytes:
1672
+ return base64.decodebytes(
1673
+ bytes(
1674
+ dataURL.split(",", 1)[1],
1675
+ "utf-8",
1676
+ )
1677
+ )
1678
+
1679
+
1680
+ """
1681
+ Pastes an image onto another with a bounding box.
1682
+ """
1683
+
1684
+
1685
+ def paste_image_into_bounding_box(
1686
+ recipient_image: ImageType,
1687
+ donor_image: ImageType,
1688
+ x: int,
1689
+ y: int,
1690
+ width: int,
1691
+ height: int,
1692
+ ) -> ImageType:
1693
+ with recipient_image as im:
1694
+ bounds = (x, y, x + width, y + height)
1695
+ im.paste(donor_image, bounds)
1696
+ return recipient_image
1697
+
1698
+
1699
+ """
1700
+ Saves a thumbnail of an image, returning its path.
1701
+ """
1702
+
1703
+
1704
+ def save_thumbnail(
1705
+ image: ImageType,
1706
+ filename: str,
1707
+ path: str,
1708
+ size: int = 256,
1709
+ ) -> str:
1710
+ base_filename = os.path.splitext(filename)[0]
1711
+ thumbnail_path = os.path.join(path, base_filename + ".webp")
1712
+
1713
+ if os.path.exists(thumbnail_path):
1714
+ return thumbnail_path
1715
+
1716
+ thumbnail_width = size
1717
+ thumbnail_height = round(size * (image.height / image.width))
1718
+
1719
+ image_copy = image.copy()
1720
+ image_copy.thumbnail(size=(thumbnail_width, thumbnail_height))
1721
+
1722
+ image_copy.save(thumbnail_path, "WEBP")
1723
+
1724
+ return thumbnail_path