InvokeAI 2.2.4.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. backend/__init__.py +0 -0
  2. backend/invoke_ai_web_server.py +1724 -0
  3. backend/modules/__init__.py +0 -0
  4. backend/modules/create_cmd_parser.py +55 -0
  5. backend/modules/get_canvas_generation_mode.py +117 -0
  6. backend/modules/parameters.py +71 -0
  7. backend/modules/parse_seed_weights.py +47 -0
  8. configs/INITIAL_MODELS.yaml +80 -0
  9. configs/models.yaml.example +29 -0
  10. configs/sd-concepts.txt +803 -0
  11. configs/stable-diffusion/v1-finetune.yaml +110 -0
  12. configs/stable-diffusion/v1-finetune_style.yaml +103 -0
  13. configs/stable-diffusion/v1-inference.yaml +79 -0
  14. configs/stable-diffusion/v1-inpainting-inference.yaml +79 -0
  15. configs/stable-diffusion/v1-m1-finetune.yaml +110 -0
  16. docs/assets/invoke-web-server-1.png +0 -0
  17. docs/assets/invoke_ai_banner.png +0 -0
  18. invokeai-2.2.4.5.data/data/assets/caution.png +0 -0
  19. invokeai-2.2.4.5.data/data/configs/INITIAL_MODELS.yaml +80 -0
  20. invokeai-2.2.4.5.data/data/configs/models.yaml.example +29 -0
  21. invokeai-2.2.4.5.data/data/configs/sd-concepts.txt +803 -0
  22. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-finetune.yaml +110 -0
  23. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-finetune_style.yaml +103 -0
  24. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-inference.yaml +79 -0
  25. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-inpainting-inference.yaml +79 -0
  26. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-m1-finetune.yaml +110 -0
  27. invokeai-2.2.4.5.data/data/frontend/dist/assets/Inter-Bold.790c108b.ttf +0 -0
  28. invokeai-2.2.4.5.data/data/frontend/dist/assets/Inter.b9a8e5e2.ttf +0 -0
  29. invokeai-2.2.4.5.data/data/frontend/dist/assets/favicon.0d253ced.ico +0 -0
  30. invokeai-2.2.4.5.data/data/frontend/dist/assets/index-legacy-b741077f.js +48 -0
  31. invokeai-2.2.4.5.data/data/frontend/dist/assets/index.0dadf5d0.css +1 -0
  32. invokeai-2.2.4.5.data/data/frontend/dist/assets/index.21ff81eb.js +625 -0
  33. invokeai-2.2.4.5.data/data/frontend/dist/assets/logo.13003d72.png +0 -0
  34. invokeai-2.2.4.5.data/data/frontend/dist/assets/polyfills-legacy-dde3a68a.js +4 -0
  35. invokeai-2.2.4.5.data/data/frontend/dist/assets/polyfills.1ff60148.js +1 -0
  36. invokeai-2.2.4.5.data/data/frontend/dist/index.html +23 -0
  37. invokeai-2.2.4.5.data/data/scripts/configure_invokeai.py +797 -0
  38. invokeai-2.2.4.5.data/data/scripts/create_root.py +43 -0
  39. invokeai-2.2.4.5.data/data/scripts/dream.py +10 -0
  40. invokeai-2.2.4.5.data/data/scripts/images2prompt.py +30 -0
  41. invokeai-2.2.4.5.data/data/scripts/invoke.py +5 -0
  42. invokeai-2.2.4.5.data/data/scripts/legacy_api.py +681 -0
  43. invokeai-2.2.4.5.data/data/scripts/load_models.py +12 -0
  44. invokeai-2.2.4.5.data/data/scripts/merge_embeddings.py +124 -0
  45. invokeai-2.2.4.5.data/data/scripts/preload_models.py +12 -0
  46. invokeai-2.2.4.5.data/data/scripts/sd-metadata.py +22 -0
  47. invokeai-2.2.4.5.dist-info/LICENSE +21 -0
  48. invokeai-2.2.4.5.dist-info/LICENSE-ModelWeights.txt +294 -0
  49. invokeai-2.2.4.5.dist-info/METADATA +288 -0
  50. invokeai-2.2.4.5.dist-info/RECORD +169 -0
  51. invokeai-2.2.4.5.dist-info/WHEEL +5 -0
  52. invokeai-2.2.4.5.dist-info/entry_points.txt +4 -0
  53. invokeai-2.2.4.5.dist-info/top_level.txt +6 -0
  54. invokeai-2.2.4.5.dist-info/zip-safe +1 -0
  55. ldm/__init__.py +0 -0
  56. ldm/data/__init__.py +0 -0
  57. ldm/data/base.py +31 -0
  58. ldm/data/imagenet.py +466 -0
  59. ldm/data/lsun.py +126 -0
  60. ldm/data/personalized.py +202 -0
  61. ldm/data/personalized_style.py +169 -0
  62. ldm/generate.py +1159 -0
  63. ldm/invoke/CLI.py +960 -0
  64. ldm/invoke/__init__.py +3 -0
  65. ldm/invoke/args.py +1257 -0
  66. ldm/invoke/concepts_lib.py +169 -0
  67. ldm/invoke/conditioning.py +289 -0
  68. ldm/invoke/devices.py +27 -0
  69. ldm/invoke/generator/__init__.py +4 -0
  70. ldm/invoke/generator/base.py +335 -0
  71. ldm/invoke/generator/embiggen.py +501 -0
  72. ldm/invoke/generator/img2img.py +97 -0
  73. ldm/invoke/generator/inpaint.py +358 -0
  74. ldm/invoke/generator/omnibus.py +175 -0
  75. ldm/invoke/generator/txt2img.py +88 -0
  76. ldm/invoke/generator/txt2img2img.py +182 -0
  77. ldm/invoke/globals.py +31 -0
  78. ldm/invoke/image_util.py +70 -0
  79. ldm/invoke/log.py +66 -0
  80. ldm/invoke/model_cache.py +449 -0
  81. ldm/invoke/patchmatch.py +44 -0
  82. ldm/invoke/pngwriter.py +113 -0
  83. ldm/invoke/prompt_parser.py +655 -0
  84. ldm/invoke/readline.py +442 -0
  85. ldm/invoke/restoration/__init__.py +4 -0
  86. ldm/invoke/restoration/base.py +38 -0
  87. ldm/invoke/restoration/codeformer.py +103 -0
  88. ldm/invoke/restoration/codeformer_arch.py +276 -0
  89. ldm/invoke/restoration/gfpgan.py +84 -0
  90. ldm/invoke/restoration/outcrop.py +108 -0
  91. ldm/invoke/restoration/outpaint.py +92 -0
  92. ldm/invoke/restoration/realesrgan.py +90 -0
  93. ldm/invoke/restoration/vqgan_arch.py +435 -0
  94. ldm/invoke/seamless.py +30 -0
  95. ldm/invoke/server.py +282 -0
  96. ldm/invoke/server_legacy.py +246 -0
  97. ldm/invoke/txt2mask.py +136 -0
  98. ldm/lr_scheduler.py +143 -0
  99. ldm/models/__init__.py +0 -0
  100. ldm/models/autoencoder.py +596 -0
  101. ldm/models/diffusion/__init__.py +0 -0
  102. ldm/models/diffusion/classifier.py +355 -0
  103. ldm/models/diffusion/cross_attention_control.py +443 -0
  104. ldm/models/diffusion/cross_attention_map_saving.py +95 -0
  105. ldm/models/diffusion/ddim.py +111 -0
  106. ldm/models/diffusion/ddpm.py +2263 -0
  107. ldm/models/diffusion/ksampler.py +312 -0
  108. ldm/models/diffusion/plms.py +146 -0
  109. ldm/models/diffusion/sampler.py +450 -0
  110. ldm/models/diffusion/shared_invokeai_diffusion.py +244 -0
  111. ldm/modules/__init__.py +0 -0
  112. ldm/modules/attention.py +274 -0
  113. ldm/modules/diffusionmodules/__init__.py +0 -0
  114. ldm/modules/diffusionmodules/model.py +894 -0
  115. ldm/modules/diffusionmodules/openaimodel.py +1036 -0
  116. ldm/modules/diffusionmodules/util.py +314 -0
  117. ldm/modules/distributions/__init__.py +0 -0
  118. ldm/modules/distributions/distributions.py +104 -0
  119. ldm/modules/ema.py +88 -0
  120. ldm/modules/embedding_manager.py +413 -0
  121. ldm/modules/encoders/__init__.py +0 -0
  122. ldm/modules/encoders/modules.py +772 -0
  123. ldm/modules/image_degradation/__init__.py +6 -0
  124. ldm/modules/image_degradation/bsrgan.py +846 -0
  125. ldm/modules/image_degradation/bsrgan_light.py +751 -0
  126. ldm/modules/image_degradation/utils_image.py +1024 -0
  127. ldm/modules/losses/__init__.py +1 -0
  128. ldm/modules/losses/contperceptual.py +175 -0
  129. ldm/modules/losses/vqperceptual.py +240 -0
  130. ldm/modules/x_transformer.py +752 -0
  131. ldm/simplet2i.py +13 -0
  132. ldm/util.py +264 -0
  133. scripts/configure_invokeai.py +797 -0
  134. scripts/create_root.py +43 -0
  135. scripts/dream.py +10 -0
  136. scripts/images2prompt.py +30 -0
  137. scripts/invoke.py +5 -0
  138. scripts/legacy_api.py +681 -0
  139. scripts/load_models.py +12 -0
  140. scripts/merge_embeddings.py +124 -0
  141. scripts/orig_scripts/img2img.py +296 -0
  142. scripts/orig_scripts/inpaint.py +98 -0
  143. scripts/orig_scripts/knn2img.py +397 -0
  144. scripts/orig_scripts/sample_diffusion.py +313 -0
  145. scripts/orig_scripts/train_searcher.py +147 -0
  146. scripts/orig_scripts/txt2img.py +324 -0
  147. scripts/preload_models.py +12 -0
  148. scripts/sd-metadata.py +22 -0
  149. vendored/clip/clip/__init__.py +1 -0
  150. vendored/clip/clip/bpe_simple_vocab_16e6.txt.gz +0 -0
  151. vendored/clip/clip/clip.py +237 -0
  152. vendored/clip/clip/model.py +436 -0
  153. vendored/clip/clip/simple_tokenizer.py +132 -0
  154. vendored/clip/hubconf.py +42 -0
  155. vendored/clip/setup.py +21 -0
  156. vendored/clip/tests/test_consistency.py +25 -0
  157. vendored/clipseg/datasets/coco_wrapper.py +99 -0
  158. vendored/clipseg/datasets/pascal_zeroshot.py +60 -0
  159. vendored/clipseg/datasets/pfe_dataset.py +129 -0
  160. vendored/clipseg/datasets/phrasecut.py +380 -0
  161. vendored/clipseg/datasets/utils.py +68 -0
  162. vendored/clipseg/evaluation_utils.py +292 -0
  163. vendored/clipseg/general_utils.py +272 -0
  164. vendored/clipseg/metrics.py +271 -0
  165. vendored/clipseg/models/clipseg.py +538 -0
  166. vendored/clipseg/models/vitseg.py +286 -0
  167. vendored/clipseg/score.py +453 -0
  168. vendored/clipseg/setup.py +30 -0
  169. vendored/clipseg/training.py +266 -0
