shared.py 42.0 KB
Newer Older
D
d8ahazard 已提交
1
import datetime
2 3
import json
import os
D
d8ahazard 已提交
4
import sys
5
import threading
6
import time
D
d8ahazard 已提交
7

8
import gradio as gr
9
import tqdm
10

A
AUTOMATIC 已提交
11
import modules.interrogate
E
EyeDeck 已提交
12
import modules.memmon
D
d8ahazard 已提交
13
import modules.styles
B
brkirch 已提交
14
import modules.devices as devices
15
from modules import localization, script_loading, errors, ui_components, shared_items, cmd_args
A
AUTOMATIC 已提交
16
from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir  # noqa: F401
17
from ldm.models.diffusion.ddpm import LatentDiffusion
18 19 20

demo = None

21
parser = cmd_args.parser
22

23 24
script_loading.preload_extensions(extensions_dir, parser)
script_loading.preload_extensions(extensions_builtin_dir, parser)
25

A
AUTOMATIC 已提交
26 27 28 29
if os.environ.get('IGNORE_CMD_ARGS_ERRORS', None) is None:
    cmd_opts = parser.parse_args()
else:
    cmd_opts, _ = parser.parse_known_args()
D
d8ahazard 已提交
30

31

32
restricted_opts = {
33
    "samples_filename_pattern",
34
    "directories_filename_pattern",
35 36 37 38 39 40 41
    "outdir_samples",
    "outdir_txt2img_samples",
    "outdir_img2img_samples",
    "outdir_extras_samples",
    "outdir_grids",
    "outdir_txt2img_grids",
    "outdir_save",
42
    "outdir_init_images"
43
}
B
brkirch 已提交
44

45
ui_reorder_categories = [
A
AUTOMATIC 已提交
46
    "inpaint",
47
    "sampler",
48 49
    "checkboxes",
    "hires_fix",
50 51 52 53
    "dimensions",
    "cfg",
    "seed",
    "batch",
54
    "override_settings",
55
    "scripts",
56
]
B
brkirch 已提交
57

S
space-nuko 已提交
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
# https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/subdomains.json
gradio_hf_hub_themes = [
    "gradio/glass",
    "gradio/monochrome",
    "gradio/seafoam",
    "gradio/soft",
    "freddyaboulton/dracula_revamped",
    "gradio/dracula_test",
    "abidlabs/dracula_test",
    "abidlabs/pakistan",
    "dawood/microsoft_windows",
    "ysharma/steampunk"
]


73
cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or cmd_opts.server_name) and not cmd_opts.enable_insecure_extension_access
74

A
AUTOMATIC 已提交
75 76
devices.device, devices.device_interrogate, devices.device_gfpgan, devices.device_esrgan, devices.device_codeformer = \
    (devices.cpu if any(y in cmd_opts.use_cpu for y in [x, 'all']) else devices.get_optimal_device() for x in ['sd', 'interrogate', 'gfpgan', 'esrgan', 'codeformer'])
B
brkirch 已提交
77 78

device = devices.device
A
AUTOMATIC 已提交
79
weight_load_location = None if cmd_opts.lowram else "cpu"
A
Abdullah Barhoum 已提交
80

81
batch_cond_uncond = cmd_opts.always_batch_cond_uncond or not (cmd_opts.lowvram or cmd_opts.medvram)
82
parallel_processing_allowed = not cmd_opts.lowvram and not cmd_opts.medvram
83
xformers_available = False
84
config_filename = cmd_opts.ui_settings_file
A
AUTOMATIC 已提交
85

A
AUTOMATIC 已提交
86
os.makedirs(cmd_opts.hypernetwork_dir, exist_ok=True)
A
AUTOMATIC 已提交
87
hypernetworks = {}
A
AUTOMATIC 已提交
88
loaded_hypernetworks = []
A
AUTOMATIC 已提交
89

A
AUTOMATIC 已提交
90

A
AUTOMATIC 已提交
91
def reload_hypernetworks():
A
AUTOMATIC 已提交
92
    from modules.hypernetworks import hypernetwork
A
AUTOMATIC 已提交
93 94 95 96
    global hypernetworks

    hypernetworks = hypernetwork.list_hypernetworks(cmd_opts.hypernetwork_dir)

97

98
class State:
99
    skipped = False
100 101
    interrupted = False
    job = ""
A
AUTOMATIC 已提交
102 103
    job_no = 0
    job_count = 0
104
    processing_has_refined_job_count = False
D
DepFA 已提交
105
    job_timestamp = '0'
A
AUTOMATIC 已提交
106 107
    sampling_step = 0
    sampling_steps = 0
A
AUTOMATIC 已提交
108 109
    current_latent = None
    current_image = None
110
    current_image_sampling_step = 0
111
    id_live_preview = 0
112
    textinfo = None
113
    time_start = None
