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,449 @@
1
+ '''
2
+ Manage a cache of Stable Diffusion model files for fast switching.
3
+ They are moved between GPU and CPU as necessary. If CPU memory falls
4
+ below a preset minimum, the least recently used model will be
5
+ cleared and loaded from disk when next needed.
6
+ '''
7
+
8
+ import torch
9
+ import os
10
+ import io
11
+ import time
12
+ import gc
13
+ import hashlib
14
+ import psutil
15
+ import sys
16
+ import transformers
17
+ import traceback
18
+ import textwrap
19
+ import contextlib
20
+ from typing import Union
21
+ from omegaconf import OmegaConf
22
+ from omegaconf.errors import ConfigAttributeError
23
+ from ldm.util import instantiate_from_config, ask_user
24
+ from ldm.invoke.globals import Globals
25
+ from picklescan.scanner import scan_file_path
26
+ from pathlib import Path
27
+
28
+ DEFAULT_MAX_MODELS=2
29
+
30
+ class ModelCache(object):
31
+ def __init__(self, config:OmegaConf, device_type:str, precision:str, max_loaded_models=DEFAULT_MAX_MODELS):
32
+ '''
33
+ Initialize with the path to the models.yaml config file,
34
+ the torch device type, and precision. The optional
35
+ min_avail_mem argument specifies how much unused system
36
+ (CPU) memory to preserve. The cache of models in RAM will
37
+ grow until this value is approached. Default is 2G.
38
+ '''
39
+ # prevent nasty-looking CLIP log message
40
+ transformers.logging.set_verbosity_error()
41
+ self.config = config
42
+ self.precision = precision
43
+ self.device = torch.device(device_type)
44
+ self.max_loaded_models = max_loaded_models
45
+ self.models = {}
46
+ self.stack = [] # this is an LRU FIFO
47
+ self.current_model = None
48
+
49
+ def valid_model(self, model_name:str)->bool:
50
+ '''
51
+ Given a model name, returns True if it is a valid
52
+ identifier.
53
+ '''
54
+ return model_name in self.config
55
+
56
+ def get_model(self, model_name:str):
57
+ '''
58
+ Given a model named identified in models.yaml, return
59
+ the model object. If in RAM will load into GPU VRAM.
60
+ If on disk, will load from there.
61
+ '''
62
+ if not self.valid_model(model_name):
63
+ print(f'** "{model_name}" is not a known model name. Please check your models.yaml file')
64
+ return self.current_model
65
+
66
+ if self.current_model != model_name:
67
+ if model_name not in self.models: # make room for a new one
68
+ self._make_cache_room()
69
+ self.offload_model(self.current_model)
70
+
71
+ if model_name in self.models:
72
+ requested_model = self.models[model_name]['model']
73
+ print(f'>> Retrieving model {model_name} from system RAM cache')
74
+ self.models[model_name]['model'] = self._model_from_cpu(requested_model)
75
+ width = self.models[model_name]['width']
76
+ height = self.models[model_name]['height']
77
+ hash = self.models[model_name]['hash']
78
+
79
+ else: # we're about to load a new model, so potentially offload the least recently used one
80
+ try:
81
+ requested_model, width, height, hash = self._load_model(model_name)
82
+ self.models[model_name] = {
83
+ 'model': requested_model,
84
+ 'width': width,
85
+ 'height': height,
86
+ 'hash': hash,
87
+ }
88
+
89
+ except Exception as e:
90
+ print(f'** model {model_name} could not be loaded: {str(e)}')
91
+ print(traceback.format_exc())
92
+ assert self.current_model,'** FATAL: no current model to restore to'
93
+ print(f'** restoring {self.current_model}')
94
+ self.get_model(self.current_model)
95
+ return
96
+
97
+ self.current_model = model_name
98
+ self._push_newest_model(model_name)
99
+ return {
100
+ 'model':requested_model,
101
+ 'width':width,
102
+ 'height':height,
103
+ 'hash': hash
104
+ }
105
+
106
+ def default_model(self) -> str:
107
+ '''
108
+ Returns the name of the default model, or None
109
+ if none is defined.
110
+ '''
111
+ for model_name in self.config:
112
+ if self.config[model_name].get('default'):
113
+ return model_name
114
+
115
+ def set_default_model(self,model_name:str) -> None:
116
+ '''
117
+ Set the default model. The change will not take
118
+ effect until you call model_cache.commit()
119
+ '''
120
+ assert model_name in self.models,f"unknown model '{model_name}'"
121
+
122
+ config = self.config
123
+ for model in config:
124
+ config[model].pop('default',None)
125
+ config[model_name]['default'] = True
126
+
127
+ def list_models(self) -> dict:
128
+ '''
129
+ Return a dict of models in the format:
130
+ { model_name1: {'status': ('active'|'cached'|'not loaded'),
131
+ 'description': description,
132
+ },
133
+ model_name2: { etc }
134
+ '''
135
+ models = {}
136
+ for name in self.config:
137
+ description = self.config[name].description if 'description' in self.config[name] else '<no description>'
138
+ weights = self.config[name].weights if 'weights' in self.config[name] else '<no weights>'
139
+ config = self.config[name].config if 'config' in self.config[name] else '<no config>'
140
+ width = self.config[name].width if 'width' in self.config[name] else 512
141
+ height = self.config[name].height if 'height' in self.config[name] else 512
142
+ default = self.config[name].default if 'default' in self.config[name] else False
143
+ vae = self.config[name].vae if 'vae' in self.config[name] else '<no vae>'
144
+
145
+ if self.current_model == name:
146
+ status = 'active'
147
+ elif name in self.models:
148
+ status = 'cached'
149
+ else:
150
+ status = 'not loaded'
151
+
152
+ models[name]={
153
+ 'status' : status,
154
+ 'description' : description,
155
+ 'weights': weights,
156
+ 'config': config,
157
+ 'width': width,
158
+ 'height': height,
159
+ 'vae': vae,
160
+ 'default': default
161
+ }
162
+ return models
163
+
164
+ def print_models(self) -> None:
165
+ '''
166
+ Print a table of models, their descriptions, and load status
167
+ '''
168
+ models = self.list_models()
169
+ for name in models:
170
+ line = f'{name:25s} {models[name]["status"]:>10s} {models[name]["description"]}'
171
+ if models[name]['status'] == 'active':
172
+ line = f'\033[1m{line}\033[0m'
173
+ print(line)
174
+
175
+ def del_model(self, model_name:str) -> None:
176
+ '''
177
+ Delete the named model.
178
+ '''
179
+ omega = self.config
180
+ del omega[model_name]
181
+ if model_name in self.stack:
182
+ self.stack.remove(model_name)
183
+
184
+ def add_model(self, model_name:str, model_attributes:dict, clobber=False) -> None:
185
+ '''
186
+ Update the named model with a dictionary of attributes. Will fail with an
187
+ assertion error if the name already exists. Pass clobber=True to overwrite.
188
+ On a successful update, the config will be changed in memory and the
189
+ method will return True. Will fail with an assertion error if provided
190
+ attributes are incorrect or the model name is missing.
191
+ '''
192
+ omega = self.config
193
+ for field in ('description','weights','height','width','config'):
194
+ assert field in model_attributes, f'required field {field} is missing'
195
+ assert (clobber or model_name not in omega), f'attempt to overwrite existing model definition "{model_name}"'
196
+
197
+ config = omega[model_name] if model_name in omega else {}
198
+ for field in model_attributes:
199
+ if field == 'weights':
200
+ field.replace('\\', '/')
201
+ config[field] = model_attributes[field]
202
+
203
+ omega[model_name] = config
204
+ if clobber:
205
+ self._invalidate_cached_model(model_name)
206
+
207
+ def _load_model(self, model_name:str):
208
+ """Load and initialize the model from configuration variables passed at object creation time"""
209
+ if model_name not in self.config:
210
+ print(f'"{model_name}" is not a known model name. Please check your models.yaml file')
211
+
212
+ mconfig = self.config[model_name]
213
+ config = mconfig.config
214
+ weights = mconfig.weights
215
+ vae = mconfig.get('vae')
216
+ width = mconfig.width
217
+ height = mconfig.height
218
+
219
+ if not os.path.isabs(weights):
220
+ weights = os.path.normpath(os.path.join(Globals.root,weights))
221
+ # scan model
222
+ self.scan_model(model_name, weights)
223
+
224
+ print(f'>> Loading {model_name} from {weights}')
225
+
226
+ # for usage statistics
227
+ if self._has_cuda():
228
+ torch.cuda.reset_peak_memory_stats()
229
+ torch.cuda.empty_cache()
230
+
231
+ tic = time.time()
232
+
233
+ # this does the work
234
+ if not os.path.isabs(config):
235
+ config = os.path.join(Globals.root,config)
236
+ omega_config = OmegaConf.load(config)
237
+ with open(weights,'rb') as f:
238
+ weight_bytes = f.read()
239
+ model_hash = self._cached_sha256(weights,weight_bytes)
240
+ sd = torch.load(io.BytesIO(weight_bytes), map_location='cpu')
241
+ del weight_bytes
242
+ # merged models from auto11 merge board are flat for some reason
243
+ if 'state_dict' in sd:
244
+ sd = sd['state_dict']
245
+
246
+ print(f' | Forcing garbage collection prior to loading new model')
247
+ gc.collect()
248
+ model = instantiate_from_config(omega_config.model)
249
+ model.load_state_dict(sd, strict=False)
250
+
251
+ if self.precision == 'float16':
252
+ print(' | Using faster float16 precision')
253
+ model.to(torch.float16)
254
+ else:
255
+ print(' | Using more accurate float32 precision')
256
+
257
+ # look and load a matching vae file. Code borrowed from AUTOMATIC1111 modules/sd_models.py
258
+ if vae:
259
+ if not os.path.isabs(vae):
260
+ vae = os.path.normpath(os.path.join(Globals.root,vae))
261
+ if os.path.exists(vae):
262
+ print(f' | Loading VAE weights from: {vae}')
263
+ vae_ckpt = torch.load(vae, map_location="cpu")
264
+ vae_dict = {k: v for k, v in vae_ckpt["state_dict"].items() if k[0:4] != "loss"}
265
+ model.first_stage_model.load_state_dict(vae_dict, strict=False)
266
+ else:
267
+ print(f' | VAE file {vae} not found. Skipping.')
268
+
269
+ model.to(self.device)
270
+ # model.to doesn't change the cond_stage_model.device used to move the tokenizer output, so set it here
271
+ model.cond_stage_model.device = self.device
272
+
273
+ model.eval()
274
+
275
+ for module in model.modules():
276
+ if isinstance(module, (torch.nn.Conv2d, torch.nn.ConvTranspose2d)):
277
+ module._orig_padding_mode = module.padding_mode
278
+
279
+ # usage statistics
280
+ toc = time.time()
281
+ print(f'>> Model loaded in', '%4.2fs' % (toc - tic))
282
+
283
+ if self._has_cuda():
284
+ print(
285
+ '>> Max VRAM used to load the model:',
286
+ '%4.2fG' % (torch.cuda.max_memory_allocated() / 1e9),
287
+ '\n>> Current VRAM usage:'
288
+ '%4.2fG' % (torch.cuda.memory_allocated() / 1e9),
289
+ )
290
+
291
+ return model, width, height, model_hash
292
+
293
+ def offload_model(self, model_name:str) -> None:
294
+ '''
295
+ Offload the indicated model to CPU. Will call
296
+ _make_cache_room() to free space if needed.
297
+ '''
298
+ if model_name not in self.models:
299
+ return
300
+
301
+ print(f'>> Offloading {model_name} to CPU')
302
+ model = self.models[model_name]['model']
303
+ self.models[model_name]['model'] = self._model_to_cpu(model)
304
+
305
+ gc.collect()
306
+ if self._has_cuda():
307
+ torch.cuda.empty_cache()
308
+
309
+ def scan_model(self, model_name, checkpoint):
310
+ # scan model
311
+ print(f'>> Scanning Model: {model_name}')
312
+ scan_result = scan_file_path(checkpoint)
313
+ if scan_result.infected_files != 0:
314
+ if scan_result.infected_files == 1:
315
+ print(f'\n### Issues Found In Model: {scan_result.issues_count}')
316
+ print('### WARNING: The model you are trying to load seems to be infected.')
317
+ print('### For your safety, InvokeAI will not load this model.')
318
+ print('### Please use checkpoints from trusted sources.')
319
+ print("### Exiting InvokeAI")
320
+ sys.exit()
321
+ else:
322
+ print('\n### WARNING: InvokeAI was unable to scan the model you are using.')
323
+ model_safe_check_fail = ask_user('Do you want to to continue loading the model?', ['y', 'n'])
324
+ if model_safe_check_fail.lower() != 'y':
325
+ print("### Exiting InvokeAI")
326
+ sys.exit()
327
+ else:
328
+ print('>> Model Scanned. OK!!')
329
+
330
+ def search_models(self, search_folder):
331
+
332
+ print(f'>> Finding Models In: {search_folder}')
333
+ models_folder = Path(search_folder).glob('**/*.ckpt')
334
+
335
+ files = [x for x in models_folder if x.is_file()]
336
+
337
+ found_models = []
338
+ for file in files:
339
+ found_models.append({
340
+ 'name': file.stem,
341
+ 'location': str(file.resolve()).replace('\\', '/')
342
+ })
343
+
344
+ return search_folder, found_models
345
+
346
+ def _make_cache_room(self) -> None:
347
+ num_loaded_models = len(self.models)
348
+ if num_loaded_models >= self.max_loaded_models:
349
+ least_recent_model = self._pop_oldest_model()
350
+ print(f'>> Cache limit (max={self.max_loaded_models}) reached. Purging {least_recent_model}')
351
+ if least_recent_model is not None:
352
+ del self.models[least_recent_model]
353
+ gc.collect()
354
+
355
+ def print_vram_usage(self) -> None:
356
+ if self._has_cuda:
357
+ print('>> Current VRAM usage: ','%4.2fG' % (torch.cuda.memory_allocated() / 1e9))
358
+
359
+ def commit(self,config_file_path:str) -> None:
360
+ '''
361
+ Write current configuration out to the indicated file.
362
+ '''
363
+ yaml_str = OmegaConf.to_yaml(self.config)
364
+ tmpfile = os.path.join(os.path.dirname(config_file_path),'new_config.tmp')
365
+ with open(tmpfile, 'w') as outfile:
366
+ outfile.write(self.preamble())
367
+ outfile.write(yaml_str)
368
+ os.replace(tmpfile,config_file_path)
369
+
370
+ def preamble(self) -> str:
371
+ '''
372
+ Returns the preamble for the config file.
373
+ '''
374
+ return textwrap.dedent('''\
375
+ # This file describes the alternative machine learning models
376
+ # available to InvokeAI script.
377
+ #
378
+ # To add a new model, follow the examples below. Each
379
+ # model requires a model config file, a weights file,
380
+ # and the width and height of the images it
381
+ # was trained on.
382
+ ''')
383
+
384
+ def _invalidate_cached_model(self,model_name:str) -> None:
385
+ self.offload_model(model_name)
386
+ if model_name in self.stack:
387
+ self.stack.remove(model_name)
388
+ self.models.pop(model_name,None)
389
+
390
+ def _model_to_cpu(self,model):
391
+ if self.device != 'cpu':
392
+ model.cond_stage_model.device = 'cpu'
393
+ model.first_stage_model.to('cpu')
394
+ model.cond_stage_model.to('cpu')
395
+ model.model.to('cpu')
396
+ return model.to('cpu')
397
+ else:
398
+ return model
399
+
400
+ def _model_from_cpu(self,model):
401
+ if self.device != 'cpu':
402
+ model.to(self.device)
403
+ model.first_stage_model.to(self.device)
404
+ model.cond_stage_model.to(self.device)
405
+ model.cond_stage_model.device = self.device
406
+ return model
407
+
408
+ def _pop_oldest_model(self):
409
+ '''
410
+ Remove the first element of the FIFO, which ought
411
+ to be the least recently accessed model. Do not
412
+ pop the last one, because it is in active use!
413
+ '''
414
+ return self.stack.pop(0)
415
+
416
+ def _push_newest_model(self,model_name:str) -> None:
417
+ '''
418
+ Maintain a simple FIFO. First element is always the
419
+ least recent, and last element is always the most recent.
420
+ '''
421
+ with contextlib.suppress(ValueError):
422
+ self.stack.remove(model_name)
423
+ self.stack.append(model_name)
424
+
425
+ def _has_cuda(self) -> bool:
426
+ return self.device.type == 'cuda'
427
+
428
+ def _cached_sha256(self,path,data) -> Union[str, bytes]:
429
+ dirname = os.path.dirname(path)
430
+ basename = os.path.basename(path)
431
+ base, _ = os.path.splitext(basename)
432
+ hashpath = os.path.join(dirname,base+'.sha256')
433
+
434
+ if os.path.exists(hashpath) and os.path.getmtime(path) <= os.path.getmtime(hashpath):
435
+ with open(hashpath) as f:
436
+ hash = f.read()
437
+ return hash
438
+
439
+ print(f'>> Calculating sha256 hash of weights file')
440
+ tic = time.time()
441
+ sha = hashlib.sha256()
442
+ sha.update(data)
443
+ hash = sha.hexdigest()
444
+ toc = time.time()
445
+ print(f'>> sha256 = {hash}','(%4.2fs)' % (toc - tic))
446
+
447
+ with open(hashpath,'w') as f:
448
+ f.write(hash)
449
+ return hash
@@ -0,0 +1,44 @@
1
+ '''
2
+ This module defines a singleton object, "patchmatch" that
3
+ wraps the actual patchmatch object. It respects the global
4
+ "try_patchmatch" attribute, so that patchmatch loading can
5
+ be suppressed or deferred
6
+ '''
7
+ from ldm.invoke.globals import Globals
8
+ import numpy as np
9
+
10
+ class PatchMatch:
11
+ '''
12
+ Thin class wrapper around the patchmatch function.
13
+ '''
14
+
15
+ patch_match = None
16
+ tried_load:bool = False
17
+
18
+ def __init__(self):
19
+ super().__init__()
20
+
21
+ @classmethod
22
+ def _load_patch_match(self):
23
+ if self.tried_load:
24
+ return
25
+ if Globals.try_patchmatch:
26
+ from patchmatch import patch_match as pm
27
+ if pm.patchmatch_available:
28
+ print('>> Patchmatch initialized')
29
+ else:
30
+ print('>> Patchmatch not loaded (nonfatal)')
31
+ self.patch_match = pm
32
+ else:
33
+ print('>> Patchmatch loading disabled')
34
+ self.tried_load = True
35
+
36
+ @classmethod
37
+ def patchmatch_available(self)->bool:
38
+ self._load_patch_match()
39
+ return self.patch_match and self.patch_match.patchmatch_available
40
+
41
+ @classmethod
42
+ def inpaint(self,*args,**kwargs)->np.ndarray:
43
+ if self.patchmatch_available():
44
+ return self.patch_match.inpaint(*args,**kwargs)
@@ -0,0 +1,113 @@
1
+ """
2
+ Two helper classes for dealing with PNG images and their path names.
3
+ PngWriter -- Converts Images generated by T2I into PNGs, finds
4
+ appropriate names for them, and writes prompt metadata
5
+ into the PNG.
6
+
7
+ Exports function retrieve_metadata(path)
8
+ """
9
+ import os
10
+ import re
11
+ import json
12
+ from PIL import PngImagePlugin, Image
13
+
14
+ # -------------------image generation utils-----
15
+
16
+
17
+ class PngWriter:
18
+ def __init__(self, outdir):
19
+ self.outdir = outdir
20
+ os.makedirs(outdir, exist_ok=True)
21
+
22
+ # gives the next unique prefix in outdir
23
+ def unique_prefix(self):
24
+ # sort reverse alphabetically until we find max+1
25
+ dirlist = sorted(os.listdir(self.outdir), reverse=True)
26
+ # find the first filename that matches our pattern or return 000000.0.png
27
+ existing_name = next(
28
+ (f for f in dirlist if re.match('^(\d+)\..*\.png', f)),
29
+ '0000000.0.png',
30
+ )
31
+ basecount = int(existing_name.split('.', 1)[0]) + 1
32
+ return f'{basecount:06}'
33
+
34
+ # saves image named _image_ to outdir/name, writing metadata from prompt
35
+ # returns full path of output
36
+ def save_image_and_prompt_to_png(self, image, dream_prompt, name, metadata=None, compress_level=6):
37
+ path = os.path.join(self.outdir, name)
38
+ info = PngImagePlugin.PngInfo()
39
+ info.add_text('Dream', dream_prompt)
40
+ if metadata:
41
+ info.add_text('sd-metadata', json.dumps(metadata))
42
+ image.save(path, 'PNG', pnginfo=info, compress_level=compress_level)
43
+ return path
44
+
45
+ def retrieve_metadata(self,img_basename):
46
+ '''
47
+ Given a PNG filename stored in outdir, returns the "sd-metadata"
48
+ metadata stored there, as a dict
49
+ '''
50
+ path = os.path.join(self.outdir,img_basename)
51
+ all_metadata = retrieve_metadata(path)
52
+ return all_metadata['sd-metadata']
53
+
54
+ def retrieve_metadata(img_path):
55
+ '''
56
+ Given a path to a PNG image, returns the "sd-metadata"
57
+ metadata stored there, as a dict
58
+ '''
59
+ im = Image.open(img_path)
60
+ if hasattr(im, 'text'):
61
+ md = im.text.get('sd-metadata', '{}')
62
+ dream_prompt = im.text.get('Dream', '')
63
+ else:
64
+ # When trying to retrieve metadata from images without a 'text' payload, such as JPG images.
65
+ md = '{}'
66
+ dream_prompt = ''
67
+ return {'sd-metadata': json.loads(md), 'Dream': dream_prompt}
68
+
69
+ def write_metadata(img_path:str, meta:dict):
70
+ im = Image.open(img_path)
71
+ info = PngImagePlugin.PngInfo()
72
+ info.add_text('sd-metadata', json.dumps(meta))
73
+ im.save(img_path,'PNG',pnginfo=info)
74
+
75
+ class PromptFormatter:
76
+ def __init__(self, t2i, opt):
77
+ self.t2i = t2i
78
+ self.opt = opt
79
+
80
+ # note: the t2i object should provide all these values.
81
+ # there should be no need to or against opt values
82
+ def normalize_prompt(self):
83
+ """Normalize the prompt and switches"""
84
+ t2i = self.t2i
85
+ opt = self.opt
86
+
87
+ switches = list()
88
+ switches.append(f'"{opt.prompt}"')
89
+ switches.append(f'-s{opt.steps or t2i.steps}')
90
+ switches.append(f'-W{opt.width or t2i.width}')
91
+ switches.append(f'-H{opt.height or t2i.height}')
92
+ switches.append(f'-C{opt.cfg_scale or t2i.cfg_scale}')
93
+ switches.append(f'-A{opt.sampler_name or t2i.sampler_name}')
94
+ # to do: put model name into the t2i object
95
+ # switches.append(f'--model{t2i.model_name}')
96
+ if opt.seamless or t2i.seamless:
97
+ switches.append(f'--seamless')
98
+ if opt.init_img:
99
+ switches.append(f'-I{opt.init_img}')
100
+ if opt.fit:
101
+ switches.append(f'--fit')
102
+ if opt.strength and opt.init_img is not None:
103
+ switches.append(f'-f{opt.strength or t2i.strength}')
104
+ if opt.gfpgan_strength:
105
+ switches.append(f'-G{opt.gfpgan_strength}')
106
+ if opt.upscale:
107
+ switches.append(f'-U {" ".join([str(u) for u in opt.upscale])}')
108
+ if opt.variation_amount > 0:
109
+ switches.append(f'-v{opt.variation_amount}')
110
+ if opt.with_variations:
111
+ formatted_variations = ','.join(f'{seed}:{weight}' for seed, weight in opt.with_variations)
112
+ switches.append(f'-V{formatted_variations}')
113
+ return ' '.join(switches)