ldm/invoke/CLI.py ADDED
@@ -0,0 +1,960 @@
1
+ import os
2
+ import re
3
+ import sys
4
+ import shlex
5
+ import copy
6
+ import warnings
7
+ import time
8
+ import traceback
9
+ import yaml
10
+
11
+ from ldm.invoke.globals import Globals
12
+ from ldm.generate import Generate
13
+ from ldm.invoke.prompt_parser import PromptParser
14
+ from ldm.invoke.readline import get_completer, Completer
15
+ from ldm.invoke.args import Args, metadata_dumps, metadata_from_png, dream_cmd_from_png
16
+ from ldm.invoke.pngwriter import PngWriter, retrieve_metadata, write_metadata
17
+ from ldm.invoke.image_util import make_grid
18
+ from ldm.invoke.log import write_log
19
+ from ldm.invoke.concepts_lib import Concepts
20
+ from omegaconf import OmegaConf
21
+ from pathlib import Path
22
+ import pyparsing
23
+
24
+ # global used in multiple functions (fix)
25
+ infile = None
26
+
27
+ def main():
28
+ """Initialize command-line parsers and the diffusion model"""
29
+ global infile
30
+
31
+ opt = Args()
32
+ args = opt.parse_args()
33
+ if not args:
34
+ sys.exit(-1)
35
+
36
+ if args.laion400m:
37
+ print('--laion400m flag has been deprecated. Please use --model laion400m instead.')
38
+ sys.exit(-1)
39
+ if args.weights:
40
+ print('--weights argument has been deprecated. Please edit ./configs/models.yaml, and select the weights using --model instead.')
41
+ sys.exit(-1)
42
+ if args.max_loaded_models is not None:
43
+ if args.max_loaded_models <= 0:
44
+ print('--max_loaded_models must be >= 1; using 1')
45
+ args.max_loaded_models = 1
46
+
47
+ if not args.conf:
48
+ if not os.path.exists(os.path.join(Globals.root,'configs','models.yaml')):
49
+ print(f"\n** Error. The file {os.path.join(Globals.root,'configs','models.yaml')} could not be found.")
50
+ print(f'** Please check the location of your invokeai directory and use the --root_dir option to point to the correct path.')
51
+ print(f'** This script will now exit.')
52
+ sys.exit(-1)
53
+
54
+ print(f'>> InvokeAI runtime directory is "{Globals.root}"')
55
+
56
+ # loading here to avoid long delays on startup
57
+ from ldm.generate import Generate
58
+
59
+ # these two lines prevent a horrible warning message from appearing
60
+ # when the frozen CLIP tokenizer is imported
61
+ import transformers
62
+ transformers.logging.set_verbosity_error()
63
+
64
+ # Loading Face Restoration and ESRGAN Modules
65
+ gfpgan,codeformer,esrgan = load_face_restoration(opt)
66
+
67
+ # normalize the config directory relative to root
68
+ if not os.path.isabs(opt.conf):
69
+ opt.conf = os.path.normpath(os.path.join(Globals.root,opt.conf))
70
+
71
+ if opt.embeddings:
72
+ if not os.path.isabs(opt.embedding_path):
73
+ embedding_path = os.path.normpath(os.path.join(Globals.root,opt.embedding_path))
74
+ else:
75
+ embedding_path = opt.embedding_path
76
+ else:
77
+ embedding_path = None
78
+
79
+ # load the infile as a list of lines
80
+ if opt.infile:
81
+ try:
82
+ if os.path.isfile(opt.infile):
83
+ infile = open(opt.infile, 'r', encoding='utf-8')
84
+ elif opt.infile == '-': # stdin
85
+ infile = sys.stdin
86
+ else:
87
+ raise FileNotFoundError(f'{opt.infile} not found.')
88
+ except (FileNotFoundError, IOError) as e:
89
+ print(f'{e}. Aborting.')
90
+ sys.exit(-1)
91
+
92
+ # creating a Generate object:
93
+ try:
94
+ gen = Generate(
95
+ conf = opt.conf,
96
+ model = opt.model,
97
+ sampler_name = opt.sampler_name,
98
+ embedding_path = embedding_path,
99
+ full_precision = opt.full_precision,
100
+ precision = opt.precision,
101
+ gfpgan=gfpgan,
102
+ codeformer=codeformer,
103
+ esrgan=esrgan,
104
+ free_gpu_mem=opt.free_gpu_mem,
105
+ safety_checker=opt.safety_checker,
106
+ max_loaded_models=opt.max_loaded_models,
107
+ )
108
+ except (FileNotFoundError, TypeError, AssertionError):
109
+ emergency_model_reconfigure(opt)
110
+ sys.exit(-1)
111
+ except (IOError, KeyError) as e:
112
+ print(f'{e}. Aborting.')
113
+ sys.exit(-1)
114
+
115
+ if opt.seamless:
116
+ print(">> changed to seamless tiling mode")
117
+
118
+ # preload the model
119
+ try:
120
+ gen.load_model()
121
+ except AssertionError:
122
+ emergency_model_reconfigure(opt)
123
+ sys.exit(-1)
124
+
125
+ # web server loops forever
126
+ if opt.web or opt.gui:
127
+ invoke_ai_web_server_loop(gen, gfpgan, codeformer, esrgan)
128
+ sys.exit(0)
129
+
130
+ if not infile:
131
+ print(
132
+ "\n* Initialization done! Awaiting your command (-h for help, 'q' to quit)"
133
+ )
134
+
135
+ try:
136
+ main_loop(gen, opt)
137
+ except KeyboardInterrupt:
138
+ print("\ngoodbye!")
139
+
140
+ # TODO: main_loop() has gotten busy. Needs to be refactored.
141
+ def main_loop(gen, opt):
142
+ """prompt/read/execute loop"""
143
+ global infile
144
+ done = False
145
+ doneAfterInFile = infile is not None
146
+ path_filter = re.compile(r'[<>:"/\\|?*]')
147
+ last_results = list()
148
+ model_config = OmegaConf.load(opt.conf)
149
+
150
+ # The readline completer reads history from the .dream_history file located in the
151
+ # output directory specified at the time of script launch. We do not currently support
152
+ # changing the history file midstream when the output directory is changed.
153
+ completer = get_completer(opt, models=list(model_config.keys()))
154
+ set_default_output_dir(opt, completer)
155
+ add_embedding_terms(gen, completer)
156
+ output_cntr = completer.get_current_history_length()+1
157
+
158
+ # os.pathconf is not available on Windows
159
+ if hasattr(os, 'pathconf'):
160
+ path_max = os.pathconf(opt.outdir, 'PC_PATH_MAX')
161
+ name_max = os.pathconf(opt.outdir, 'PC_NAME_MAX')
162
+ else:
163
+ path_max = 260
164
+ name_max = 255
165
+
166
+ while not done:
167
+
168
+ operation = 'generate'
169
+
170
+ try:
171
+ command = get_next_command(infile)
172
+ except EOFError:
173
+ done = infile is None or doneAfterInFile
174
+ infile = None
175
+ continue
176
+
177
+ # skip empty lines
178
+ if not command.strip():
179
+ continue
180
+
181
+ if command.startswith(('#', '//')):
182
+ continue
183
+
184
+ if len(command.strip()) == 1 and command.startswith('q'):
185
+ done = True
186
+ break
187
+
188
+ if not command.startswith('!history'):
189
+ completer.add_history(command)
190
+
191
+ if command.startswith('!'):
192
+ command, operation = do_command(command, gen, opt, completer)
193
+
194
+ if operation is None:
195
+ continue
196
+
197
+ if opt.parse_cmd(command) is None:
198
+ continue
199
+
200
+ if opt.init_img:
201
+ try:
202
+ if not opt.prompt:
203
+ oldargs = metadata_from_png(opt.init_img)
204
+ opt.prompt = oldargs.prompt
205
+ print(f'>> Retrieved old prompt "{opt.prompt}" from {opt.init_img}')
206
+ except (OSError, AttributeError, KeyError):
207
+ pass
208
+
209
+ if len(opt.prompt) == 0:
210
+ opt.prompt = ''
211
+
212
+ # width and height are set by model if not specified
213
+ if not opt.width:
214
+ opt.width = gen.width
215
+ if not opt.height:
216
+ opt.height = gen.height
217
+
218
+ # retrieve previous value of init image if requested
219
+ if opt.init_img is not None and re.match('^-\\d+$', opt.init_img):
220
+ try:
221
+ opt.init_img = last_results[int(opt.init_img)][0]
222
+ print(f'>> Reusing previous image {opt.init_img}')
223
+ except IndexError:
224
+ print(
225
+ f'>> No previous initial image at position {opt.init_img} found')
226
+ opt.init_img = None
227
+ continue
228
+
229
+ # the outdir can change with each command, so we adjust it here
230
+ set_default_output_dir(opt,completer)
231
+
232
+ # try to relativize pathnames
233
+ for attr in ('init_img','init_mask','init_color'):
234
+ if getattr(opt,attr) and not os.path.exists(getattr(opt,attr)):
235
+ basename = getattr(opt,attr)
236
+ path = os.path.join(opt.outdir,basename)
237
+ setattr(opt,attr,path)
238
+
239
+ # retrieve previous value of seed if requested
240
+ # Exception: for postprocess operations negative seed values
241
+ # mean "discard the original seed and generate a new one"
242
+ # (this is a non-obvious hack and needs to be reworked)
243
+ if opt.seed is not None and opt.seed < 0 and operation != 'postprocess':
244
+ try:
245
+ opt.seed = last_results[opt.seed][1]
246
+ print(f'>> Reusing previous seed {opt.seed}')
247
+ except IndexError:
248
+ print(f'>> No previous seed at position {opt.seed} found')
249
+ opt.seed = None
250
+ continue
251
+
252
+ if opt.strength is None:
253
+ opt.strength = 0.75 if opt.out_direction is None else 0.83
254
+
255
+ if opt.with_variations is not None:
256
+ opt.with_variations = split_variations(opt.with_variations)
257
+
258
+ if opt.prompt_as_dir and operation == 'generate':
259
+ # sanitize the prompt to a valid folder name
260
+ subdir = path_filter.sub('_', opt.prompt)[:name_max].rstrip(' .')
261
+
262
+ # truncate path to maximum allowed length
263
+ # 39 is the length of '######.##########.##########-##.png', plus two separators and a NUL
264
+ subdir = subdir[:(path_max - 39 - len(os.path.abspath(opt.outdir)))]
265
+ current_outdir = os.path.join(opt.outdir, subdir)
266
+
267
+ print('Writing files to directory: "' + current_outdir + '"')
268
+
269
+ # make sure the output directory exists
270
+ if not os.path.exists(current_outdir):
271
+ os.makedirs(current_outdir)
272
+ else:
273
+ if not os.path.exists(opt.outdir):
274
+ os.makedirs(opt.outdir)
275
+ current_outdir = opt.outdir
276
+
277
+ # Here is where the images are actually generated!
278
+ last_results = []
279
+ try:
280
+ file_writer = PngWriter(current_outdir)
281
+ results = [] # list of filename, prompt pairs
282
+ grid_images = dict() # seed -> Image, only used if `opt.grid`
283
+ prior_variations = opt.with_variations or []
284
+ prefix = file_writer.unique_prefix()
285
+ step_callback = make_step_callback(gen, opt, prefix) if opt.save_intermediates > 0 else None
286
+
287
+ def image_writer(image, seed, upscaled=False, first_seed=None, use_prefix=None, prompt_in=None, attention_maps_image=None):
288
+ # note the seed is the seed of the current image
289
+ # the first_seed is the original seed that noise is added to
290
+ # when the -v switch is used to generate variations
291
+ nonlocal prior_variations
292
+ nonlocal prefix
293
+
294
+ path = None
295
+ if opt.grid:
296
+ grid_images[seed] = image
297
+
298
+ elif operation == 'mask':
299
+ filename = f'{prefix}.{use_prefix}.{seed}.png'
300
+ tm = opt.text_mask[0]
301
+ th = opt.text_mask[1] if len(opt.text_mask)>1 else 0.5
302
+ formatted_dream_prompt = f'!mask {opt.input_file_path} -tm {tm} {th}'
303
+ path = file_writer.save_image_and_prompt_to_png(
304
+ image = image,
305
+ dream_prompt = formatted_dream_prompt,
306
+ metadata = {},
307
+ name = filename,
308
+ compress_level = opt.png_compression,
309
+ )
310
+ results.append([path, formatted_dream_prompt])
311
+
312
+ else:
313
+ if use_prefix is not None:
314
+ prefix = use_prefix
315
+ postprocessed = upscaled if upscaled else operation=='postprocess'
316
+ opt.prompt = gen.concept_lib().replace_triggers_with_concepts(opt.prompt or prompt_in) # to avoid the problem of non-unique concept triggers
317
+ filename, formatted_dream_prompt = prepare_image_metadata(
318
+ opt,
319
+ prefix,
320
+ seed,
321
+ operation,
322
+ prior_variations,
323
+ postprocessed,
324
+ first_seed
325
+ )
326
+ path = file_writer.save_image_and_prompt_to_png(
327
+ image = image,
328
+ dream_prompt = formatted_dream_prompt,
329
+ metadata = metadata_dumps(
330
+ opt,
331
+ seeds = [seed if opt.variation_amount==0 and len(prior_variations)==0 else first_seed],
332
+ model_hash = gen.model_hash,
333
+ ),
334
+ name = filename,
335
+ compress_level = opt.png_compression,
336
+ )
337
+
338
+ # update rfc metadata
339
+ if operation == 'postprocess':
340
+ tool = re.match('postprocess:(\w+)',opt.last_operation).groups()[0]
341
+ add_postprocessing_to_metadata(
342
+ opt,
343
+ opt.input_file_path,
344
+ filename,
345
+ tool,
346
+ formatted_dream_prompt,
347
+ )
348
+
349
+ if (not postprocessed) or opt.save_original:
350
+ # only append to results if we didn't overwrite an earlier output
351
+ results.append([path, formatted_dream_prompt])
352
+
353
+ # so that the seed autocompletes (on linux|mac when -S or --seed specified
354
+ if completer and operation == 'generate':
355
+ completer.add_seed(seed)
356
+ completer.add_seed(first_seed)
357
+ last_results.append([path, seed])
358
+
359
+ if operation == 'generate':
360
+ catch_ctrl_c = infile is None # if running interactively, we catch keyboard interrupts
361
+ opt.last_operation='generate'
362
+ try:
363
+ gen.prompt2image(
364
+ image_callback=image_writer,
365
+ step_callback=step_callback,
366
+ catch_interrupts=catch_ctrl_c,
367
+ **vars(opt)
368
+ )
369
+ except (PromptParser.ParsingException, pyparsing.ParseException) as e:
370
+ print('** An error occurred while processing your prompt **')
371
+ print(f'** {str(e)} **')
372
+ elif operation == 'postprocess':
373
+ print(f'>> fixing {opt.prompt}')
374
+ opt.last_operation = do_postprocess(gen,opt,image_writer)
375
+
376
+ elif operation == 'mask':
377
+ print(f'>> generating masks from {opt.prompt}')
378
+ do_textmask(gen, opt, image_writer)
379
+
380
+ if opt.grid and len(grid_images) > 0:
381
+ grid_img = make_grid(list(grid_images.values()))
382
+ grid_seeds = list(grid_images.keys())
383
+ first_seed = last_results[0][1]
384
+ filename = f'{prefix}.{first_seed}.png'
385
+ formatted_dream_prompt = opt.dream_prompt_str(seed=first_seed,grid=True,iterations=len(grid_images))
386
+ formatted_dream_prompt += f' # {grid_seeds}'
387
+ metadata = metadata_dumps(
388
+ opt,
389
+ seeds = grid_seeds,
390
+ model_hash = gen.model_hash
391
+ )
392
+ path = file_writer.save_image_and_prompt_to_png(
393
+ image = grid_img,
394
+ dream_prompt = formatted_dream_prompt,
395
+ metadata = metadata,
396
+ name = filename
397
+ )
398
+ results = [[path, formatted_dream_prompt]]
399
+
400
+ except AssertionError as e:
401
+ print(e)
402
+ continue
403
+
404
+ except OSError as e:
405
+ print(e)
406
+ continue
407
+
408
+ print('Outputs:')
409
+ log_path = os.path.join(current_outdir, 'invoke_log')
410
+ output_cntr = write_log(results, log_path ,('txt', 'md'), output_cntr)
411
+ print()
412
+
413
+ print('goodbye!')
414
+
415
+ # TO DO: remove repetitive code and the awkward command.replace() trope
416
+ # Just do a simple parse of the command!
417
+ def do_command(command:str, gen, opt:Args, completer) -> tuple:
418
+ global infile
419
+ operation = 'generate' # default operation, alternative is 'postprocess'
420
+
421
+ if command.startswith('!dream'): # in case a stored prompt still contains the !dream command
422
+ command = command.replace('!dream ','',1)
423
+
424
+ elif command.startswith('!fix'):
425
+ command = command.replace('!fix ','',1)
426
+ operation = 'postprocess'
427
+
428
+ elif command.startswith('!mask'):
429
+ command = command.replace('!mask ','',1)
430
+ operation = 'mask'
431
+
432
+ elif command.startswith('!switch'):
433
+ model_name = command.replace('!switch ','',1)
434
+ gen.set_model(model_name)
435
+ add_embedding_terms(gen, completer)
436
+ completer.add_history(command)
437
+ operation = None
438
+
439
+ elif command.startswith('!models'):
440
+ gen.model_cache.print_models()
441
+ completer.add_history(command)
442
+ operation = None
443
+
444
+ elif command.startswith('!import'):
445
+ path = shlex.split(command)
446
+ if len(path) < 2:
447
+ print('** please provide a path to a .ckpt or .vae model file')
448
+ elif not os.path.exists(path[1]):
449
+ print(f'** {path[1]}: file not found')
450
+ else:
451
+ add_weights_to_config(path[1], gen, opt, completer)
452
+ completer.add_history(command)
453
+ operation = None
454
+
455
+ elif command.startswith('!edit'):
456
+ path = shlex.split(command)
457
+ if len(path) < 2:
458
+ print('** please provide the name of a model')
459
+ else:
460
+ edit_config(path[1], gen, opt, completer)
461
+ completer.add_history(command)
462
+ operation = None
463
+
464
+ elif command.startswith('!del'):
465
+ path = shlex.split(command)
466
+ if len(path) < 2:
467
+ print('** please provide the name of a model')
468
+ else:
469
+ del_config(path[1], gen, opt, completer)
470
+ completer.add_history(command)
471
+ operation = None
472
+
473
+ elif command.startswith('!fetch'):
474
+ file_path = command.replace('!fetch','',1).strip()
475
+ retrieve_dream_command(opt,file_path,completer)
476
+ completer.add_history(command)
477
+ operation = None
478
+
479
+ elif command.startswith('!replay'):
480
+ file_path = command.replace('!replay','',1).strip()
481
+ if infile is None and os.path.isfile(file_path):
482
+ infile = open(file_path, 'r', encoding='utf-8')
483
+ completer.add_history(command)
484
+ operation = None
485
+
486
+ elif command.startswith('!history'):
487
+ completer.show_history()
488
+ operation = None
489
+
490
+ elif command.startswith('!search'):
491
+ search_str = command.replace('!search','',1).strip()
492
+ completer.show_history(search_str)
493
+ operation = None
494
+
495
+ elif command.startswith('!clear'):
496
+ completer.clear_history()
497
+ operation = None
498
+
499
+ elif re.match('^!(\d+)',command):
500
+ command_no = re.match('^!(\d+)',command).groups()[0]
501
+ command = completer.get_line(int(command_no))
502
+ completer.set_line(command)
503
+ operation = None
504
+
505
+ else: # not a recognized command, so give the --help text
506
+ command = '-h'
507
+ return command, operation
508
+
509
+ def set_default_output_dir(opt:Args, completer:Completer):
510
+ '''
511
+ If opt.outdir is relative, we add the root directory to it
512
+ normalize the outdir relative to root and make sure it exists.
513
+ '''
514
+ if not os.path.isabs(opt.outdir):
515
+ opt.outdir=os.path.normpath(os.path.join(Globals.root,opt.outdir))
516
+ if not os.path.exists(opt.outdir):
517
+ os.makedirs(opt.outdir)
518
+ completer.set_default_dir(opt.outdir)
519
+
520
+
521
+ def add_weights_to_config(model_path:str, gen, opt, completer):
522
+ print(f'>> Model import in process. Please enter the values needed to configure this model:')
523
+ print()
524
+
525
+ new_config = {}
526
+ new_config['weights'] = model_path
527
+
528
+ done = False
529
+ while not done:
530
+ model_name = input('Short name for this model: ')
531
+ if not re.match('^[\w._-]+$',model_name):
532
+ print('** model name must contain only words, digits and the characters [._-] **')
533
+ else:
534
+ done = True
535
+ new_config['description'] = input('Description of this model: ')
536
+
537
+ completer.complete_extensions(('.yaml','.yml'))
538
+ completer.linebuffer = 'configs/stable-diffusion/v1-inference.yaml'
539
+
540
+ done = False
541
+ while not done:
542
+ new_config['config'] = input('Configuration file for this model: ')
543
+ done = os.path.exists(new_config['config'])
544
+
545
+ done = False
546
+ completer.complete_extensions(('.vae.pt','.vae','.ckpt'))
547
+ while not done:
548
+ vae = input('VAE autoencoder file for this model [None]: ')
549
+ if os.path.exists(vae):
550
+ new_config['vae'] = vae
551
+ done = True
552
+ else:
553
+ done = len(vae)==0
554
+
555
+ completer.complete_extensions(None)
556
+
557
+ for field in ('width','height'):
558
+ done = False
559
+ while not done:
560
+ try:
561
+ completer.linebuffer = '512'
562
+ value = int(input(f'Default image {field}: '))
563
+ assert value >= 64 and value <= 2048
564
+ new_config[field] = value
565
+ done = True
566
+ except:
567
+ print('** Please enter a valid integer between 64 and 2048')
568
+
569
+ make_default = input('Make this the default model? [n] ') in ('y','Y')
570
+
571
+ if write_config_file(opt.conf, gen, model_name, new_config, make_default=make_default):
572
+ completer.add_model(model_name)
573
+
574
+ def del_config(model_name:str, gen, opt, completer):
575
+ current_model = gen.model_name
576
+ if model_name == current_model:
577
+ print("** Can't delete active model. !switch to another model first. **")
578
+ return
579
+ gen.model_cache.del_model(model_name)
580
+ gen.model_cache.commit(opt.conf)
581
+ print(f'** {model_name} deleted')
582
+ completer.del_model(model_name)
583
+
584
+ def edit_config(model_name:str, gen, opt, completer):
585
+ config = gen.model_cache.config
586
+
587
+ if model_name not in config:
588
+ print(f'** Unknown model {model_name}')
589
+ return
590
+
591
+ print(f'\n>> Editing model {model_name} from configuration file {opt.conf}')
592
+
593
+ conf = config[model_name]
594
+ new_config = {}
595
+ completer.complete_extensions(('.yaml','.yml','.ckpt','.vae.pt'))
596
+ for field in ('description', 'weights', 'vae', 'config', 'width','height'):
597
+ completer.linebuffer = str(conf[field]) if field in conf else ''
598
+ new_value = input(f'{field}: ')
599
+ new_config[field] = int(new_value) if field in ('width','height') else new_value
600
+ make_default = input('Make this the default model? [n] ') in ('y','Y')
601
+ completer.complete_extensions(None)
602
+ write_config_file(opt.conf, gen, model_name, new_config, clobber=True, make_default=make_default)
603
+
604
+ def write_config_file(conf_path, gen, model_name, new_config, clobber=False, make_default=False):
605
+ current_model = gen.model_name
606
+
607
+ op = 'modify' if clobber else 'import'
608
+ print('\n>> New configuration:')
609
+ if make_default:
610
+ new_config['default'] = True
611
+ print(yaml.dump({model_name:new_config}))
612
+ if input(f'OK to {op} [n]? ') not in ('y','Y'):
613
+ return False
614
+
615
+ try:
616
+ print('>> Verifying that new model loads...')
617
+ gen.model_cache.add_model(model_name, new_config, clobber)
618
+ assert gen.set_model(model_name) is not None, 'model failed to load'
619
+ except AssertionError as e:
620
+ print(f'** aborting **')
621
+ gen.model_cache.del_model(model_name)
622
+ return False
623
+
624
+ if make_default:
625
+ print('making this default')
626
+ gen.model_cache.set_default_model(model_name)
627
+
628
+ gen.model_cache.commit(conf_path)
629
+
630
+ do_switch = input(f'Keep model loaded? [y]')
631
+ if len(do_switch)==0 or do_switch[0] in ('y','Y'):
632
+ pass
633
+ else:
634
+ gen.set_model(current_model)
635
+ return True
636
+
637
+ def do_textmask(gen, opt, callback):
638
+ image_path = opt.prompt
639
+ if not os.path.exists(image_path):
640
+ image_path = os.path.join(opt.outdir,image_path)
641
+ assert os.path.exists(image_path), '** "{opt.prompt}" not found. Please enter the name of an existing image file to mask **'
642
+ assert opt.text_mask is not None and len(opt.text_mask) >= 1, '** Please provide a text mask with -tm **'
643
+ opt.input_file_path = image_path
644
+ tm = opt.text_mask[0]
645
+ threshold = float(opt.text_mask[1]) if len(opt.text_mask) > 1 else 0.5
646
+ gen.apply_textmask(
647
+ image_path = image_path,
648
+ prompt = tm,
649
+ threshold = threshold,
650
+ callback = callback,
651
+ )
652
+
653
+ def do_postprocess (gen, opt, callback):
654
+ file_path = opt.prompt # treat the prompt as the file pathname
655
+ if opt.new_prompt is not None:
656
+ opt.prompt = opt.new_prompt
657
+ else:
658
+ opt.prompt = None
659
+
660
+ if os.path.dirname(file_path) == '': #basename given
661
+ file_path = os.path.join(opt.outdir,file_path)
662
+
663
+ opt.input_file_path = file_path
664
+
665
+ tool=None
666
+ if opt.facetool_strength > 0:
667
+ tool = opt.facetool
668
+ elif opt.embiggen:
669
+ tool = 'embiggen'
670
+ elif opt.upscale:
671
+ tool = 'upscale'
672
+ elif opt.out_direction:
673
+ tool = 'outpaint'
674
+ elif opt.outcrop:
675
+ tool = 'outcrop'
676
+ opt.save_original = True # do not overwrite old image!
677
+ opt.last_operation = f'postprocess:{tool}'
678
+ try:
679
+ gen.apply_postprocessor(
680
+ image_path = file_path,
681
+ tool = tool,
682
+ facetool_strength = opt.facetool_strength,
683
+ codeformer_fidelity = opt.codeformer_fidelity,
684
+ save_original = opt.save_original,
685
+ upscale = opt.upscale,
686
+ out_direction = opt.out_direction,
687
+ outcrop = opt.outcrop,
688
+ callback = callback,
689
+ opt = opt,
690
+ )
691
+ except OSError:
692
+ print(traceback.format_exc(), file=sys.stderr)
693
+ print(f'** {file_path}: file could not be read')
694
+ return
695
+ except (KeyError, AttributeError):
696
+ print(traceback.format_exc(), file=sys.stderr)
697
+ return
698
+ return opt.last_operation
699
+
700
+ def add_postprocessing_to_metadata(opt,original_file,new_file,tool,command):
701
+ original_file = original_file if os.path.exists(original_file) else os.path.join(opt.outdir,original_file)
702
+ new_file = new_file if os.path.exists(new_file) else os.path.join(opt.outdir,new_file)
703
+ try:
704
+ meta = retrieve_metadata(original_file)['sd-metadata']
705
+ except AttributeError:
706
+ try:
707
+ meta = retrieve_metadata(new_file)['sd-metadata']
708
+ except AttributeError:
709
+ meta = {}
710
+
711
+ if 'image' not in meta:
712
+ meta = metadata_dumps(opt,seeds=[opt.seed])['image']
713
+ meta['image'] = {}
714
+ img_data = meta.get('image')
715
+ pp = img_data.get('postprocessing',[]) or []
716
+ pp.append(
717
+ {
718
+ 'tool':tool,
719
+ 'dream_command':command,
720
+ }
721
+ )
722
+ meta['image']['postprocessing'] = pp
723
+ write_metadata(new_file,meta)
724
+
725
+ def prepare_image_metadata(
726
+ opt,
727
+ prefix,
728
+ seed,
729
+ operation='generate',
730
+ prior_variations=[],
731
+ postprocessed=False,
732
+ first_seed=None
733
+ ):
734
+
735
+ if postprocessed and opt.save_original:
736
+ filename = choose_postprocess_name(opt,prefix,seed)
737
+ else:
738
+ wildcards = dict(opt.__dict__)
739
+ wildcards['prefix'] = prefix
740
+ wildcards['seed'] = seed
741
+ try:
742
+ filename = opt.fnformat.format(**wildcards)
743
+ except KeyError as e:
744
+ print(f'** The filename format contains an unknown key \'{e.args[0]}\'. Will use \'{{prefix}}.{{seed}}.png\' instead')
745
+ filename = f'{prefix}.{seed}.png'
746
+ except IndexError as e:
747
+ print(f'** The filename format is broken or complete. Will use \'{{prefix}}.{{seed}}.png\' instead')
748
+ filename = f'{prefix}.{seed}.png'
749
+
750
+ if opt.variation_amount > 0:
751
+ first_seed = first_seed or seed
752
+ this_variation = [[seed, opt.variation_amount]]
753
+ opt.with_variations = prior_variations + this_variation
754
+ formatted_dream_prompt = opt.dream_prompt_str(seed=first_seed)
755
+ elif len(prior_variations) > 0:
756
+ formatted_dream_prompt = opt.dream_prompt_str(seed=first_seed)
757
+ elif operation == 'postprocess':
758
+ formatted_dream_prompt = '!fix '+opt.dream_prompt_str(seed=seed,prompt=opt.input_file_path)
759
+ else:
760
+ formatted_dream_prompt = opt.dream_prompt_str(seed=seed)
761
+ return filename,formatted_dream_prompt
762
+
763
+ def choose_postprocess_name(opt,prefix,seed) -> str:
764
+ match = re.search('postprocess:(\w+)',opt.last_operation)
765
+ if match:
766
+ modifier = match.group(1) # will look like "gfpgan", "upscale", "outpaint" or "embiggen"
767
+ else:
768
+ modifier = 'postprocessed'
769
+
770
+ counter = 0
771
+ filename = None
772
+ available = False
773
+ while not available:
774
+ if counter == 0:
775
+ filename = f'{prefix}.{seed}.{modifier}.png'
776
+ else:
777
+ filename = f'{prefix}.{seed}.{modifier}-{counter:02d}.png'
778
+ available = not os.path.exists(os.path.join(opt.outdir,filename))
779
+ counter += 1
780
+ return filename
781
+
782
+ def get_next_command(infile=None) -> str: # command string
783
+ if infile is None:
784
+ command = input('invoke> ')
785
+ else:
786
+ command = infile.readline()
787
+ if not command:
788
+ raise EOFError
789
+ else:
790
+ command = command.strip()
791
+ if len(command)>0:
792
+ print(f'#{command}')
793
+ return command
794
+
795
+ def invoke_ai_web_server_loop(gen: Generate, gfpgan, codeformer, esrgan):
796
+ print('\n* --web was specified, starting web server...')
797
+ from backend.invoke_ai_web_server import InvokeAIWebServer
798
+ # Change working directory to the stable-diffusion directory
799
+ os.chdir(
800
+ os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
801
+ )
802
+
803
+ invoke_ai_web_server = InvokeAIWebServer(generate=gen, gfpgan=gfpgan, codeformer=codeformer, esrgan=esrgan)
804
+
805
+ try:
806
+ invoke_ai_web_server.run()
807
+ except KeyboardInterrupt:
808
+ pass
809
+
810
+ def add_embedding_terms(gen,completer):
811
+ '''
812
+ Called after setting the model, updates the autocompleter with
813
+ any terms loaded by the embedding manager.
814
+ '''
815
+ completer.add_embedding_terms(gen.model.embedding_manager.list_terms())
816
+
817
+ def split_variations(variations_string) -> list:
818
+ # shotgun parsing, woo
819
+ parts = []
820
+ broken = False # python doesn't have labeled loops...
821
+ for part in variations_string.split(','):
822
+ seed_and_weight = part.split(':')
823
+ if len(seed_and_weight) != 2:
824
+ print(f'** Could not parse with_variation part "{part}"')
825
+ broken = True
826
+ break
827
+ try:
828
+ seed = int(seed_and_weight[0])
829
+ weight = float(seed_and_weight[1])
830
+ except ValueError:
831
+ print(f'** Could not parse with_variation part "{part}"')
832
+ broken = True
833
+ break
834
+ parts.append([seed, weight])
835
+ if broken:
836
+ return None
837
+ elif len(parts) == 0:
838
+ return None
839
+ else:
840
+ return parts
841
+
842
+ def load_face_restoration(opt):
843
+ try:
844
+ gfpgan, codeformer, esrgan = None, None, None
845
+ if opt.restore or opt.esrgan:
846
+ from ldm.invoke.restoration import Restoration
847
+ restoration = Restoration()
848
+ if opt.restore:
849
+ gfpgan, codeformer = restoration.load_face_restore_models(opt.gfpgan_model_path)
850
+ else:
851
+ print('>> Face restoration disabled')
852
+ if opt.esrgan:
853
+ esrgan = restoration.load_esrgan(opt.esrgan_bg_tile)
854
+ else:
855
+ print('>> Upscaling disabled')
856
+ else:
857
+ print('>> Face restoration and upscaling disabled')
858
+ except (ModuleNotFoundError, ImportError):
859
+ print(traceback.format_exc(), file=sys.stderr)
860
+ print('>> You may need to install the ESRGAN and/or GFPGAN modules')
861
+ return gfpgan,codeformer,esrgan
862
+
863
+ def make_step_callback(gen, opt, prefix):
864
+ destination = os.path.join(opt.outdir,'intermediates',prefix)
865
+ os.makedirs(destination,exist_ok=True)
866
+ print(f'>> Intermediate images will be written into {destination}')
867
+ def callback(img, step):
868
+ if step % opt.save_intermediates == 0 or step == opt.steps-1:
869
+ filename = os.path.join(destination,f'{step:04}.png')
870
+ image = gen.sample_to_image(img)
871
+ image.save(filename,'PNG')
872
+ return callback
873
+
874
+ def retrieve_dream_command(opt,command,completer):
875
+ '''
876
+ Given a full or partial path to a previously-generated image file,
877
+ will retrieve and format the dream command used to generate the image,
878
+ and pop it into the readline buffer (linux, Mac), or print out a comment
879
+ for cut-and-paste (windows)
880
+
881
+ Given a wildcard path to a folder with image png files,
882
+ will retrieve and format the dream command used to generate the images,
883
+ and save them to a file commands.txt for further processing
884
+ '''
885
+ if len(command) == 0:
886
+ return
887
+
888
+ tokens = command.split()
889
+ dir,basename = os.path.split(tokens[0])
890
+ if len(dir) == 0:
891
+ path = os.path.join(opt.outdir,basename)
892
+ else:
893
+ path = tokens[0]
894
+
895
+ if len(tokens) > 1:
896
+ return write_commands(opt, path, tokens[1])
897
+
898
+ cmd = ''
899
+ try:
900
+ cmd = dream_cmd_from_png(path)
901
+ except OSError:
902
+ print(f'## {tokens[0]}: file could not be read')
903
+ except (KeyError, AttributeError, IndexError):
904
+ print(f'## {tokens[0]}: file has no metadata')
905
+ except:
906
+ print(f'## {tokens[0]}: file could not be processed')
907
+ if len(cmd)>0:
908
+ completer.set_line(cmd)
909
+
910
+ def write_commands(opt, file_path:str, outfilepath:str):
911
+ dir,basename = os.path.split(file_path)
912
+ try:
913
+ paths = sorted(list(Path(dir).glob(basename)))
914
+ except ValueError:
915
+ print(f'## "{basename}": unacceptable pattern')
916
+ return
917
+
918
+ commands = []
919
+ cmd = None
920
+ for path in paths:
921
+ try:
922
+ cmd = dream_cmd_from_png(path)
923
+ except (KeyError, AttributeError, IndexError):
924
+ print(f'## {path}: file has no metadata')
925
+ except:
926
+ print(f'## {path}: file could not be processed')
927
+ if cmd:
928
+ commands.append(f'# {path}')
929
+ commands.append(cmd)
930
+ if len(commands)>0:
931
+ dir,basename = os.path.split(outfilepath)
932
+ if len(dir)==0:
933
+ outfilepath = os.path.join(opt.outdir,basename)
934
+ with open(outfilepath, 'w', encoding='utf-8') as f:
935
+ f.write('\n'.join(commands))
936
+ print(f'>> File {outfilepath} with commands created')
937
+
938
+ def emergency_model_reconfigure(opt):
939
+ print()
940
+ print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
941
+ print(' You appear to have a missing or misconfigured model file(s). ')
942
+ print(' The script will now exit and run configure_invokeai.py to help fix the problem.')
943
+ print(' After reconfiguration is done, please relaunch invoke.py. ')
944
+ print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
945
+ print('configure_invokeai is launching....\n')
946
+
947
+ # Match arguments that were set on the CLI
948
+ # only the arguments accepted by the configuration script are parsed
949
+ root_dir = ["--root", opt.root_dir] if opt.root_dir is not None else []
950
+ config = ["--config", opt.conf] if opt.conf is not None else []
951
+ yes_to_all = os.environ.get('INVOKE_MODEL_RECONFIGURE')
952
+
953
+ sys.argv = [ 'configure_invokeai' ]
954
+ sys.argv.extend(root_dir)
955
+ sys.argv.extend(config)
956
+ if yes_to_all is not None:
957
+ sys.argv.append(yes_to_all)
958
+
959
+ import configure_invokeai
960
+ configure_invokeai.main()