114
    server_start = None
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
    _server_command_signal = threading.Event()
    _server_command: str | None = None

    @property
    def need_restart(self) -> bool:
        # Compatibility getter for need_restart.
        return self.server_command == "restart"

    @need_restart.setter
    def need_restart(self, value: bool) -> None:
        # Compatibility setter for need_restart.
        if value:
            self.server_command = "restart"

    @property
    def server_command(self):
        return self._server_command

    @server_command.setter
    def server_command(self, value: str | None) -> None:
        """
        Set the server command to `value` and signal that it's been set.
        """
        self._server_command = value
        self._server_command_signal.set()

    def wait_for_server_command(self, timeout: float | None = None) -> str | None:
        """
        Wait for server command to get set; return and clear the value and signal.
        """
        if self._server_command_signal.wait(timeout):
            self._server_command_signal.clear()
            req = self._server_command
            self._server_command = None
            return req
        return None

    def request_restart(self) -> None:
        self.interrupt()
        self.server_command = True
155

156 157 158
    def skip(self):
        self.skipped = True

159 160 161
    def interrupt(self):
        self.interrupted = True

A
AUTOMATIC 已提交
162
    def nextjob(self):
163
        if opts.live_previews_enable and opts.show_progress_every_n_steps == -1:
164
            self.do_set_current_image()
E
evshiron 已提交
165

A
AUTOMATIC 已提交
166 167
        self.job_no += 1
        self.sampling_step = 0
168
        self.current_image_sampling_step = 0
D
update  
discus0434 已提交
169

E
evshiron 已提交
170
    def dict(self):
E
evshiron 已提交
171 172
        obj = {
            "skipped": self.skipped,
V
Vladimir Mandic 已提交
173
            "interrupted": self.interrupted,
E
evshiron 已提交
174 175
            "job": self.job,
            "job_count": self.job_count,
176
            "job_timestamp": self.job_timestamp,
E
evshiron 已提交
177 178 179 180 181
            "job_no": self.job_no,
            "sampling_step": self.sampling_step,
            "sampling_steps": self.sampling_steps,
        }

E
evshiron 已提交
182
        return obj
E
evshiron 已提交
183

184 185 186
    def begin(self):
        self.sampling_step = 0
        self.job_count = -1
187
        self.processing_has_refined_job_count = False
188 189 190 191 192
        self.job_no = 0
        self.job_timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
        self.current_latent = None
        self.current_image = None
        self.current_image_sampling_step = 0
193
        self.id_live_preview = 0
194 195 196
        self.skipped = False
        self.interrupted = False
        self.textinfo = None
197
        self.time_start = time.time()
198 199 200 201 202 203 204 205

        devices.torch_gc()

    def end(self):
        self.job = ""
        self.job_count = 0

        devices.torch_gc()
A
AUTOMATIC 已提交
206

A
AUTOMATIC 已提交
207
    def set_current_image(self):
208
        """sets self.current_image from self.current_latent if enough sampling steps have been made after the last call to this"""
209 210 211
        if not parallel_processing_allowed:
            return

212
        if self.sampling_step - self.current_image_sampling_step >= opts.show_progress_every_n_steps and opts.live_previews_enable and opts.show_progress_every_n_steps != -1:
213 214 215 216 217
            self.do_set_current_image()

    def do_set_current_image(self):
        if self.current_latent is None:
            return
E
evshiron 已提交
218

A
AUTOMATIC 已提交
219
        import modules.sd_samplers
220
        if opts.show_progress_grid:
221
            self.assign_current_image(modules.sd_samplers.samples_to_image_grid(self.current_latent))
222
        else:
223
            self.assign_current_image(modules.sd_samplers.sample_to_image(self.current_latent))
A
AUTOMATIC 已提交
224

225
        self.current_image_sampling_step = self.sampling_step
A
AUTOMATIC 已提交
226

227 228 229 230
    def assign_current_image(self, image):
        self.current_image = image
        self.id_live_preview += 1

A
AUTOMATIC 已提交
231

232
state = State()
233
state.server_start = time.time()
234

235
styles_filename = cmd_opts.styles_file
A
AUTOMATIC 已提交
236
prompt_styles = modules.styles.StyleDatabase(styles_filename)
237

A
AUTOMATIC 已提交
238 239
interrogator = modules.interrogate.InterrogateModels("interrogate")

A
AUTOMATIC 已提交
240
face_restorers = []
241

242

243
class OptionInfo:
244
    def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after=''):
245 246 247 248 249
        self.default = default
        self.label = label
        self.component = component
        self.component_args = component_args
        self.onchange = onchange
250
        self.section = section
251
        self.refresh = refresh
252

253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
        self.comment_before = comment_before
        """HTML text that will be added after label in UI"""

        self.comment_after = comment_after
        """HTML text that will be added before label in UI"""

    def link(self, label, url):
        self.comment_before += f"[<a href='{url}' target='_blank'>{label}</a>]"
        return self

    def js(self, label, js_func):
        self.comment_before += f"[<a onclick='{js_func}(); return false'>{label}</a>]"
        return self

    def info(self, info):
        self.comment_after += f"<span class='info'>({info})</span>"
        return self

271 272 273 274 275 276
    def needs_restart(self):
        self.comment_after += " <span class='info'>(requires restart)</span>"
        return self



277

A
Aidan Holland 已提交
278
def options_section(section_identifier, options_dict):
A
AUTOMATIC 已提交
279
    for v in options_dict.values():
A
Aidan Holland 已提交
280
        v.section = section_identifier
281 282 283

    return options_dict

E
Eyrie 已提交
284

A
AUTOMATIC 已提交
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
def list_checkpoint_tiles():
    import modules.sd_models
    return modules.sd_models.checkpoint_tiles()


def refresh_checkpoints():
    import modules.sd_models
    return modules.sd_models.list_models()


def list_samplers():
    import modules.sd_samplers
    return modules.sd_samplers.all_samplers


300
hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config}
301
tab_names = []
302 303 304

options_templates = {}

305 306 307
options_templates.update(options_section(('saving-images', "Saving images/grids"), {
    "samples_save": OptionInfo(True, "Always save all generated images"),
    "samples_format": OptionInfo('png', 'File format for images'),
308
    "samples_filename_pattern": OptionInfo("", "Images filename pattern", component_args=hide_dirs).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Images-Filename-Name-and-Subdirectory"),
309
    "save_images_add_number": OptionInfo(True, "Add number to filename when saving", component_args=hide_dirs),
310 311 312 313 314

    "grid_save": OptionInfo(True, "Always save all generated image grids"),
    "grid_format": OptionInfo('png', 'File format for grids'),
    "grid_extended_filename": OptionInfo(False, "Add extended info (seed, prompt) to filename when saving grid"),
    "grid_only_if_multiple": OptionInfo(True, "Do not save grids consisting of one picture"),
315
    "grid_prevent_empty_spots": OptionInfo(False, "Prevent empty spots in grid (when set to autodetect)"),
316 317
    "n_rows": OptionInfo(-1, "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}),

318 319 320
    "enable_pnginfo": OptionInfo(True, "Save text information about generation parameters as chunks to png files"),
    "save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters."),
    "save_images_before_face_restoration": OptionInfo(False, "Save a copy of image before doing face restoration."),
321
    "save_images_before_highres_fix": OptionInfo(False, "Save a copy of image before applying highres fix."),
322
    "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"),
323 324
    "save_mask": OptionInfo(False, "For inpainting, save a copy of the greyscale mask"),
    "save_mask_composite": OptionInfo(False, "For inpainting, save a masked composite"),
325
    "jpeg_quality": OptionInfo(80, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}),
M
missionfloyd 已提交
326
    "webp_lossless": OptionInfo(False, "Use lossless compression for webp images"),
327
    "export_for_4chan": OptionInfo(True, "Save copy of large images as JPG").info("if the file size is above the limit, or either width or height are above the limit"),
328 329
    "img_downscale_threshold": OptionInfo(4.0, "File size limit for the above option, MB", gr.Number),
    "target_side_length": OptionInfo(4000, "Width/height limit for the above option, in pixels", gr.Number),
330
    "img_max_size_mp": OptionInfo(200, "Maximum image size", gr.Number).info("in megapixels"),
331

332
    "use_original_name_batch": OptionInfo(True, "Use original name for output filename during batch process in extras tab"),
333
    "use_upscaler_name_as_suffix": OptionInfo(False, "Use upscaler name as filename suffix in the extras tab"),
334
    "save_selected_only": OptionInfo(True, "When using 'Save' button, only save a single selected image"),
F
forsurefr 已提交
335
    "save_init_img": OptionInfo(False, "Save init images when using img2img"),
336 337 338 339

    "temp_dir":  OptionInfo("", "Directory for temporary images; leave empty for default"),
    "clean_temp_dir_at_start": OptionInfo(False, "Cleanup non-default temporary directory when starting webui"),

340 341
}))

342 343
options_templates.update(options_section(('saving-paths', "Paths for saving"), {
    "outdir_samples": OptionInfo("", "Output directory for images; if empty, defaults to three directories below", component_args=hide_dirs),
344 345 346 347 348 349 350
    "outdir_txt2img_samples": OptionInfo("outputs/txt2img-images", 'Output directory for txt2img images', component_args=hide_dirs),
    "outdir_img2img_samples": OptionInfo("outputs/img2img-images", 'Output directory for img2img images', component_args=hide_dirs),
    "outdir_extras_samples": OptionInfo("outputs/extras-images", 'Output directory for images from extras tab', component_args=hide_dirs),
    "outdir_grids": OptionInfo("", "Output directory for grids; if empty, defaults to two directories below", component_args=hide_dirs),
    "outdir_txt2img_grids": OptionInfo("outputs/txt2img-grids", 'Output directory for txt2img grids', component_args=hide_dirs),
    "outdir_img2img_grids": OptionInfo("outputs/img2img-grids", 'Output directory for img2img grids', component_args=hide_dirs),
    "outdir_save": OptionInfo("log/images", "Directory for saving images using the Save button", component_args=hide_dirs),
351
    "outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs),
352 353
}))

354
options_templates.update(options_section(('saving-to-dirs', "Saving to a directory"), {
355 356
    "save_to_dirs": OptionInfo(True, "Save images to a subdirectory"),
    "grid_save_to_dirs": OptionInfo(True, "Save grids to a subdirectory"),
A
AUTOMATIC 已提交
357
    "use_save_to_dirs_for_ui": OptionInfo(False, "When using \"Save\" button, save images to a subdirectory"),
358
    "directories_filename_pattern": OptionInfo("[date]", "Directory name pattern", component_args=hide_dirs).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Images-Filename-Name-and-Subdirectory"),
359
    "directories_max_prompt_words": OptionInfo(8, "Max prompt words for [prompt_words] pattern", gr.Slider, {"minimum": 1, "maximum": 20, "step": 1, **hide_dirs}),
360 361 362
}))

options_templates.update(options_section(('upscaling', "Upscaling"), {
363 364 365
    "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"),
    "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"),
    "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Select which Real-ESRGAN models to show in the web UI.", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}),
366
    "upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}),
367 368 369
}))

options_templates.update(options_section(('face-restoration', "Face restoration"), {
M
missionfloyd 已提交
370
    "face_restoration_model": OptionInfo("CodeFormer", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}),
371
    "code_former_weight": OptionInfo(0.5, "CodeFormer weight", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}).info("0 = maximum effect; 1 = minimum effect"),
372
    "face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"),
373 374
}))

375
options_templates.update(options_section(('system', "System"), {
A
AUTOMATIC 已提交
376
    "show_warnings": OptionInfo(False, "Show warnings in console."),
377
    "memmon_poll_rate": OptionInfo(8, "VRAM usage polls per second during generation.", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}).info("0 = disable"),
378
    "samples_log_stdout": OptionInfo(False, "Always print all generation info to standard output"),
J
Justin Riddiough 已提交
379
    "multiple_tqdm": OptionInfo(True, "Add a second progress bar to the console that shows progress for an entire job."),
380
    "print_hypernet_extra": OptionInfo(False, "Print extra hypernetwork information to console."),
381 382
}))

383
options_templates.update(options_section(('training', "Training"), {
F
Fampai 已提交
384
    "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible. Saves VRAM."),
385
    "pin_memory": OptionInfo(False, "Turn on pin_memory for DataLoader. Makes training slightly faster but can increase memory usage."),
S
Shondoit 已提交
386
    "save_optimizer_state": OptionInfo(False, "Saves Optimizer state as separate *.optim file. Training of embedding or HN can be resumed with the matching optim file."),
387
    "save_training_settings_to_txt": OptionInfo(True, "Save textual inversion and hypernet settings to a text file whenever training starts."),
388 389
    "dataset_filename_word_regex": OptionInfo("", "Filename word regex"),
    "dataset_filename_join_string": OptionInfo(" ", "Filename join string"),
390 391
    "training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}),
    "training_write_csv_every": OptionInfo(500, "Save an csv containing the loss to log directory every N steps, 0 to disable"),
F
Fampai 已提交
392
    "training_xattention_optimizations": OptionInfo(False, "Use cross attention optimizations while training"),
393 394 395
    "training_enable_tensorboard": OptionInfo(False, "Enable tensorboard logging."),
    "training_tensorboard_save_images": OptionInfo(False, "Save generated images within tensorboard."),
    "training_tensorboard_flush_every": OptionInfo(120, "How often, in seconds, to flush the pending tensorboard events and summaries to disk."),
396 397
}))

398
options_templates.update(options_section(('sd', "Stable Diffusion"), {
A
AUTOMATIC 已提交
399
    "sd_model_checkpoint": OptionInfo(None, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints),
400
    "sd_checkpoint_cache": OptionInfo(0, "Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}),
H
hitomi 已提交
401
    "sd_vae_checkpoint_cache": OptionInfo(0, "VAE Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}),
402
    "sd_vae": OptionInfo("Automatic", "SD VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list).info("choose VAE model: Automatic = use one with same filename as checkpoint; None = use VAE from checkpoint"),
403
    "sd_vae_as_default": OptionInfo(True, "Ignore selected VAE for stable diffusion checkpoints that have their own .vae.pt next to them"),
R
random_thoughtss 已提交
404
    "inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}),
405
    "initial_noise_multiplier": OptionInfo(1.0, "Noise multiplier for img2img", gr.Slider, {"minimum": 0.5, "maximum": 1.5, "step": 0.01}),
406
    "img2img_color_correction": OptionInfo(False, "Apply color correction to img2img results to match original colors."),
407
    "img2img_fix_steps": OptionInfo(False, "With img2img, do exactly the amount of steps the slider specifies.").info("normally you'd do less with less denoising"),
408
    "img2img_background_color": OptionInfo("#ffffff", "With img2img, fill image's transparent parts with this color.", ui_components.FormColorPicker, {}),
409
    "enable_quantization": OptionInfo(False, "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply."),
410
    "enable_emphasis": OptionInfo(True, "Enable emphasis").info("use (text) to make model pay more attention to text and [text] to make it pay less attention"),
411
    "enable_batch_seeds": OptionInfo(True, "Make K-diffusion samplers produce same images in a batch as when making a single image"),
412 413
    "comma_padding_backtrack": OptionInfo(20, "Prompt word wrap length limit", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1}).info("in tokens - for texts shorter than specified, if they don't fit into 75 token limit, move them to the next 75 token chunk"),
    "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#clip-skip").info("ignore last layers of CLIP nrtwork; 1 ignores none, 2 ignores one layer"),
414
    "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"),
415
    "randn_source": OptionInfo("GPU", "Random number generator source.", gr.Radio, {"choices": ["GPU", "CPU"]}).info("changes seeds drastically; use CPU to produce the same picture across different vidocard vendors"),
416 417 418 419
}))

options_templates.update(options_section(('optimizations', "Optimizations"), {
    "s_min_uncond": OptionInfo(0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"),
420
    "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9256").info("0=disable, higher=faster"),
421 422
    "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).info("only applies if non-zero and overrides above"),
    "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for high-res pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).info("only applies if non-zero and overrides above"),
423 424
}))

425 426 427
options_templates.update(options_section(('compatibility', "Compatibility"), {
    "use_old_emphasis_implementation": OptionInfo(False, "Use old emphasis implementation. Can be useful to reproduce old seeds."),
    "use_old_karras_scheduler_sigmas": OptionInfo(False, "Use old karras scheduler sigmas (0.1 to 10)."),
428
    "no_dpmpp_sde_batch_determinism": OptionInfo(False, "Do not make DPM++ SDE deterministic across different batch sizes."),
429
    "use_old_hires_fix_width_height": OptionInfo(False, "For hires fix, use width/height sliders to set final resolution rather than first pass (disables Upscale by, Resize width/height to)."),
C
catboxanon 已提交
430
    "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers."),
431 432
}))

433
options_templates.update(options_section(('interrogate', "Interrogate Options"), {
434 435 436 437 438 439
    "interrogate_keep_models_in_memory": OptionInfo(False, "Keep models in VRAM"),
    "interrogate_return_ranks": OptionInfo(False, "Include ranks of model tags matches in results.").info("booru only"),
    "interrogate_clip_num_beams": OptionInfo(1, "BLIP: num_beams", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}),
    "interrogate_clip_min_length": OptionInfo(24, "BLIP: minimum description length", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}),
    "interrogate_clip_max_length": OptionInfo(48, "BLIP: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}),
    "interrogate_clip_dict_limit": OptionInfo(1500, "CLIP: maximum number of lines in text file").info("0 = No limit"),
440
    "interrogate_clip_skip_categories": OptionInfo([], "CLIP: skip inquire categories", gr.CheckboxGroup, lambda: {"choices": modules.interrogate.category_types()}, refresh=modules.interrogate.category_types),
441 442 443 444 445
    "interrogate_deepbooru_score_threshold": OptionInfo(0.5, "deepbooru: score threshold", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}),
    "deepbooru_sort_alpha": OptionInfo(True, "deepbooru: sort tags alphabetically").info("if not: sort by score"),
    "deepbooru_use_spaces": OptionInfo(True, "deepbooru: use spaces in tags").info("if not: use underscores"),
    "deepbooru_escape": OptionInfo(True, "deepbooru: escape (\\) brackets").info("so they are used as literal brackets and not for emphasis"),
    "deepbooru_filter_tags": OptionInfo("", "deepbooru: filter out those tags").info("separate by comma"),
446
}))
447

448
options_templates.update(options_section(('extra_networks', "Extra Networks"), {
449 450
    "extra_networks_default_view": OptionInfo("cards", "Default view for Extra Networks", gr.Dropdown, {"choices": ["cards", "thumbs"]}),
    "extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}),
451 452 453
    "extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks").info("in pixels"),
    "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks").info("in pixels"),
    "extra_networks_add_text_separator": OptionInfo(" ", "Extra networks separator").info("extra text to add before <...> when adding extra network to prompt"),
A
AUTOMATIC 已提交
454
    "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None", *hypernetworks]}, refresh=reload_hypernetworks),
455 456
}))

457
options_templates.update(options_section(('ui', "User interface"), {
458 459
    "localization": OptionInfo("None", "Localization", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)).needs_restart(),
    "gradio_theme": OptionInfo("Default", "Gradio theme", ui_components.DropdownEditable, lambda: {"choices": ["Default"] + gradio_hf_hub_themes}).needs_restart(),
460
    "return_grid": OptionInfo(True, "Show grid in results for web"),
461 462
    "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results for web"),
    "return_mask_composite": OptionInfo(False, "For inpainting, include masked composite in results for web"),
A
AUTOMATIC 已提交
463
    "do_not_show_images": OptionInfo(False, "Do not show any images in results for web"),
464
    "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"),
465
    "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"),
466 467
    "font": OptionInfo("", "Font for image grids that have text"),
    "js_modal_lightbox": OptionInfo(True, "Enable full page image viewer"),
A
Aidan Holland 已提交
468
    "js_modal_lightbox_initially_zoomed": OptionInfo(True, "Show images zoomed in by default in full page image viewer"),
M
missionfloyd 已提交
469
    "js_modal_lightbox_gamepad": OptionInfo(True, "Navigate image viewer with gamepad"),
M
missionfloyd 已提交
470
    "js_modal_lightbox_gamepad_repeat": OptionInfo(250, "Gamepad repeat period, in milliseconds"),
D
DepFA 已提交
471
    "show_progress_in_title": OptionInfo(True, "Show generation progress in window title."),
472 473
    "samplers_in_dropdown": OptionInfo(True, "Use dropdown for sampler selection instead of radio group").needs_restart(),
    "dimensions_and_batch_together": OptionInfo(True, "Show Width/Height and Batch sliders in same row").needs_restart(),
A
AUTOMATIC 已提交
474 475
    "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}),
    "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing <extra networks:0.9>", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}),
476
    "keyedit_delimiters": OptionInfo(".,\\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"),
477 478
    "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that appear at the top of page rather than in settings tab").needs_restart(),
    "hidden_tabs": OptionInfo([], "Hidden UI tabs", ui_components.DropdownMulti, lambda: {"choices": list(tab_names)}).needs_restart(),
A
AUTOMATIC 已提交
479
    "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"),
480
    "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order").needs_restart(),
481 482
}))

483 484 485 486 487 488 489
options_templates.update(options_section(('infotext', "Infotext"), {
    "add_model_hash_to_info": OptionInfo(True, "Add model hash to generation information"),
    "add_model_name_to_info": OptionInfo(True, "Add model name to generation information"),
    "add_version_to_infotext": OptionInfo(True, "Add program version to generation information"),
    "disable_weights_auto_swap": OptionInfo(True, "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint."),
}))

490
options_templates.update(options_section(('ui', "Live previews"), {
A
AUTOMATIC 已提交
491
    "show_progressbar": OptionInfo(True, "Show progressbar"),
492
    "live_previews_enable": OptionInfo(True, "Show live previews of the created image"),
493
    "live_previews_image_format": OptionInfo("png", "Live preview file format", gr.Radio, {"choices": ["jpeg", "png", "webp"]}),
494
    "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"),
495
    "show_progress_every_n_steps": OptionInfo(10, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}).info("in sampling steps - show new live preview image every N sampling steps; -1 = only show after completion of batch"),
A
AUTOMATIC1111 已提交
496
    "show_progress_type": OptionInfo("Approx NN", "Live preview method", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap", "TAESD"]}).info("Full = slow but pretty; Approx NN and TAESD = fast but low quality; Approx cheap = super fast but terrible otherwise"),
497
    "live_preview_content": OptionInfo("Prompt", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}),
498
    "live_preview_refresh_period": OptionInfo(1000, "Progressbar and preview update period").info("in milliseconds"),
499 500
}))

501
options_templates.update(options_section(('sampler-params', "Sampler parameters"), {
502 503 504
    "hide_samplers": OptionInfo([], "Hide samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in list_samplers()]}).needs_restart(),
    "eta_ddim": OptionInfo(0.0, "Eta for DDIM", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}).info("noise multiplier; higher = more unperdictable results"),
    "eta_ancestral": OptionInfo(1.0, "Eta for ancestral samplers", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}).info("noise multiplier; applies to Euler a and other samplers that have a in them"),
505 506 507 508
    "ddim_discretize": OptionInfo('uniform', "img2img DDIM discretize", gr.Radio, {"choices": ['uniform', 'quad']}),
    's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}),
    's_tmin':  OptionInfo(0.0, "sigma tmin",  gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}),
    's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}),
509 510
    'eta_noise_seed_delta': OptionInfo(0, "Eta noise seed delta", gr.Number, {"precision": 0}).info("ENSD; does not improve anything, just produces different results for ancestral samplers - only useful for reproducing images"),
    'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma").link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/6044"),
S
space-nuko 已提交
511
    'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "bh2", "vary_coeff"]}),
S
space-nuko 已提交
512
    'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}),
513
    'uni_pc_order': OptionInfo(3, "UniPC order", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}).info("must be < sampling steps"),
S
space-nuko 已提交
514
    'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final"),
515
}))
516

517
options_templates.update(options_section(('postprocessing', "Postprocessing"), {
518 519
    'postprocessing_enable_in_main_ui': OptionInfo([], "Enable postprocessing operations in txt2img and img2img tabs", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}),
    'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}),
520 521 522
    'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}),
}))

523
options_templates.update(options_section((None, "Hidden options"), {
524
    "disabled_extensions": OptionInfo([], "Disable these extensions"),
525
    "disable_all_extensions": OptionInfo("none", "Disable all extensions (preserves the list of disabled extensions)", gr.Radio, {"choices": ["none", "extra", "all"]}),
526
    "restore_config_state_file": OptionInfo("", "Config state file to restore from, under 'config-states/' folder"),
527
    "sd_checkpoint_hash": OptionInfo("", "SHA256 hash of the current checkpoint"),
528 529
}))

530

531 532
options_templates.update()

533

534
class Options:
535
    data = None
536
    data_labels = options_templates
537
    typemap = {int: float}
538 539 540 541 542 543

    def __init__(self):
        self.data = {k: v.default for k, v in self.data_labels.items()}

    def __setattr__(self, key, value):
        if self.data is not None:
544
            if key in self.data or key in self.data_labels:
545 546
                assert not cmd_opts.freeze_settings, "changing settings is disabled"

547 548
                info = opts.data_labels.get(key, None)
                comp_args = info.component_args if info else None
549 550 551 552 553 554
                if isinstance(comp_args, dict) and comp_args.get('visible', True) is False:
                    raise RuntimeError(f"not possible to set {key} because it is restricted")

                if cmd_opts.hide_ui_dir_config and key in restricted_opts:
                    raise RuntimeError(f"not possible to set {key} because it is restricted")

555
                self.data[key] = value
556
                return
557 558 559 560 561 562 563 564 565 566 567 568 569

        return super(Options, self).__setattr__(key, value)

    def __getattr__(self, item):
        if self.data is not None:
            if item in self.data:
                return self.data[item]

        if item in self.data_labels:
            return self.data_labels[item].default

        return super(Options, self).__getattribute__(item)

570 571 572 573 574 575 576 577 578 579 580 581 582
    def set(self, key, value):
        """sets an option and calls its onchange callback, returning True if the option changed and False otherwise"""

        oldval = self.data.get(key, None)
        if oldval == value:
            return False

        try:
            setattr(self, key, value)
        except RuntimeError:
            return False

        if self.data_labels[key].onchange is not None:
583 584 585 586 587 588
            try:
                self.data_labels[key].onchange()
            except Exception as e:
                errors.display(e, f"changing setting {key} to {value}")
                setattr(self, key, oldval)
                return False
589 590 591

        return True

A
AUTOMATIC 已提交
592 593 594 595 596 597 598 599 600
    def get_default(self, key):
        """returns the default value for the key"""

        data_label = self.data_labels.get(key)
        if data_label is None:
            return None

        return data_label.default

601
    def save(self, filename):
602 603
        assert not cmd_opts.freeze_settings, "saving settings is disabled"

604
        with open(filename, "w", encoding="utf8") as file:
W
w-e-w 已提交
605
            json.dump(self.data, file, indent=4)
606

607 608 609
    def same_type(self, x, y):
        if x is None or y is None:
            return True
610

611 612
        type_x = self.typemap.get(type(x), type(x))
        type_y = self.typemap.get(type(y), type(y))
613

614
        return type_x == type_y
615

616 617 618
    def load(self, filename):
        with open(filename, "r", encoding="utf8") as file:
            self.data = json.load(file)
619

W
w-e-w 已提交
620 621 622 623
        # 1.1.1 quicksettings list migration
        if self.data.get('quicksettings') is not None and self.data.get('quicksettings_list') is None:
            self.data['quicksettings_list'] = [i.strip() for i in self.data.get('quicksettings').split(',')]

624 625 626
        bad_settings = 0
        for k, v in self.data.items():
            info = self.data_labels.get(k, None)
627
            if info is not None and not self.same_type(info.default, v):
628 629 630 631 632 633
                print(f"Warning: bad setting value: {k}: {v} ({type(v).__name__}; expected {type(info.default).__name__})", file=sys.stderr)
                bad_settings += 1

        if bad_settings > 0:
            print(f"The program is likely to not work with bad settings.\nSettings file: {filename}\nEither fix the file, or delete it and restart.", file=sys.stderr)

M
Muhammad Rizqi Nur 已提交
634
    def onchange(self, key, func, call=True):
635 636 637
        item = self.data_labels.get(key)
        item.onchange = func

M
Muhammad Rizqi Nur 已提交
638 639
        if call:
            func()
A
AUTOMATIC 已提交
640

641
    def dumpjson(self):
642 643 644
        d = {k: self.data.get(k, v.default) for k, v in self.data_labels.items()}
        d["_comments_before"] = {k: v.comment_before for k, v in self.data_labels.items() if v.comment_before is not None}
        d["_comments_after"] = {k: v.comment_after for k, v in self.data_labels.items() if v.comment_after is not None}
645 646
        return json.dumps(d)

647 648 649 650 651 652 653 654
    def add_option(self, key, info):
        self.data_labels[key] = info

    def reorder(self):
        """reorder settings so that all items related to section always go together"""

        section_ids = {}
        settings_items = self.data_labels.items()
A
AUTOMATIC 已提交
655
        for _, item in settings_items:
656 657 658
            if item.section not in section_ids:
                section_ids[item.section] = len(section_ids)

A
AUTOMATIC 已提交
659
        self.data_labels = dict(sorted(settings_items, key=lambda x: section_ids[x[1].section]))
660

661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
    def cast_value(self, key, value):
        """casts an arbitrary to the same type as this setting's value with key
        Example: cast_value("eta_noise_seed_delta", "12") -> returns 12 (an int rather than str)
        """

        if value is None:
            return None

        default_value = self.data_labels[key].default
        if default_value is None:
            default_value = getattr(self, key, None)
        if default_value is None:
            return None

        expected_type = type(default_value)
        if expected_type == bool and value == "False":
            value = False
        else:
            value = expected_type(value)

        return value


684 685 686 687
opts = Options()
if os.path.exists(config_filename):
    opts.load(config_filename)

688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712

class Shared(sys.modules[__name__].__class__):
    """
    this class is here to provide sd_model field as a property, so that it can be created and loaded on demand rather than
    at program startup.
    """

    sd_model_val = None

    @property
    def sd_model(self):
        import modules.sd_models

        return modules.sd_models.model_data.get_sd_model()

    @sd_model.setter
    def sd_model(self, value):
        import modules.sd_models

        modules.sd_models.model_data.set_sd_model(value)


sd_model: LatentDiffusion = None  # this var is here just for IDE's type checking; it cannot be accessed because the class field above will be accessed instead
sys.modules[__name__].__class__ = Shared

713
settings_components = None
714
"""assinged from ui.py, a mapping on setting names to gradio components repsponsible for those settings"""
715

A
AUTOMATIC 已提交
716 717
latent_upscale_default_mode = "Latent"
latent_upscale_modes = {
M
MMaker 已提交
718 719 720
    "Latent": {"mode": "bilinear", "antialias": False},
    "Latent (antialiased)": {"mode": "bilinear", "antialias": True},
    "Latent (bicubic)": {"mode": "bicubic", "antialias": False},
M
MMaker 已提交
721
    "Latent (bicubic antialiased)": {"mode": "bicubic", "antialias": True},
M
MMaker 已提交
722
    "Latent (nearest)": {"mode": "nearest", "antialias": False},
723
    "Latent (nearest-exact)": {"mode": "nearest-exact", "antialias": False},
A
AUTOMATIC 已提交
724 725
}

A
AUTOMATIC 已提交
726
sd_upscalers = []
727

M
MalumaDev 已提交
728
clip_model = None
A
AUTOMATIC 已提交
729

730
progress_print_out = sys.stdout
A
AUTOMATIC 已提交
731

S
space-nuko 已提交
732 733 734 735 736 737 738 739
gradio_theme = gr.themes.Base()


def reload_gradio_theme(theme_name=None):
    global gradio_theme
    if not theme_name:
        theme_name = opts.gradio_theme

C
catboxanon 已提交
740 741 742 743 744
    default_theme_args = dict(
        font=["Source Sans Pro", 'ui-sans-serif', 'system-ui', 'sans-serif'],
        font_mono=['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'],
    )

S
space-nuko 已提交
745
    if theme_name == "Default":
C
catboxanon 已提交
746
        gradio_theme = gr.themes.Default(**default_theme_args)
S
space-nuko 已提交
747 748 749
    else:
        try:
            gradio_theme = gr.themes.ThemeClass.from_hub(theme_name)
750 751
        except Exception as e:
            errors.display(e, "changing gradio theme")
C
catboxanon 已提交
752
            gradio_theme = gr.themes.Default(**default_theme_args)
S
space-nuko 已提交
753 754


755 756 757 758 759 760 761 762 763 764 765 766 767 768

class TotalTQDM:
    def __init__(self):
        self._tqdm = None

    def reset(self):
        self._tqdm = tqdm.tqdm(
            desc="Total progress",
            total=state.job_count * state.sampling_steps,
            position=1,
            file=progress_print_out
        )

    def update(self):
769
        if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars:
770 771 772 773 774
            return
        if self._tqdm is None:
            self.reset()
        self._tqdm.update()

775
    def updateTotal(self, new_total):
776
        if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars:
777 778 779
            return
        if self._tqdm is None:
            self.reset()
780
        self._tqdm.total = new_total
781

782 783
    def clear(self):
        if self._tqdm is not None:
784
            self._tqdm.refresh()
785 786 787 788 789
            self._tqdm.close()
            self._tqdm = None


total_tqdm = TotalTQDM()
E
EyeDeck 已提交
790 791 792

mem_mon = modules.memmon.MemUsageMonitor("MemMon", device, opts)
mem_mon.start()
793 794 795


def listfiles(dirname):
B
bluelovers 已提交
796
    filenames = [os.path.join(dirname, x) for x in sorted(os.listdir(dirname), key=str.lower) if not x.startswith(".")]
797
    return [file for file in filenames if os.path.isfile(file)]
A
AUTOMATIC 已提交
798 799 800 801 802 803 804 805 806 807 808 809 810 811


def html_path(filename):
    return os.path.join(script_path, "html", filename)


def html(filename):
    path = html_path(filename)

    if os.path.exists(path):
        with open(path, encoding="utf8") as file:
            return file.read()

    return ""
812 813 814 815 816 817 818 819 820


def walk_files(path, allowed_extensions=None):
    if not os.path.exists(path):
        return

    if allowed_extensions is not None:
        allowed_extensions = set(allowed_extensions)

C
catboxanon 已提交
821
    for root, _, files in os.walk(path, followlinks=True):
822 823 824 825 826 827 828
        for filename in files:
            if allowed_extensions is not None:
                _, ext = os.path.splitext(filename)
                if ext not in allowed_extensions:
                    continue

            yield os.path.join(root, filename)