diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e5765563..47ffc4781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Change Log for SD.Next +## Update for 2023-10-18 + +Service release addressing all zero-day issues reported so far... + +**Fixes** +- fix freeu for backend original and add it to xyz grid +- fix loading diffuser models in huggingface format from non-standard location +- fix default styles looking in wrong location +- fix missing upscaler folder on initial startup +- fix handling of relative path for models +- fix simple live preview device mismatch +- fix batch img2img +- fix diffusers dpm++ 2m and 1s samplers +- fix new style filename template +- fix image name template using model name +- fix model path using relative path +- fix torch-rocm version detection (thanks @xangelix) +- force second requirements check on startup +- remove lyco, multiple_tqdm +- enhance extension compatibility for exensions directly importing codeformers +- enhance extension compatibility for exensions directly accessing processing params +- update `openvino` (thanks @disty0) +- update `typing-extensions` + +**Themes** +- all built-in themes are fully supported: + - *black-teal (default), light-teal, black-orange, invoked, amethyst-nightfall, midnight-barbie* +- if you're using any **gradio default** themes or a **3rd party** theme or that are not optimized for SD.Next, you may experience issues + default minimal style has been updated for compatibility, but actual styling is completely outside of SD.Next control + ## Update for 2023-10-17 This is a major release, with many changes and new functionality... diff --git a/README.md b/README.md index bb3b34e94..13e53421a 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ General goals: ### **Sponsors**
-Allan GrantMichael HarrisBrent OzarToniXMatthew RunoHELLO WORLD SASSalad TechnologiesGym Dreams • GymDreams8 +Allan GrantMichael HarrisBrent OzarToniXMatthew RunoHELLO WORLD SASSalad TechnologiesGym Dreams • GymDreams8

diff --git a/beautiful girl sitting on an edge of a.jpg b/beautiful girl sitting on an edge of a.jpg new file mode 100644 index 000000000..1c5e7382f Binary files /dev/null and b/beautiful girl sitting on an edge of a.jpg differ diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 4edcf986b..2132c8ed5 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -374,8 +374,13 @@ def list_available_networks(): available_network_hash_lookup.clear() forbidden_network_aliases.update({"none": 1, "Addams": 1}) os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True) - candidates = list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) - candidates += list(shared.walk_files(shared.cmd_opts.lyco_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) + candidates = [] + if os.path.exists(shared.cmd_opts.lora_dir): + candidates += list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) + else: + shared.log.warning('LoRA directory not found: path={shared.cmd_opts.lora_dir}') + if os.path.exists(shared.cmd_opts.lyco_dir): + candidates += list(shared.walk_files(shared.cmd_opts.lyco_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) for filename in candidates: if os.path.isdir(filename): continue diff --git a/html/locale_en.json b/html/locale_en.json index 92b41bf39..de35d33ec 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -59,7 +59,7 @@ {"id":"","label":"Skip","localized":"","hint":"Stop processing current job and continue processing"}, {"id":"","label":"Interrupt","localized":"","hint":"Interrupt current processing job"}, {"id":"","label":"Pause","localized":"","hint":"Pause processing"}, - {"id":"","label":"Apply","localized":"","hint":"Apply parameters from last known generated image"}, + {"id":"","label":"Restore","localized":"","hint":"Restore parameters from last known generated image"}, {"id":"","label":"Clear","localized":"","hint":"Clear prompts"}, {"id":"","label":"Networks","localized":"","hint":"Open extra network interface"}, {"id":"","label":"Interrogate\nCLIP","localized":"","hint":"Run interrogate using CLIP model"}, @@ -79,26 +79,23 @@ {"id":"","label":"Do not automatically build extra network pages","localized":"","hint":""}, {"id":"","label":"Use LyCoris handler for all Lora types","localized":"","hint":""}, {"id":"","label":"Use Kohya method for handling multiple LoRA","localized":"","hint":""}, - {"id":"","label":"Multiplier for extra networks","localized":"","hint":"When adding extra network such as Hypernetwork or Lora to prompt, use this multiplier for it"}, + {"id":"","label":"Default multiplier for extra networks","localized":"","hint":"When adding extra network such as Lora to prompt, use this multiplier for it"}, {"id":"","label":"Add hypernetwork to prompt","localized":"","hint":""}, {"id":"","label":"Add Lora to prompt","localized":"","hint":""}, {"id":"","label":"shuffle tags by ',' when creating prompts.","localized":"","hint":""}, {"id":"","label":"When adding to prompt, refer to Lora by","localized":"","hint":""}, {"id":"","label":"add lora hashes to infotext","localized":"","hint":""}, - {"id":"","label":"Checkpoints","localized":"","hint":"Trained model checkpoints"}, + {"id":"","label":"Model","localized":"","hint":"Trained model checkpoints"}, + {"id":"","label":"Style","localized":"","hint":"Additional styles to be applied on selected generation paramters"}, {"id":"","label":"Styles","localized":"","hint":"Additional styles to be applied on selected generation paramters"}, {"id":"","label":"Lora","localized":"","hint":"LoRA: Low-Rank Adaptation. Fine-tuned model that is applied on top of a loaded model"}, {"id":"","label":"LyCORIS","localized":"","hint":"LyCORIS: Lora beYond Conventional methods. Fine-tuned model that is applied on top of a loaded model"}, - {"id":"","label":"Textual Inversion","localized":"","hint":"Textual inversion embedding is a trained embedded information about the subject"}, - {"id":"","label":"Hypernetworks","localized":"","hint":"Small trained neural network that modifies behavior of the loaded model"}, - {"id":"","label":"Save preview","localized":"","hint":"Save current image as extra network preview"}, - {"id":"","label":"Save description","localized":"","hint":"Save current text as extra network description"}, - {"id":"","label":"Read description","localized":"","hint":"Read stored extra network description"} + {"id":"","label":"Embedding","localized":"","hint":"Textual inversion embedding is a trained embedded information about the subject"}, + {"id":"","label":"Hypernetwork","localized":"","hint":"Small trained neural network that modifies behavior of the loaded model"} ], "gallery buttons": [ {"id":"","label":"show","localized":"","hint":"Show image location"}, {"id":"","label":"save","localized":"","hint":"Save image"}, - {"id":"","label":"zip","localized":"","hint":"Create zip archive from images"}, {"id":"","label":"delete","localized":"","hint":"Delete image"}, {"id":"","label":"replace","localized":"","hint":"Replace image"}, {"id":"","label":"➠ text","localized":"","hint":"Transfer image to text interface"}, @@ -118,8 +115,8 @@ {"id":"","label":"Specific branch name","localized":"","hint":"Specify extension branch name, leave blank for default"}, {"id":"","label":"Local directory name","localized":"","hint":"Directory where to install extension, leave blank for default"}, {"id":"","label":"Refresh extension list","localized":"","hint":"Refresh list of available extensions"}, - {"id":"","label":"Update installed extensions","localized":"","hint":"Update installed extensions to their latest available version"}, - {"id":"","label":"Apply changes & restart server","localized":"","hint":"Apply all changes and restart server"}, + {"id":"","label":"Update all installed","localized":"","hint":"Update installed extensions to their latest available version"}, + {"id":"","label":"Apply changes","localized":"","hint":"Apply all changes and restart server"}, {"id":"","label":"install","localized":"","hint":"install this extension"}, {"id":"","label":"uninstall","localized":"","hint":"uninstall this extension"}, {"id":"","label":"User interface","localized":"","hint":"Review and set current values as default values for the user interface"}, @@ -135,13 +132,15 @@ {"id":"","label":"Advanced","localized":"","hint":"Additional advanced options"}, {"id":"","label":"Sampling method","localized":"","hint":"Which algorithm to use to produce the image"}, {"id":"","label":"Sampling steps","localized":"","hint":"How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results"}, + {"id":"","label":"Sampler options","localized":"","hint":"Advanced sampler options"}, + {"id":"","label":"Sigma algorithm","localized":"","hint":"Algorithm to progress noise resolution on each sampler step"}, {"id":"","label":"Restore faces","localized":"","hint":"Use a pre-trained model to correct the generated faces. See GFPGAN or Codeformer."}, {"id":"","label":"Tiling","localized":"","hint":"Produce an image that can be tiled"}, {"id":"","label":"full quality","localized":"","hint":"Use full quality VAE to decode latent samples"}, {"id":"","label":"face restore","localized":"","hint":"Run processed image through additional face restoration model"}, {"id":"","label":"denoise","localized":"","hint":"Denoising details for img2img"}, {"id":"","label":"remove background","localized":"","hint":"Run processed image through additional background removal model"}, - {"id":"","label":"Second pass","localized":"","hint":"Use a similar process as image to image to upscale and/or add detail to the final image. Optionally uses refiner model to enhance image details."}, + {"id":"","label":"Enable second pass","localized":"","hint":"Use a similar process as image to image to upscale and/or add detail to the final image. Optionally uses refiner model to enhance image details."}, {"id":"","label":"Denoising strength","localized":"","hint":"Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies"}, {"id":"","label":"Denoise start","localized":"","hint":"Override denoise strength by stating how early base model should finish and when refiner should start. Only applicable to refiner usage. If set to 0 or 1, denoising strength will be used"}, {"id":"","label":"Hires steps","localized":"","hint":"Number of sampling steps for upscaled picture. If 0, uses same as for original"}, @@ -163,7 +162,7 @@ {"id":"","label":"Batch size","localized":"","hint":"How many image to create in a single batch (increases generation performance at cost of higher VRAM usage)"}, {"id":"","label":"cfg scale","localized":"","hint":"Classifier Free Guidance scale: how strongly the image should conform to prompt. Lower values produce more creative results, higher values make it follow the prompt more strictly; recommended values between 5-10"}, {"id":"","label":"CLIP skip","localized":"","hint":"Clip skip is a feature that allows users to control the level of specificity of the prompt, the higher the CLIP skip value, the less deep the prompt will be interpreted. CLIP Skip 1 is typical while some anime models produce better results at CLIP skip 2"}, - {"id":"","label":"Seed","localized":"","hint":"A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result"}, + {"id":"","label":"Initial seed","localized":"","hint":"A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result"}, {"id":"","label":"Extra","localized":"","hint":"Show additional options"}, {"id":"","label":"Variation seed","localized":"","hint":"Seed of a different picture to be mixed into the generation"}, {"id":"","label":"Variation strength","localized":"","hint":"How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something)"}, @@ -183,10 +182,7 @@ {"id":"","label":"Resize","localized":"","hint":"Resizing details. Higher resolutions require additional processing memory."}, {"id":"","label":"Crop to fit","localized":"","hint":"If the dimensions of your source image (e.g. 512x510) deviate from your target dimensions (e.g. 1024x768) this function will fit your upscaled image into your target size image. Excess will be cropped"}, {"id":"","label":"Secondary Upscaler","localized":"","hint":"Select secondary upscaler to run after initial upscaler"}, - {"id":"","label":"Upscaler 2 visibility","localized":"","hint":"Strength of the secondary upscaler"}, - {"id":"","label":"GFPGAN visibility","localized":"","hint":"Strength of GFPGAN face restore network, 0=disabled"}, - {"id":"","label":"CodeFormer visibility","localized":"","hint":"Strength of CodeFormer face restore network, 0=disabled"}, - {"id":"","label":"CodeFormer weight","localized":"","hint":"0=maximum strength, 1=minimum strength"} + {"id":"","label":"Upscaler 2 visibility","localized":"","hint":"Strength of the secondary upscaler"} ], "settings menu": [ {"id":"settings_submit","label":"Apply settings","localized":"","hint":"Save current settings, server restart is recommended"}, @@ -251,7 +247,7 @@ {"id":"","label":"Merge","localized":"","hint":""}, {"id":"","label":"Validate","localized":"","hint":""}, {"id":"","label":"List model details","localized":"","hint":""}, - {"id":"","label":"Calculate hash for all models (may take a long time)","localized":"","hint":""}, + {"id":"","label":"Calculate hash for all models","localized":"","hint":"Calculates hash for all available models which may take a very long time"}, {"id":"","label":"search models","localized":"","hint":""}, {"id":"","label":"select model","localized":"","hint":""}, {"id":"","label":"specify model variant","localized":"","hint":""}, @@ -492,8 +488,8 @@ {"id":"","label":"Path to notification sound","localized":"","hint":""}, {"id":"","label":"Live preview display period","localized":"","hint":""}, {"id":"","label":"Full VAE","localized":"","hint":""}, - {"id":"","label":"Approximate NN","localized":"","hint":"Cheap neural network approximation. Very fast compared to VAE, but produces pictures with 4 times smaller horizontal/vertical resolution and lower quality"}, - {"id":"","label":"Approximate simple","localized":"","hint":"Very cheap approximation. Very fast compared to VAE, but produces pictures with 8 times smaller horizontal/vertical resolution and extremely low quality"}, + {"id":"","label":"Approximate","localized":"","hint":"Cheap neural network approximation. Very fast compared to VAE, but produces pictures with 4 times smaller horizontal/vertical resolution and lower quality"}, + {"id":"","label":"Simple","localized":"","hint":"Very cheap approximation. Very fast compared to VAE, but produces pictures with 8 times smaller horizontal/vertical resolution and extremely low quality"}, {"id":"","label":"TAESD","localized":"","hint":""}, {"id":"","label":"Combined","localized":"","hint":""}, {"id":"","label":"Progress update period","localized":"","hint":"Update period for UI progress bar and preview checks, in miliseconds"}, @@ -546,8 +542,6 @@ {"id":"","label":"Filename word regex","localized":"","hint":"This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is"}, {"id":"","label":"Filename join string","localized":"","hint":"This string will be used to join split words into a single line if the option above is enabled"}, {"id":"","label":"Embeddings train templates directory","localized":"","hint":""}, - {"id":"","label":"Number of repeats for a single input image per epoch","localized":"","hint":"Used only for displaying epoch number"}, - {"id":"","label":"Save CSV file containing the loss to log directory","localized":"","hint":"Save CSV file containing the loss to log directory every N steps, 0 to disable"}, {"id":"","label":"Enable tensorboard logging","localized":"","hint":""}, {"id":"","label":"Save generated images within tensorboard","localized":"","hint":""}, {"id":"","label":"Tensorboard flush period","localized":"","hint":"How often, in seconds, to flush the pending Tensorboard events and summaries to disk"}, @@ -567,10 +561,8 @@ {"id":"","label":"Escape brackets in deepbooru","localized":"","hint":"Escape (\\) brackets in deepbooru so they are used as literal brackets and not for emphasis"}, {"id":"","label":"Filter out tags from deepbooru output","localized":"","hint":"Filter out those tags from deepbooru output (separated by comma)"}, {"id":"","label":"Default upscaler for image resize operations","localized":"","hint":""}, - {"id":"","label":"Tile size for ESRGAN upscalers","localized":"","hint":"0 = no tiling"}, - {"id":"","label":"Tile overlap in pixels for ESRGAN upscalers","localized":"","hint":"Low values = visible seam"}, - {"id":"","label":"Tile size for SCUNET upscalers","localized":"","hint":"0 = no tiling"}, - {"id":"","label":"Tile overlap for SCUNET upscalers","localized":"","hint":" Low values = visible seam"}, + {"id":"","label":"Upscaler tile size","localized":"","hint":"0 = no tiling"}, + {"id":"","label":"Upscaler tile overlap","localized":"","hint":"Low values = visible seam"}, {"id":"","label":"Do not fix prompt schedule for second order samplers","localized":"","hint":""}, {"id":"","label":"CodeFormer","localized":"","hint":""}, {"id":"","label":"GFPGAN","localized":"","hint":"Restore low quality faces using GFPGAN neural network"}, diff --git a/installer.py b/installer.py index 36121cdba..e93ae9b01 100644 --- a/installer.py +++ b/installer.py @@ -410,9 +410,9 @@ def check_torch(): rocm_ver = None if rocm_ver in {"5.7"}: # install torch nightly via torchvision to avoid wasting bandwidth when torchvision depends on torch from yesterday - torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision --pre --index-url https://download.pytorch.org/whl/nightly/rocm{rocm_ver}') + torch_command = os.environ.get('TORCH_COMMAND', f'torch torchvision --pre --index-url https://download.pytorch.org/whl/nightly/rocm{rocm_ver}') elif rocm_ver in {"5.5", "5.6"}: - torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm{rocm_ver}') + torch_command = os.environ.get('TORCH_COMMAND', f'torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm{rocm_ver}') else: # ROCm 5.5 is oldest for PyTorch 2.1 torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision --index-url https://download.pytorch.org/whl/rocm5.5') @@ -431,9 +431,38 @@ def check_torch(): ipex_pip = 'https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.0.110%2Bxpu-master%2Bdll-bundle/intel_extension_for_pytorch-2.0.110+gitc6ea20b-cp310-cp310-win_amd64.whl' torch_command = os.environ.get('TORCH_COMMAND', f'{pytorch_pip} {torchvision_pip} {ipex_pip}') elif allow_openvino and args.use_openvino: - #Remove this after 2.1.0 releases log.info('Using OpenVINO') - torch_command = os.environ.get('TORCH_COMMAND', '--pre torch==2.1.0.dev20230820+cpu torchvision==0.16.0.dev20230820+cpu -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html') + if "linux" in sys.platform: + if sys.version_info[1] == 11: + pytorch_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torch-2.1.0.dev20230820+cpu-cp311-cp311-linux_x86_64.whl' + torchvision_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torchvision-0.16.0.dev20230820+cpu-cp311-cp311-linux_x86_64.whl' + elif sys.version_info[1] == 10: + pytorch_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torch-2.1.0.dev20230820+cpu-cp310-cp310-linux_x86_64.whl' + torchvision_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torchvision-0.16.0.dev20230820+cpu-cp310-cp310-linux_x86_64.whl' + elif sys.version_info[1] == 8: + pytorch_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torch-2.1.0.dev20230820+cpu-cp38-cp38-linux_x86_64.whl' + torchvision_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torchvision-0.16.0.dev20230820+cpu-cp38-cp38-linux_x86_64.whl' + else: + log.error('Unsupported Python version') + #Will error when generating an image: + pytorch_pip = 'torch==2.1.0' + torchvision_pip = 'torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cpu' + else: + if sys.version_info[1] == 11: + pytorch_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torch-2.1.0.dev20230820+cpu-cp311-cp311-win_amd64.whl' + torchvision_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torchvision-0.16.0.dev20230820+cpu-cp311-cp311-win_amd64.whl' + elif sys.version_info[1] == 10: + pytorch_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torch-2.1.0.dev20230820+cpu-cp310-cp310-win_amd64.whl' + torchvision_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torchvision-0.16.0.dev20230820+cpu-cp310-cp310-win_amd64.whl' + elif sys.version_info[1] == 8: + pytorch_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torch-2.1.0.dev20230820+cpu-cp38-cp38-win_amd64.whl' + torchvision_pip = 'https://github.com/Disty0/automatic/releases/download/openvino_pre_release_pytorch/torchvision-0.16.0.dev20230820+cpu-cp38-cp38-win_amd64.whl' + else: + log.error('Unsupported Python version') + #Will error when generating an image: + pytorch_pip = 'torch==2.1.0' + torchvision_pip = 'torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cpu' + torch_command = os.environ.get('TORCH_COMMAND', f'{pytorch_pip} {torchvision_pip}') else: machine = platform.machine() if sys.platform == 'darwin': @@ -447,7 +476,7 @@ def check_torch(): log.info('Using CPU-only Torch') torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision') if 'torch' in torch_command and not args.version: - log.info('Installing torch - this may take a while') + # log.info('Installing torch - this may take a while') install(torch_command, 'torch torchvision') else: try: @@ -505,7 +534,7 @@ def check_torch(): if opts.get('cuda_compile_backend', '') == 'hidet': install('hidet', 'hidet') if args.use_openvino or opts.get('cuda_compile_backend', '') == 'openvino_fx': - install('openvino==2023.1.0', 'openvino') + install('openvino==2023.2.0.dev20230922', 'openvino') os.environ.setdefault('PYTORCH_TRACING_MODE', 'TORCHFX') os.environ.setdefault('NEOReadDebugKeys', '1') os.environ.setdefault('ClDeviceGlobalMemSizeAvailablePercent', '100') @@ -757,6 +786,7 @@ def set_environment(): os.environ.setdefault('HF_HUB_DISABLE_EXPERIMENTAL_WARNING', '1') os.environ.setdefault('UVICORN_TIMEOUT_KEEP_ALIVE', '60') os.environ.setdefault('K_DIFFUSION_USE_COMPILE', '0') + os.environ.setdefault('TF_ENABLE_ONEDNN_OPTS', '0') if sys.platform == 'darwin': os.environ.setdefault('PYTORCH_ENABLE_MPS_FALLBACK', '1') diff --git a/javascript/base.css b/javascript/base.css new file mode 100644 index 000000000..86ef0f2a7 --- /dev/null +++ b/javascript/base.css @@ -0,0 +1,125 @@ +/* toolbutton */ +.gradio-button.tool { max-width: min-content; min-width: min-content !important; align-self: end; font-size: 1.4em; color: var(--body-text-color) !important; margin-bottom: var(--spacing-md); } + +/* token counters */ +.block.token-counter{ position: absolute; display: inline-block; right: 0; min-width: 0 !important; width: auto; z-index: 100; top: 0; } +.block.token-counter span{ background: var(--input-background-fill) !important; box-shadow: 0 0 0.0 0.3em rgba(192,192,192,0.15), inset 0 0 0.6em rgba(192,192,192,0.075); border: 2px solid rgba(192,192,192,0.4) !important; } +.block.token-counter.error span{ box-shadow: 0 0 0.0 0.3em rgba(255,0,0,0.15), inset 0 0 0.6em rgba(255,0,0,0.075); border: 2px solid rgba(255,0,0,0.4) !important; } +.block.token-counter div{ display: inline; } +.block.token-counter span{ padding: 0.1em 0.75em; } + +/* live preview */ +.progressDiv{ position: relative; height: 20px; background: #b4c0cc; margin-bottom: -3px; } +.dark .progressDiv{ background: #424c5b; } +.progressDiv .progress{ width: 0%; height: 20px; background: #0060df; color: white; font-weight: bold; line-height: 20px; padding: 0 8px 0 0; text-align: right; overflow: visible; white-space: nowrap; padding: 0 0.5em; } +.livePreview { position: absolute; z-index: 50; background-color: transparent; width: -moz-available; width: -webkit-fill-available; } +.livePreview img { position: absolute; object-fit: contain; width: 100%; height: 100%; } +.dark .livePreview { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } +.popup-metadata { color: white; background: #0000; display: inline-block; white-space: pre-wrap; font-size: 0.75em; } +.global-popup{ display: flex; position: fixed; z-index: 10001; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(20, 20, 20, 0.95);} +.global-popup-close:before { content: "×"; } +.global-popup-close{ position: fixed; right: 0.5em; top: 0; cursor: pointer; color: white; font-size: 32pt; } +.global-popup-inner{ display: inline-block; margin: auto; padding: 2em; } + +/* fullpage image viewer */ +#lightboxModal{ display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(20, 20, 20, 0.75); backdrop-filter: blur(6px); + user-select: none; -webkit-user-select: none; flex-direction: row; } +.modalControls { display: flex; justify-content: space-evenly; background-color: transparent; position: absolute; width: 99%; z-index: 1; } +.modalControls:hover { background-color: #50505050; } +.modalControls span { color: white; font-size: 2em; font-weight: bold; cursor: pointer; filter: grayscale(100%); } +.modalControls span:hover, .modalControls span:focus { color: var(--highlight-color); filter: none; } +.lightboxModalPreviewZone { display: flex; width: 100%; height: 100%; } +.lightboxModalPreviewZone:focus-visible { outline: none; } +.lightboxModalPreviewZone > img { display: block; margin: auto; width: auto; } +.lightboxModalPreviewZone > img.modalImageFullscreen{ object-fit: contain; height: 100%; width: 100%; min-height: 0; background: transparent; } +table.settings-value-table { background: white; border-collapse: collapse; margin: 1em; border: var(--spacing-sm) solid white; } +table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-width: 36em; } +.modalPrev, .modalNext { cursor: pointer; position: relative; z-index: 1; top: 0; width: auto; height: 100vh; line-height: 100vh; text-align: center; padding: 16px; + margin-top: -50px; color: white; font-weight: bold; font-size: 20px; transition: 0.6s ease; user-select: none; -webkit-user-select: none; } +.modalNext { right: 0; } +.modalPrev:hover, .modalNext:hover { background-color: rgba(0, 0, 0, 0.8); } +#imageARPreview { position: absolute; top: 0px; left: 0px; border: 2px solid red; background: rgba(255, 0, 0, 0.3); z-index: 900; pointer-events: none; display: none; } + +/* context menu (ie for the generate button) */ +#context-menu { z-index: 9999; position: absolute; display: block; padding: var(--spacing-md); border: 2px solid var(--highlight-color); background: var(--background-fill-primary); color: var(--body-text-color); } +.context-menu-items { list-style: none; margin: 0; padding: 0; } +.context-menu-items a { display: block; padding: var(--spacing-md); cursor: pointer; font-weight: normal; } +.context-menu-items a:hover { background: var(--highlight-color) } + +/* log monitor */ +.log-monitor { display: none; justify-content: unset !important; overflow: hidden; padding: 0; margin-top: auto; font-family: monospace; font-size: 0.85em; } +.log-monitor td, .log-monitor th { padding-left: 1em; } + +/* changelog */ +.md h2 { background-color: var(--background-fill-primary); padding: 0.5em; } +.md ul { list-style-type: square !important; text-indent: 1em; margin-left: 4em; } +.md li { list-style-position: outside !important; text-indent: 0; } +.md p { margin-left: 2em; } + +/* extensions */ +#tab_extensions table, #tab_config table{ border-collapse: collapse; } +#tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config table th { border: 1px solid #ccc; padding: 0.25em 0.5em; } +#tab_extensions table input[type="checkbox"] { margin-right: 0.5em; appearance: checkbox; } +#tab_extensions button{ max-width: 16em; } +#tab_extensions input[disabled="disabled"]{ opacity: 0.5; } +.extension-tag{ font-weight: bold; font-size: 95%; } +.extension-button { font-size: 95% !important; width: 6em; } +#extensions .name{ font-size: 1.1rem } +#extensions .type{ opacity: 0.5; font-size: 90%; text-align: center; } +#extensions .version{ opacity: 0.7; } +#extensions .info{ margin: 0; } +#extensions .date{ opacity: 0.85; font-size: 90%; } + +/* extra networks */ +.extra-networks > div { margin: 0; gap: 0.2em; border-bottom: none !important; } +.extra-networks .second-line { display: flex; width: -moz-available; width: -webkit-fill-available; gap: 0.3em; box-shadow: var(--input-shadow); } +.extra-networks .search { flex: 1; } +.extra-networks .description { flex: 3; } +.extra-networks .tab-nav > button { margin-right: 0; height: 24px; padding: 2px 4px 2px 4px; } +.extra-networks .buttons { position: absolute; right: 0; margin: -4px; background: var(--background-color); } +.extra-networks .buttons>button { height: 1.2em; margin-top: var(--spacing-md); } +.extra-networks .custom-button { width: 120px; width: 100%; background: none; justify-content: left; text-align: left; padding: 2px 8px 2px 16px; text-indent: -8px; box-shadow: none; line-break: auto; } +.extra-networks .custom-button:hover { background: var(--button-primary-background-fill) } +.extra-networks-tab { padding: 0 !important; } +.extra-network-subdirs { background: var(--input-background-fill); overflow-x: hidden; overflow-y: auto; min-width: max(15%, 120px); padding-top: 0.5em; margin-top: -4px !important; } +.extra-networks-page { display: flex } +.extra-network-cards { display: flex; flex-wrap: wrap; overflow-y: auto; overflow-x: hidden; align-content: flex-start; width: -moz-available; width: -webkit-fill-available; } +.extra-network-cards .card { height: fit-content; margin: 0 0 0.5em 0.5em; position: relative; scroll-snap-align: start; scroll-margin-top: 0; } +.extra-network-cards .card .overlay { position: absolute; bottom: 0; padding: 0.2em; z-index: 10; width: 100%; background: none; } +.extra-network-cards .card .overlay .name { font-size: 1.1em; font-weight: bold; text-shadow: 1px 1px black; color: white; overflow-wrap: break-word; } +.extra-network-cards .card .preview { box-shadow: var(--button-shadow); min-height: 30px; } +.extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); } +.extra-network-cards .card:hover .preview { box-shadow: none; filter: grayscale(100%); } +.extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); } +.extra-network-cards .card .overlay .tags { margin: 4px; display: none; overflow-wrap: break-word; } +.extra-network-cards .card .overlay .tag { padding: 2px; margin: 2px; background: var(--neutral-700); cursor: pointer; display: inline-block; } +.extra-network-cards .card .actions > span { padding: 4px; } +.extra-network-cards .card:hover .actions { display: block; } +.extra-network-cards .card:hover .overlay .tags { display: block; } +.extra-network-cards .card .actions { font-size: 3em; display: none; text-align-last: right; cursor: pointer; font-variant: unicase; position: absolute; z-index: 100; right: 0; height: 0.7em; width: 100%; background: rgba(0, 0, 0, 0.40); } +#txt2img_description, #img2img_description { max-height: 63px; overflow-y: auto !important; } +#txt2img_description > label > textarea, #img2img_description > label > textarea { font-size: 0.9em } + +#txt2img_extra_details > div, #img2img_extra_details > div { overflow-y: auto; min-height: 40vh; max-height: 80vh; align-self: flex-start; } +#txt2img_extra_details, #img2img_extra_details { position: fixed; bottom: 50%; left: 50%; transform: translate(-50%, 50%); padding: 0.8em; border: var(--block-border-width) solid var(--highlight-color) !important; + z-index: 100; box-shadow: var(--button-shadow); } +#txt2img_extra_details td:first-child, #img2img_extra_details td:first-child { font-weight: bold; vertical-align: top; } +#txt2img_extra_details .gradio-image, #img2img_extra_details .gradio-image { max-height: 70vh; } + +/* custom component */ +.folder-selector textarea { height: 2em !important; padding: 6px !important; } +.nvml { position: fixed; bottom: 10px; right: 10px; background: var(--background-fill-primary); border: 1px solid var(--button-primary-border-color); padding: 6px; color: var(--button-primary-text-color); + font-size: 0.7em; z-index: 50; font-family: monospace; display: none; } + +/* loader */ +.splash { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 1000; display: block; text-align: center; } +.motd { margin-top: 2em; color: var(--body-text-color-subdued); font-family: monospace; font-variant: all-petite-caps; } +.splash-img { margin: 10% auto 0 auto; width: 512px; background-repeat: no-repeat; height: 512px; animation: color 10s infinite alternate; } +.loading { color: white; position: absolute; top: 20%; left: 50%; transform: translateX(-50%); } +.loader { width: 300px; height: 300px; border: var(--spacing-md) solid transparent; border-radius: 50%; border-top: var(--spacing-md) solid var(--primary-600); animation: spin 4s linear infinite; position: relative; } +.loader::before, .loader::after { content: ""; position: absolute; top: 6px; bottom: 6px; left: 6px; right: 6px; border-radius: 50%; border: var(--spacing-md) solid transparent; } +.loader::before { border-top-color: var(--primary-900); animation: 3s spin linear infinite; } +.loader::after { border-top-color: var(--primary-300); animation: spin 1.5s linear infinite; } +@keyframes move { from { background-position-x: 0, -40px; } to { background-position-x: 0, 40px; } } +@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } +@keyframes color { from { filter: hue-rotate(0deg) } to { filter: hue-rotate(360deg) } } diff --git a/javascript/style.css b/javascript/sdnext.css similarity index 98% rename from javascript/style.css rename to javascript/sdnext.css index 217796950..fb6a198f3 100644 --- a/javascript/style.css +++ b/javascript/sdnext.css @@ -1,4 +1,4 @@ -:root { --checkbox-label-gap: 0.25em 0.1em; --section-header-text-size: 12pt; --block-background-fill: transparent;} +:root { --left-column: 490px; } a { font-weight: bold; cursor: pointer; } h2 { margin-top: 1em !important; font-size: 1.4em !important; } footer { display: none; } diff --git a/javascript/setHints.js b/javascript/setHints.js index 1ce221723..726d83dfa 100644 --- a/javascript/setHints.js +++ b/javascript/setHints.js @@ -30,20 +30,15 @@ async function tooltipHide(e) { async function validateHints(elements, data) { let original = elements.map((e) => e.textContent.toLowerCase().trim()).sort((a, b) => a > b); - original = [...new Set(original)]; - log('all hints:', original); - log('hints-differences', { elements: original.length, hints: data.length }); + original = [...new Set(original)]; // remove duplicates const current = data.map((e) => e.label.toLowerCase().trim()).sort((a, b) => a > b); - let missing = []; - for (let i = 0; i < original.length; i++) { - if (!current.includes(original[i])) missing.push(original[i]); - } - log('missing in locale:', missing); - missing = []; - for (let i = 0; i < current.length; i++) { - if (!original.includes(current[i])) missing.push(current[i]); - } - log('in locale but not ui:', missing); + log('all elements:', original); + log('all hints:', current); + log('hints-differences', { elements: original.length, hints: current.length }); + const missingLocale = original.filter((e) => !current.includes(e)); + log('missing in locale:', missingLocale); + const missingUI = current.filter((e) => !original.includes(e)); + log('in locale but not ui:', missingUI); } async function setHints() { diff --git a/launch.py b/launch.py index d5fe9a7d1..b725961f9 100644 --- a/launch.py +++ b/launch.py @@ -199,7 +199,7 @@ if __name__ == "__main__": installer.install_repositories() installer.install_submodules() installer.install_extensions() - installer.install_packages() # redo packages since extensions may change them + installer.install_requirements() # redo requirements since extensions may change them installer.update_wiki() if installer.errors == 0: installer.log.debug(f'Setup complete without errors: {round(time.time())}') diff --git a/modules/api/api.py b/modules/api/api.py index 163e11881..6cdb362d5 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -182,7 +182,8 @@ class Api: if shared.opts.motd: res = requests.get('https://vladmandic.github.io/automatic/motd', timeout=10) if res.status_code == 200: - shared.log.info(f'MOTD: {res.text}') + msg = (res.text or '').strip() + shared.log.info(f'MOTD: {msg if len(msg) > 0 else "N/A"}') motd += res.text return motd diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 6459204d1..ef2074f1e 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -94,7 +94,7 @@ def compatibility_args(opts, args): group.add_argument("--lyco-dir", help=argparse.SUPPRESS, default=opts.lyco_dir) group.add_argument("--embeddings-dir", help=argparse.SUPPRESS, default=opts.embeddings_dir) group.add_argument("--hypernetwork-dir", help=argparse.SUPPRESS, default=opts.hypernetwork_dir) - group.add_argument("--lyco-patch-lora", help=argparse.SUPPRESS, default=opts.lyco_patch_lora) + group.add_argument("--lyco-patch-lora", help=argparse.SUPPRESS, default=False) group.add_argument("--lyco-debug", help=argparse.SUPPRESS, action='store_true', default=False) group.add_argument("--enable-console-prompts", help=argparse.SUPPRESS, action='store_true', default=False) group.add_argument("--safe", help=argparse.SUPPRESS, action='store_true', default=False) @@ -114,7 +114,7 @@ def compatibility_args(opts, args): opts.sd_vae_as_default = True opts.enable_emphasis = True opts.enable_batch_seeds = True - opts.multiple_tqdm = False + # opts.multiple_tqdm = False opts.print_hypernet_extra = False opts.dimensions_and_batch_together = True opts.enable_pnginfo = True diff --git a/modules/images.py b/modules/images.py index 299eaa4d9..4b0d198a2 100644 --- a/modules/images.py +++ b/modules/images.py @@ -294,8 +294,8 @@ class FilenameGenerator: 'job_timestamp': lambda self: getattr(self.p, "job_timestamp", shared.state.job_timestamp), 'model': lambda self: shared.sd_model.sd_checkpoint_info.title, - 'model_shortname': lambda self: shared.sd_model.sd_checkpoint_info.name, - 'model_name': lambda self: shared.sd_model.sd_checkpoint_info.name, + 'model_shortname': lambda self: shared.sd_model.sd_checkpoint_info.model_name, + 'model_name': lambda self: shared.sd_model.sd_checkpoint_info.model_name, 'model_hash': lambda self: shared.sd_model.sd_checkpoint_info.shorthash, 'prompt': lambda self: self.prompt, @@ -351,7 +351,7 @@ class FilenameGenerator: return shorthash def prompt_words(self): - if self.p is None or self.prompt is None: + if self.prompt is None: return '' no_attention = re_attention.sub(r'\1', self.prompt) no_network = re_network.sub(r'\1', no_attention) diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py index 61bb32921..b4294f9ee 100644 --- a/modules/intel/openvino/__init__.py +++ b/modules/intel/openvino/__init__.py @@ -8,6 +8,7 @@ from openvino.runtime import Core, Type, PartialShape, serialize from torch._dynamo.backends.common import fake_tensor_unsupported from torch._dynamo.backends.registry import register_backend from torch.fx.experimental.proxy_tensor import make_fx +from torch.fx import GraphModule from torch.utils._pytree import tree_flatten from types import MappingProxyType from hashlib import sha256 @@ -132,7 +133,7 @@ def cached_model_name(model_hash_str, device, args, cache_root, reversed = False return file_name -def check_fully_supported(self, graph_module): +def check_fully_supported(self, graph_module: GraphModule) -> bool: num_fused = 0 for node in graph_module.graph.nodes: if node.op == "call_module" and "fused_" in node.name: @@ -178,7 +179,7 @@ def openvino_clear_caches(): compiled_cache.clear() partitioned_modules.clear() -def openvino_compile(gm, *args, model_hash_str: str = None, file_name=""): +def openvino_compile(gm: GraphModule, *args, model_hash_str: str = None, file_name=""): core = Core() device = get_device() @@ -259,7 +260,7 @@ def openvino_compile_cached_model(cached_model_path, *example_inputs): return compiled_model -def openvino_execute(gm, *args, executor_parameters=None, partition_id, file_name=""): +def openvino_execute(gm: GraphModule, *args, executor_parameters=None, partition_id, file_name=""): executor_parameters = executor_parameters or DEFAULT_OPENVINO_PYTHON_CONFIG use_cache = executor_parameters.get( @@ -292,7 +293,7 @@ def openvino_execute(gm, *args, executor_parameters=None, partition_id, file_nam return results1[0] return results1 -def openvino_execute_partitioned(gm, *args, executor_parameters=None, file_name=""): +def openvino_execute_partitioned(gm: GraphModule, *args, executor_parameters=None, file_name=""): executor_parameters = executor_parameters or DEFAULT_OPENVINO_PYTHON_CONFIG global partitioned_modules @@ -316,7 +317,7 @@ def openvino_execute_partitioned(gm, *args, executor_parameters=None, file_name= return partitioned_modules[signature](*args) -def partition_graph(gm, use_python_fusion_cache: bool, model_hash_str: str = None, file_name=""): +def partition_graph(gm: GraphModule, use_python_fusion_cache: bool, model_hash_str: str = None, file_name=""): global max_openvino_partitions for node in gm.graph.nodes: if node.op == "call_module" and "fused_" in node.name: diff --git a/modules/loader.py b/modules/loader.py index c0cc8dea3..6353ee1bb 100644 --- a/modules/loader.py +++ b/modules/loader.py @@ -9,7 +9,11 @@ initialized = False logging.getLogger("DeepSpeed").disabled = True # os.environ.setdefault('OMP_NUM_THREADS', 1) # os.environ.setdefault('MKL_NUM_THREADS', 1) + +# import tensorflow as tf # pylint: disable=C0411 + import torch # pylint: disable=C0411 + # torch.set_num_threads(1) try: import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import @@ -19,14 +23,14 @@ except Exception: urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) import torchvision # pylint: disable=W0611,C0411 import pytorch_lightning # pytorch_lightning should be imported after torch, but it re-enables warnings on import so import once to disable them # pylint: disable=W0611,C0411 -if ".dev" in torch.__version__ or "+git" in torch.__version__: - torch.__long_version__ = torch.__version__ - torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0) logging.getLogger("xformers").addFilter(lambda record: 'A matching Triton is not available' not in record.getMessage()) logging.getLogger("pytorch_lightning").disabled = True warnings.filterwarnings(action="ignore", category=DeprecationWarning) warnings.filterwarnings(action="ignore", category=FutureWarning) warnings.filterwarnings(action="ignore", category=UserWarning, module="torchvision") +if ".dev" in torch.__version__ or "+git" in torch.__version__: + torch.__long_version__ = torch.__version__ + torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0) timer.startup.record("torch") from fastapi import FastAPI # pylint: disable=W0611,C0411 diff --git a/modules/paths.py b/modules/paths.py index 6baed946c..0fd7b87b5 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -1,17 +1,18 @@ import os import sys -import modules.paths_internal +from modules import paths_internal, errors -data_path = modules.paths_internal.data_path -script_path = modules.paths_internal.script_path -models_path = modules.paths_internal.models_path -sd_configs_path = modules.paths_internal.sd_configs_path -sd_default_config = modules.paths_internal.sd_default_config -sd_model_file = modules.paths_internal.sd_model_file -default_sd_model_file = modules.paths_internal.default_sd_model_file -extensions_dir = modules.paths_internal.extensions_dir -extensions_builtin_dir = modules.paths_internal.extensions_builtin_dir +debug = errors.log.info if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None +data_path = paths_internal.data_path +script_path = paths_internal.script_path +models_path = paths_internal.models_path +sd_configs_path = paths_internal.sd_configs_path +sd_default_config = paths_internal.sd_default_config +sd_model_file = paths_internal.sd_model_file +default_sd_model_file = paths_internal.default_sd_model_file +extensions_dir = paths_internal.extensions_dir +extensions_builtin_dir = paths_internal.extensions_builtin_dir # data_path = cmd_opts_pre.data sys.path.insert(0, script_path) @@ -39,14 +40,14 @@ paths = {} for d, must_exist, what, _options in path_dirs: must_exist_path = os.path.abspath(os.path.join(script_path, d, must_exist)) if not os.path.exists(must_exist_path): - print(f"Warning: {what} not found at path {must_exist_path}", file=sys.stderr) + errors.log.error(f'Required path not found: path={must_exist_path} item={what}') else: d = os.path.abspath(d) sys.path.append(d) paths[what] = d -def create_paths(opts, log=None): +def create_paths(opts): def create_path(folder): if folder is None or folder == '': return @@ -54,26 +55,22 @@ def create_paths(opts, log=None): return try: os.makedirs(folder, exist_ok=True) - if log is not None: - log.debug(f'Create folder={folder}') + errors.log.info(f'Create folder={folder}') except Exception as e: - if log is not None: - log.error(f'Create Failed folder={folder} {e}') + errors.log.error(f'Create Failed folder={folder} {e}') def fix_path(folder): tgt = opts.data.get(folder, None) or opts.data_labels[folder].default if tgt is None or tgt == '': return tgt - if os.path.isabs(tgt): - return tgt - if len(data_path) > 0 and tgt.startswith(data_path): # path is already relative to data_path - return tgt - else: - tgt = os.path.join(data_path, tgt) - if os.path.isabs(tgt): - return tgt - tgt = os.path.relpath(tgt, script_path) - opts.data[folder] = tgt + fix = tgt + if not os.path.isabs(tgt) and len(data_path) > 0 and not tgt.startswith(data_path): # path is already relative to data_path + fix = os.path.join(data_path, fix) + if fix.startswith('..'): + fix = os.path.abspath(fix) + fix = fix if os.path.isabs(fix) else os.path.relpath(fix, script_path) + opts.data[folder] = fix + debug(f'Paths: folder={folder} original="{tgt}" target="{fix}"') return opts.data[folder] create_path(data_path) @@ -83,11 +80,12 @@ def create_paths(opts, log=None): create_path(extensions_dir) create_path(extensions_builtin_dir) create_path(fix_path('temp_dir')) - create_path(fix_path('hypernetwork_dir')) create_path(fix_path('ckpt_dir')) - create_path(fix_path('vae_dir')) create_path(fix_path('diffusers_dir')) + create_path(fix_path('vae_dir')) + create_path(fix_path('lora_dir')) create_path(fix_path('embeddings_dir')) + create_path(fix_path('hypernetwork_dir')) create_path(fix_path('outdir_samples')) create_path(fix_path('outdir_txt2img_samples')) create_path(fix_path('outdir_img2img_samples')) diff --git a/modules/postprocess/codeformer_model.py b/modules/postprocess/codeformer_model.py index e11747018..3f959b6b8 100644 --- a/modules/postprocess/codeformer_model.py +++ b/modules/postprocess/codeformer_model.py @@ -1,9 +1,6 @@ import os -import sys - import cv2 import torch - import modules.face_restoration from modules import shared, devices, modelloader, errors from modules.paths import models_path @@ -88,7 +85,7 @@ def setup_model(dirname): cropped_face_t = cropped_face_t.unsqueeze(0).to(devices.device_codeformer) try: with devices.inference_context(): - output = self.net(cropped_face_t, w=w if w is not None else shared.opts.code_former_weight, adain=True)[0] + output = self.net(cropped_face_t, w=w if w is not None else shared.opts.code_former_weight, adain=True)[0] # pylint: disable=not-callable restored_face = tensor2img(output, rgb2bgr=True, min_max=(-1, 1)) del output devices.torch_gc() diff --git a/modules/processing.py b/modules/processing.py index aa0bfafc8..0b3e461da 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -163,6 +163,7 @@ class StableDiffusionProcessing: self.refiner_steps = 5 self.refiner_start = 0 self.ops = [] + self.resize_mode: int = 0 self.ddim_discretize = shared.opts.ddim_discretize self.s_min_uncond = shared.opts.s_min_uncond self.s_churn = shared.opts.s_churn @@ -171,11 +172,15 @@ class StableDiffusionProcessing: self.s_max = shared.opts.s_max self.s_tmin = shared.opts.s_tmin self.s_tmax = float('inf') # not representable as a standard ui option - self.refiner_switch_at = 0 # a1111 compatibility item + shared.opts.data['clip_skip'] = clip_skip + # TODO a1111 compatibility items + self.refiner_switch_at = 0 + self.hr_prompt = '' + self.all_hr_prompts = [] + self.hr_negative_prompt = '' + self.all_hr_negative_prompts = [] self.comments = {} self.is_api = False - self.resize_mode: int = 0 - shared.opts.data['clip_skip'] = clip_skip @property def sd_model(self): @@ -1180,6 +1185,8 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): crop_region = None image_mask = self.image_mask if image_mask is not None: + if type(image_mask) == list: + image_mask = image_mask[0] image_mask = image_mask.convert('L') if self.inpainting_mask_invert: image_mask = ImageOps.invert(image_mask) diff --git a/modules/sd_hijack_freeu.py b/modules/sd_hijack_freeu.py index 4d21601d4..ef893d2f3 100644 --- a/modules/sd_hijack_freeu.py +++ b/modules/sd_hijack_freeu.py @@ -1,7 +1,7 @@ import math import functools import torch -from modules.shared import state, log, opts +from modules import shared from modules.sd_hijack_unet import th # based on @@ -23,7 +23,7 @@ cat_original = None def to_denoising_step(number, steps=None) -> int: if steps is None: - steps = state.sampling_steps + steps = shared.state.sampling_steps if isinstance(number, float): return int(number * steps) return number @@ -34,11 +34,11 @@ def get_schedule_ratio(): stop_step = to_denoising_step(stop_ratio) if start_step == stop_step: smooth_schedule_ratio = 0.0 - elif state.sampling_step < start_step: - smooth_schedule_ratio = min(1.0, max(0.0, state.sampling_step / start_step)) + elif shared.state.sampling_step < start_step: + smooth_schedule_ratio = min(1.0, max(0.0, shared.state.sampling_step / start_step)) else: - smooth_schedule_ratio = min(1.0, max(0.0, 1 + (state.sampling_step - start_step) / (start_step - stop_step))) - flat_schedule_ratio = 1.0 if start_step <= state.sampling_step < stop_step else 0.0 + smooth_schedule_ratio = min(1.0, max(0.0, 1 + (shared.state.sampling_step - start_step) / (start_step - stop_step))) + flat_schedule_ratio = 1.0 if start_step <= shared.state.sampling_step < stop_step else 0.0 return lerp(flat_schedule_ratio, smooth_schedule_ratio, transition_smoothness) @@ -47,7 +47,7 @@ def lerp(a, b, r): def free_u_cat_hijack(hs, *args, original_function, **kwargs): - if not opts.freeu_enabled: + if not shared.opts.freeu_enabled: return original_function(hs, *args, **kwargs) schedule_ratio = get_schedule_ratio() if schedule_ratio == 0: @@ -67,8 +67,8 @@ def free_u_cat_hijack(hs, *args, original_function, **kwargs): mask = (region_begin <= mask) & (mask <= region_end) if region_inverted: mask = ~mask - backbone_factor = opts.freeu_b1 if index == 0 else opts.freeu_b2 - skip_factor = opts.freeu_s1 if index == 0 else opts.freeu_s2 + backbone_factor = shared.opts.freeu_b1 if index == 0 else shared.opts.freeu_b2 + skip_factor = shared.opts.freeu_s1 if index == 0 else shared.opts.freeu_s2 h[:, mask] *= lerp(1, backbone_factor, schedule_ratio) h_skip = filter_skip(h_skip, threshold=skip_cutoff, scale=lerp(1, skip_factor, schedule_ratio), scale_high=lerp(1, skip_high_end_factor, schedule_ratio)) return original_function([h, h_skip], *args, **kwargs) @@ -130,8 +130,8 @@ def apply_freeu(p, backend_original): global state_enabled # pylint: disable=global-statement global cat_original # pylint: disable=global-statement if backend_original: - if opts.freeu_enabled: - p.extra_generation_params['FreeU'] = f'b1={opts.freeu_b1} b2={opts.freeu_b2} s1={opts.freeu_s1} s2={opts.freeu_s2}' + if shared.opts.freeu_enabled: + p.extra_generation_params['FreeU'] = f'b1={shared.opts.freeu_b1} b2={shared.opts.freeu_b2} s1={shared.opts.freeu_s1} s2={shared.opts.freeu_s2}' if not state_enabled: # otherwise already patched cat_original = th.cat th.cat = functools.partial(free_u_cat_hijack, original_function=th.cat) @@ -141,12 +141,12 @@ def apply_freeu(p, backend_original): th.cat = cat_original state_enabled = False elif hasattr(p.sd_model, 'enable_freeu'): - if opts.freeu_enabled: - p.extra_generation_params['FreeU'] = f'b1={opts.freeu_b1} b2={opts.freeu_b2} s1={opts.freeu_s1} s2={opts.freeu_s2}' - p.sd_model.enable_freeu(s1=opts.freeu_s1, s2=opts.freeu_s2, b1=opts.freeu_b1, b2=opts.freeu_b2) + if shared.opts.freeu_enabled: + p.extra_generation_params['FreeU'] = f'b1={shared.opts.freeu_b1} b2={shared.opts.freeu_b2} s1={shared.opts.freeu_s1} s2={shared.opts.freeu_s2}' + p.sd_model.enable_freeu(s1=shared.opts.freeu_s1, s2=shared.opts.freeu_s2, b1=shared.opts.freeu_b1, b2=shared.opts.freeu_b2) state_enabled = True elif state_enabled: p.sd_model.disable_freeu() state_enabled = False - if opts.freeu_enabled: - log.info(f'Applying free-u: b1={opts.freeu_b1} b2={opts.freeu_b2} s1={opts.freeu_s1} s2={opts.freeu_s2}') + if shared.opts.freeu_enabled: + shared.log.info(f'Applying free-u: b1={shared.opts.freeu_b1} b2={shared.opts.freeu_b2} s1={shared.opts.freeu_s1} s2={shared.opts.freeu_s2}') diff --git a/modules/sd_models.py b/modules/sd_models.py index 6aad35ea0..ff95ee7a8 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -50,21 +50,29 @@ class CheckpointInfo: self.hash = None self.filename = filename self.type = '' - filename = os.path.abspath(filename) relname = filename - if relname.startswith(script_path): + app_path = os.path.abspath(script_path) + + def rel(fn, path): try: - relname = os.path.relpath(filename, script_path) - except: - pass - try: - relname = os.path.relpath(filename, model_path) - except: - pass - try: - relname = os.path.relpath(filename, shared.cmd_opts.ckpt_dir) - except: - pass + return os.path.relpath(fn, path) + except Exception: + return fn + + if relname.startswith('..'): + relname = os.path.abspath(relname) + if relname.startswith(shared.opts.ckpt_dir): + relname = rel(filename, shared.opts.ckpt_dir) + elif relname.startswith(shared.opts.diffusers_dir): + relname = rel(filename, shared.opts.diffusers_dir) + elif relname.startswith(model_path): + relname = rel(filename, model_path) + elif relname.startswith(script_path): + relname = rel(filename, script_path) + elif relname.startswith(app_path): + relname = rel(filename, app_path) + else: + relname = os.path.abspath(relname) relname, ext = os.path.splitext(relname) ext = ext.lower()[1:] @@ -87,9 +95,6 @@ class CheckpointInfo: self.sha256 = repo[0]['hash'] self.type = 'diffusers' - # info = shared.readfile(self.filename, silent=True) - # if 'tags' in info: - # self.tags = info['tags'] self.shorthash = self.sha256[0:10] if self.sha256 else None self.title = self.name if self.shorthash is None else f'{self.name} [{self.shorthash}]' self.path = self.filename @@ -264,7 +269,8 @@ def select_checkpoint(op='model'): return checkpoint_info if len(checkpoints_list) == 0 and not shared.cmd_opts.no_download: shared.log.error("Cannot generate without a checkpoint") - shared.log.error("Use --ckpt to force using existing checkpoint") + shared.log.info("Set system paths to use existing folders in a different location") + shared.log.info("Or use --ckpt to force using existing checkpoint") return None checkpoint_info = next(iter(checkpoints_list.values())) if model_checkpoint is not None: @@ -705,6 +711,9 @@ def compile_diffusers(sd_model): def set_diffuser_options(sd_model, vae, op: str): + if sd_model is None: + shared.log.warning(f'{op} is not loaded') + return if (shared.opts.diffusers_model_cpu_offload or shared.cmd_opts.medvram) and (shared.opts.diffusers_seq_cpu_offload or shared.cmd_opts.lowvram): shared.log.warning(f'Setting {op}: Model CPU offload and Sequential CPU offload are not compatible') shared.log.debug(f'Setting {op}: disabling model CPU offload') diff --git a/modules/sd_samplers_diffusers.py b/modules/sd_samplers_diffusers.py index 6dfd2c73c..e7067c1fd 100644 --- a/modules/sd_samplers_diffusers.py +++ b/modules/sd_samplers_diffusers.py @@ -29,8 +29,8 @@ config = { 'DDIM': { 'clip_sample': True, 'set_alpha_to_one': True, 'steps_offset': 0, 'clip_sample_range': 1.0, 'sample_max_value': 1.0, 'timestep_spacing': 'linspace', 'rescale_betas_zero_snr': False }, 'DDPM': { 'variance_type': "fixed_small", 'clip_sample': True, 'thresholding': False, 'clip_sample_range': 1.0, 'sample_max_value': 1.0, 'timestep_spacing': 'linspace'}, 'DEIS': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "deis", 'solver_type': "logrho", 'lower_order_final': True }, - 'DPM 1S++': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False }, - 'DPM 2M++': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False }, + 'DPM++ 1S': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False }, + 'DPM++ 2M': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False }, 'DPM SDE': { 'use_karras_sigmas': False }, 'Euler a': { }, 'Euler': { 'interpolation_type': "linear", 'use_karras_sigmas': False }, @@ -66,9 +66,10 @@ class DiffusionSampler: return self.name = name self.config = {} - self.config = config['All'].copy() # apply global defaults if not hasattr(model, 'scheduler'): return + for key, value in config.get('All', {}).items(): # apply global defaults + self.config[key] = value for key, value in config.get(name, {}).items(): # apply diffusers per-scheduler defaults self.config[key] = value if hasattr(model.scheduler, 'scheduler_config'): # find model defaults diff --git a/modules/sd_vae_approx.py b/modules/sd_vae_approx.py index f5c577458..3e4b93ddd 100644 --- a/modules/sd_vae_approx.py +++ b/modules/sd_vae_approx.py @@ -5,8 +5,6 @@ from modules import devices, paths, shared sd_vae_approx_model = None -simple_weights = None -simple_bias = None class VAEApprox(nn.Module): @@ -58,24 +56,22 @@ def nn_approximation(sample): # Approximate NN def cheap_approximation(sample): # Approximate simple # https://discuss.huggingface.co/t/decoding-latents-to-rgb-without-upscaling/23204/2 - global simple_weights, simple_bias # pylint: disable=global-statement - if simple_weights is None or simple_bias is None: - if shared.sd_model_type == "sdxl": - simple_weights = torch.tensor([ - [0.4543,-0.2868, 0.1566,-0.4748], - [0.5008, 0.0952, 0.2155,-0.3268], - [0.5294, 0.1625,-0.0624,-0.3793] - ]).reshape(3, 4, 1, 1).to(sample.device) - simple_bias = torch.tensor([0.1375, 0.0144, -0.0675]).to(sample.device) - else: - simple_weights = torch.tensor([ - [0.298, 0.187,-0.158,-0.184], - [0.207, 0.286, 0.189,-0.271], - [0.208, 0.173, 0.264,-0.473], - ]).reshape(3, 4, 1, 1).to(sample.device) - simple_bias = None + if shared.sd_model_type == "sdxl": + simple_weights = torch.tensor([ + [0.4543,-0.2868, 0.1566,-0.4748], + [0.5008, 0.0952, 0.2155,-0.3268], + [0.5294, 0.1625,-0.0624,-0.3793] + ]).reshape(3, 4, 1, 1) + simple_bias = torch.tensor([0.1375, 0.0144, -0.0675]) + else: + simple_weights = torch.tensor([ + [0.298, 0.187,-0.158,-0.184], + [0.207, 0.286, 0.189,-0.271], + [0.208, 0.173, 0.264,-0.473], + ]).reshape(3, 4, 1, 1) + simple_bias = None try: - x_sample = nn.functional.conv2d(sample, simple_weights, simple_bias) # pylint: disable=not-callable + x_sample = nn.functional.conv2d(sample, simple_weights.to(sample.device, sample.dtype), simple_bias.to(sample.device, sample.dtype)) # pylint: disable=not-callable return x_sample except Exception as e: shared.log.error(f'Decode simple: {e}') diff --git a/modules/shared.py b/modules/shared.py index 0b2d3f41c..9c0b8058e 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -4,7 +4,6 @@ import sys import time import json import contextlib -import urllib.request from types import SimpleNamespace from urllib.parse import urlparse from enum import Enum @@ -12,7 +11,7 @@ import requests import gradio as gr import fasteners from rich.console import Console -from modules import errors, shared_items, shared_state, cmd_args, ui_components +from modules import errors, shared_items, shared_state, cmd_args, ui_components, theme 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 # pylint: disable=W0611 from modules.dml import memory_providers, default_memory_provider, directml_do_hijack import modules.interrogate @@ -42,7 +41,6 @@ extra_networks = [] options_templates = {} hypernetworks = {} loaded_hypernetworks = [] -gradio_theme = gr.themes.Base() settings_components = None latent_upscale_default_mode = "None" latent_upscale_modes = { @@ -162,7 +160,7 @@ def list_samplers(): def temp_disable_extensions(): disabled = [] if cmd_opts.safe: - for ext in ['sd-webui-controlnet', 'multidiffusion-upscaler-for-automatic1111', 'a1111-sd-webui-lycoris', 'sd-webui-agent-scheduler', 'clip-interrogator-ext', 'stable-diffusion-webui-rembg', 'sd-extension-chainner']: + for ext in ['sd-webui-controlnet', 'multidiffusion-upscaler-for-automatic1111', 'a1111-sd-webui-lycoris', 'sd-webui-agent-scheduler', 'clip-interrogator-ext', 'stable-diffusion-webui-rembg', 'sd-extension-chainner', 'stable-diffusion-webui-images-browser']: if ext not in opts.disabled_extensions: disabled.append(ext) log.info(f'Safe mode disabling extensions: {disabled}') @@ -171,50 +169,10 @@ def temp_disable_extensions(): if ext not in opts.disabled_extensions: disabled.append(ext) log.info(f'Diffusers disabling uncompatible extensions: {disabled}') - if opts.lyco_patch_lora and backend != Backend.DIFFUSERS: - cmd_opts.lyco_dir = opts.lora_dir - if 'Lora' not in opts.disabled_extensions: - disabled.append('Lora') cmd_opts.controlnet_loglevel = 'WARNING' return disabled -def list_builtin_themes(): - files = [os.path.splitext(f)[0] for f in os.listdir('javascript') if f.endswith('.css')] - return files - - -def list_themes(): - fn = os.path.join('html', 'themes.json') - if not os.path.exists(fn): - refresh_themes() - if os.path.exists(fn): - with open(fn, mode='r', encoding='utf=8') as f: - res = json.loads(f.read()) - else: - res = [] - builtin = list_builtin_themes() - default = ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] - external = {x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()} - log.debug(f'Themes: builtin={len(builtin)} default={len(default)} external={len(external)}') - themes = sorted(builtin) + sorted(default) + sorted(external, key=str.casefold) - return themes - - -def refresh_themes(): - try: - r = req('https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/subdomains.json') - if r.status_code == 200: - res = r.json() - fn = os.path.join('html', 'themes.json') - writefile(res, fn) - list_themes() - else: - log.error('Error refreshing UI themes') - except Exception: - log.error('Exception refreshing UI themes') - - def readfile(filename, silent=False): data = {} try: @@ -339,7 +297,7 @@ options_templates.update(options_section(('advanced', "Inference Settings"), { "token_merging_sep": OptionInfo("

Token merging

", "", gr.HTML), "token_merging_ratio": OptionInfo(0.0, "Token merging ratio (txt2img)", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio (img2img)", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), - "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for (hires)", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), + "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio (hires)", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), "freeu_sep": OptionInfo("

FreeU

", "", gr.HTML), "freeu_enabled": OptionInfo(False, "FreeU enabled"), @@ -388,9 +346,9 @@ options_templates.update(options_section(('system-paths', "System Paths"), { "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Folder with stable diffusion models", folder=True), "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Folder with Hugggingface models", folder=True), "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Folder with VAE files", folder=True), - # "sd_lora": OptionInfo("", "Add LoRA to prompt", gr.Textbox, {"visible": False}), + "sd_lora": OptionInfo("", "Add LoRA to prompt", gr.Textbox, {"visible": False}), "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Folder with LoRA network(s)", folder=True), - "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Folder with LyCORIS network(s)", folder=True), + "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Folder with LyCORIS network(s)", gr.Text, {"visible": False}), "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "File or Folder with user-defined styles", folder=True), "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Folder with textual inversion embeddings", folder=True), "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Folder with Hypernetwork models", folder=True), @@ -464,7 +422,7 @@ options_templates.update(options_section(('saving-paths', "Image Naming & Paths" options_templates.update(options_section(('ui', "User Interface"), { "motd": OptionInfo(True, "Show MOTD"), - "gradio_theme": OptionInfo("black-teal", "UI theme", gr.Dropdown, lambda: {"choices": list_themes()}, refresh=refresh_themes), + "gradio_theme": OptionInfo("black-teal", "UI theme", gr.Dropdown, lambda: {"choices": theme.list_themes()}, refresh=theme.refresh_themes), "theme_style": OptionInfo("Auto", "Theme mode", gr.Radio, {"choices": ["Auto", "Dark", "Light"]}), "tooltips": OptionInfo("UI Tooltips", "UI tooltips", gr.Radio, {"choices": ["None", "Browser default", "UI tooltips"], "visible": False}), "compact_view": OptionInfo(False, "Compact view"), @@ -611,7 +569,6 @@ options_templates.update(options_section(('extra_networks', "Extra Networks"), { "lora_preferred_name": OptionInfo("filename", "LoRA preffered name", gr.Radio, {"choices": ["filename", "alias"]}), "lora_add_hashes_to_infotext": OptionInfo(True, "LoRA add hash info"), "lora_in_memory_limit": OptionInfo(0, "LoRA memory cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), - "lyco_patch_lora": OptionInfo(False, "Use LyCoris handler for all LoRA types", gr.Checkbox, { "visible": False }), "lora_functional": OptionInfo(False, "Use Kohya method for handling multiple LoRA", gr.Checkbox, { "visible": False }), "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, { "choices": ["None"], "visible": False }), @@ -809,45 +766,6 @@ if devices.backend == "directml": directml_do_hijack() -def reload_gradio_theme(theme_name=None): - global gradio_theme # pylint: disable=global-statement - if not theme_name: - theme_name = opts.gradio_theme - default_font_params = {} - res = 0 - try: - request = urllib.request.Request("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono", method="HEAD") - res = urllib.request.urlopen(request, timeout=3.0).status # pylint: disable=consider-using-with - except Exception: - res = 0 - if res != 200: - log.info('No internet access detected, using default fonts') - default_font_params = { - 'font':['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], - 'font_mono':['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'] - } - if theme_name in list_builtin_themes(): - gradio_theme = gr.themes.Base(**default_font_params) - elif theme_name.startswith("gradio/"): - if theme_name == "gradio/default": - gradio_theme = gr.themes.Default(**default_font_params) - if theme_name == "gradio/base": - gradio_theme = gr.themes.Base(**default_font_params) - if theme_name == "gradio/glass": - gradio_theme = gr.themes.Glass(**default_font_params) - if theme_name == "gradio/monochrome": - gradio_theme = gr.themes.Monochrome(**default_font_params) - if theme_name == "gradio/soft": - gradio_theme = gr.themes.Soft(**default_font_params) - else: - try: - gradio_theme = gr.themes.ThemeClass.from_hub(theme_name) - except Exception: - log.error("Theme download error accessing HuggingFace") - gradio_theme = gr.themes.Default(**default_font_params) - log.info(f'Loading UI theme: name={theme_name} style={opts.theme_style}') - - class TotalTQDM: # compatibility with previous global-tqdm # import tqdm def __init__(self): diff --git a/modules/styles.py b/modules/styles.py index 2b637800a..ea7c2dc7b 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -124,7 +124,7 @@ class StyleDatabase: list_folder(self.path) self.styles = dict(sorted(self.styles.items(), key=lambda style: style[1].filename)) if self.built_in: - self.load_style(os.path.join(paths.data_path, 'html', 'art-styles.json'), 'built-in') + self.load_style(os.path.join('html', 'art-styles.json'), 'built-in') log.debug(f'Loaded styles: folder={self.path} items={len(self.styles.keys())}') diff --git a/modules/theme.py b/modules/theme.py new file mode 100644 index 000000000..293a174fb --- /dev/null +++ b/modules/theme.py @@ -0,0 +1,88 @@ +import os +import json +import urllib.request +import gradio as gr +import modules.shared +# from modules.shared import log, opts, req, writefile + + +gradio_theme = gr.themes.Base() + + +def list_builtin_themes(): + files = [os.path.splitext(f)[0] for f in os.listdir('javascript') if f.endswith('.css')] + return files + + +def list_themes(): + fn = os.path.join('html', 'themes.json') + if not os.path.exists(fn): + refresh_themes() + if os.path.exists(fn): + with open(fn, mode='r', encoding='utf=8') as f: + res = json.loads(f.read()) + else: + res = [] + builtin = list_builtin_themes() + default = ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] + external = {x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()} + modules.shared.log.debug(f'Themes: builtin={len(builtin)} default={len(default)} external={len(external)}') + themes = sorted(builtin) + sorted(default) + sorted(external, key=str.casefold) + return themes + + +def refresh_themes(): + try: + r = modules.shared.req('https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/subdomains.json') + if r.status_code == 200: + res = r.json() + fn = os.path.join('html', 'themes.json') + modules.shared.writefile(res, fn) + list_themes() + else: + modules.shared.log.error('Error refreshing UI themes') + except Exception: + modules.shared.log.error('Exception refreshing UI themes') + + +def reload_gradio_theme(theme_name=None): + global gradio_theme # pylint: disable=global-statement + if not theme_name: + theme_name = modules.shared.opts.gradio_theme + default_font_params = {} + res = 0 + try: + request = urllib.request.Request("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono", method="HEAD") + res = urllib.request.urlopen(request, timeout=3.0).status # pylint: disable=consider-using-with + except Exception: + res = 0 + if res != 200: + modules.shared.log.info('No internet access detected, using default fonts') + default_font_params = { + 'font':['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], + 'font_mono':['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'] + } + is_builtin = theme_name in list_builtin_themes() + modules.shared.log.info(f'Loading UI theme: name={theme_name} style={modules.shared.opts.theme_style} base={"style.css" if is_builtin else "base.css"}') + if is_builtin: + gradio_theme = gr.themes.Base(**default_font_params) + elif theme_name.startswith("gradio/"): + modules.shared.log.warning('Using Gradio default theme which is not optimized for SD.Next') + if theme_name == "gradio/default": + gradio_theme = gr.themes.Default(**default_font_params) + if theme_name == "gradio/base": + gradio_theme = gr.themes.Base(**default_font_params) + if theme_name == "gradio/glass": + gradio_theme = gr.themes.Glass(**default_font_params) + if theme_name == "gradio/monochrome": + gradio_theme = gr.themes.Monochrome(**default_font_params) + if theme_name == "gradio/soft": + gradio_theme = gr.themes.Soft(**default_font_params) + else: + try: + modules.shared.log.warning('Using 3rd party theme which is not optimized for SD.Next') + gradio_theme = gr.themes.ThemeClass.from_hub(theme_name) + except Exception: + modules.shared.log.error("Theme download error accessing HuggingFace") + gradio_theme = gr.themes.Default(**default_font_params) + return is_builtin diff --git a/modules/ui.py b/modules/ui.py index 5a7573be8..b7edcdaac 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -25,6 +25,7 @@ import modules.shared import modules.errors import modules.styles import modules.extras +import modules.theme import modules.textual_inversion.ui import modules.sd_samplers @@ -250,7 +251,7 @@ def create_toprow(is_img2img): pause = gr.Button('Pause', elem_id=f"{id_part}_pause") pause.click(fn=lambda: modules.shared.state.pause(), _js='checkPaused', inputs=[], outputs=[]) with gr.Row(elem_id=f"{id_part}_tools"): - button_paste = gr.Button(value='Apply', variant='secondary', elem_id="paste") # symbols.paste + button_paste = gr.Button(value='Restore', variant='secondary', elem_id="paste") # symbols.paste button_clear = gr.Button(value='Clear', variant='secondary', elem_id=f"{id_part}_clear_prompt_btn") # symbols.clear button_extra = gr.Button(value='Networks', variant='secondary', elem_id=f"{id_part}_extra_networks_btn") # symbols.networks button_clear.click(fn=lambda *x: ['', ''], inputs=[prompt, negative_prompt], outputs=[prompt, negative_prompt], show_progress=False) @@ -1103,7 +1104,7 @@ def create_ui(startup_timer = None): for _interface, label, _ifid in interfaces: modules.shared.tab_names.append(label) - with gr.Blocks(theme=modules.shared.gradio_theme, analytics_enabled=False, title="SD.Next") as demo: + with gr.Blocks(theme=modules.theme.gradio_theme, analytics_enabled=False, title="SD.Next") as demo: with gr.Row(elem_id="quicksettings", variant="compact"): for _i, k, _item in sorted(quicksettings_list, key=lambda x: quicksettings_names.get(x[1], x[0])): component = create_setting_component(k, is_quicksettings=True) @@ -1226,19 +1227,20 @@ def html_body(): return body -def html_css(): +def html_css(is_builtin: bool): added = [] def stylesheet(fn): added.append(fn) return f'' - head = stylesheet(os.path.join(script_path, 'javascript/style.css')) + css = 'sdnext.css' if is_builtin else 'base.css' + head = stylesheet(os.path.join(script_path, 'javascript', css)) for cssfile in modules.scripts.list_files_with_name("style.css"): if not os.path.isfile(cssfile): continue head += stylesheet(cssfile) - if opts.gradio_theme in modules.shared.list_builtin_themes(): + if opts.gradio_theme in modules.theme.list_builtin_themes(): head += stylesheet(os.path.join(script_path, "javascript", f"{opts.gradio_theme}.css")) if os.path.exists(os.path.join(data_path, "user.css")): head += stylesheet(os.path.join(data_path, "user.css")) @@ -1248,8 +1250,9 @@ def html_css(): def reload_javascript(): + is_builtin = modules.theme.reload_gradio_theme() head = html_head() - css = html_css() + css = html_css(is_builtin) body = html_body() def template_response(*args, **kwargs): diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index cc5101931..0fff0776f 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -23,9 +23,11 @@ allowed_dirs = [] dir_cache = {} # key=path, value=(mtime, listdir(path)) refresh_time = 0 extra_pages = shared.extra_networks +debug = shared.log.info if os.environ.get('SD_EN_DEBUG', None) is not None else lambda *args, **kwargs: None def listdir(path): + debug(f'EN list-dir: {path}') if not os.path.exists(path): return [] if path in dir_cache and os.path.getmtime(path) == dir_cache[path][0]: @@ -37,6 +39,7 @@ def listdir(path): def register_page(page): # registers extra networks page for the UI; recommend doing it in on_before_ui() callback for extensions + debug(f'EN register-page: {page}') shared.extra_networks.append(page) allowed_dirs.clear() for pg in shared.extra_networks: @@ -165,6 +168,7 @@ class ExtraNetworksPage: return True def create_thumb(self): + debug(f'EN create-thumb: {self.name}') created = 0 for f in self.missing_thumbs: if not os.path.exists(f): @@ -197,6 +201,7 @@ class ExtraNetworksPage: self.missing_thumbs.clear() def create_items(self, tabname): + debug(f'EN create-items: {self.name}') if self.refresh_time is not None and self.refresh_time > refresh_time: # cached results return t0 = time.time() @@ -213,6 +218,7 @@ class ExtraNetworksPage: def create_page(self, tabname, skip = False): + debug(f'EN create-page: {self.name}') if self.page_time > refresh_time: # cached page return self.html self_name_id = self.name.replace(" ", "_") @@ -249,7 +255,8 @@ class ExtraNetworksPage: else: return '' shared.log.debug(f"Extra networks: page='{self.name}' items={len(self.items)} subdirs={len(subdirs)} tab={tabname} dirs={self.allowed_directories_for_previews()} time={self.list_time}s") - threading.Thread(target=self.create_thumb).start() + if len(self.missing_thumbs) > 0: + threading.Thread(target=self.create_thumb).start() return self.html def list_items(self): @@ -394,6 +401,7 @@ class ExtraNetworksUi: def create_ui(container, button_parent, tabname, skip_indexing = False): + debug(f'EN create-ui: {tabname}') ui = ExtraNetworksUi() ui.tabname = tabname ui.pages = [] @@ -517,8 +525,11 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): if image.width > 512 or image.height > 512: image = image.convert('RGB') image.thumbnail((512, 512), Image.HAMMING) - image.save(ui.last_item.local_preview, quality=50) - shared.log.debug(f'Extra network save image: item={ui.last_item.name} filename={ui.last_item.local_preview}') + try: + image.save(ui.last_item.local_preview, quality=50) + shared.log.debug(f'Extra network save image: item={ui.last_item.name} filename={ui.last_item.local_preview}') + except Exception as e: + shared.log.error(f'Extra network save image: item={ui.last_item.name} filename={ui.last_item.local_preview} {e}') return image def fn_delete_img(): diff --git a/modules/ui_models.py b/modules/ui_models.py index 817ec607d..717fe15d8 100644 --- a/modules/ui_models.py +++ b/modules/ui_models.py @@ -143,7 +143,7 @@ def create_ui(): with gr.Row(): model_list_btn = gr.Button(value="List model details", variant='primary') - model_checkhash_btn = gr.Button(value="Calculate hash for all models (may take a long time)", variant='primary') + model_checkhash_btn = gr.Button(value="Calculate hash for all models", variant='primary') model_checkhash_btn.click(fn=sd_models.update_model_hashes, inputs=[], outputs=[models_outcome]) with gr.Row(): model_table = gr.DataFrame( diff --git a/modules/upscaler.py b/modules/upscaler.py index 45cdef63a..b05862f78 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -64,6 +64,8 @@ class Upscaler: scalers.append(scaler) loaded.append(model_path) # modules.shared.log.debug(f'Upscaler type={self.name} folder="{self.user_path}" model="{model[0]}" path="{model_path}"') + if not os.path.exists(self.user_path): + return scalers for fn in os.listdir(self.user_path): # from folder if not fn.endswith('.pth') and not fn.endswith('.pt'): continue diff --git a/requirements.txt b/requirements.txt index 7d5e41aef..ffb5dacc3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -43,7 +43,6 @@ fasteners dctorch compel==2.0.2 torchsde==0.2.6 -typing-extensions==4.7.1 antlr4-python3-runtime==4.9.3 requests==2.31.0 tqdm==4.66.1 @@ -64,4 +63,5 @@ tomesd==0.1.3 urllib3==1.26.15 Pillow==9.5.0 timm==0.9.7 -pydantic==1.10.11 +pydantic==1.10.13 +typing-extensions==4.8.0 diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 3a251e668..81ef716d1 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -225,27 +225,32 @@ axis_options = [ AxisOption("Clip skip", int, apply_clip_skip), AxisOption("Denoising strength", float, apply_field("denoising_strength")), AxisOption("Prompt order", str_permutations, apply_order, fmt=format_value_join_list), - AxisOption("Upscaler", str, apply_upscaler, choices=lambda: [x.name for x in shared.sd_upscalers][1:]), - AxisOption("Face restore", str, apply_face_restore, fmt=format_value), - AxisOption("Token merging ratio (txt2img)", float, apply_override('token_merging_ratio')), - AxisOption("Token merging ratio (hires)", float, apply_override('token_merging_ratio_hr')), + AxisOption("[Postprocess] Upscaler", str, apply_upscaler, choices=lambda: [x.name for x in shared.sd_upscalers][1:]), + AxisOption("[Postprocess] Face restore", str, apply_face_restore, fmt=format_value), AxisOptionImg2Img("Image mask weight", float, apply_field("inpainting_mask_weight")), AxisOption("Model dictionary", str, apply_dict, fmt=format_value, cost=1.0, choices=lambda: ['None'] + list(sd_models.checkpoints_list)), - AxisOption("Sampler sigma min", float, apply_field("s_min")), - AxisOption("Sampler sigma max", float, apply_field("s_max")), - AxisOption("Sampler sigma tmin", float, apply_field("s_tmin")), - AxisOption("Sampler sigma tmax", float, apply_field("s_tmax")), - AxisOption("Sampler sigma Churn", float, apply_field("s_churn")), - AxisOption("Sampler sigma noise", float, apply_field("s_noise")), - AxisOption("Sampler eta", float, apply_field("eta")), - AxisOption("Sampler solver order", int, apply_setting("schedulers_solver_order")), - AxisOption("SecondPass upscaler", str, apply_field("hr_upscaler"), choices=lambda: [*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]]), - AxisOption("SecondPass sampler", str, apply_latent_sampler, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), - AxisOption("SecondPass denoising Strength", float, apply_field("denoising_strength")), - AxisOption("SecondPass steps", int, apply_field("hr_second_pass_steps")), - AxisOption("SecondPass CFG scale", float, apply_field("image_cfg_scale")), - AxisOption("SecondPass guidance rescale", float, apply_field("diffusers_guidance_rescale")), - AxisOption("SecondPass refiner start", float, apply_field("refiner_start")), + AxisOption("[Sampler] sigma min", float, apply_field("s_min")), + AxisOption("[Sampler] sigma max", float, apply_field("s_max")), + AxisOption("[Sampler] sigma tmin", float, apply_field("s_tmin")), + AxisOption("[Sampler] sigma tmax", float, apply_field("s_tmax")), + AxisOption("[Sampler] sigma Churn", float, apply_field("s_churn")), + AxisOption("[Sampler] sigma noise", float, apply_field("s_noise")), + AxisOption("[Sampler] eta", float, apply_field("eta")), + AxisOption("[Sampler] solver order", int, apply_setting("schedulers_solver_order")), + AxisOption("[Second pass] upscaler", str, apply_field("hr_upscaler"), choices=lambda: [*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]]), + AxisOption("[Second pass] sampler", str, apply_latent_sampler, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), + AxisOption("[Second pass] denoising Strength", float, apply_field("denoising_strength")), + AxisOption("[Second pass] steps", int, apply_field("hr_second_pass_steps")), + AxisOption("[Second pass] CFG scale", float, apply_field("image_cfg_scale")), + AxisOption("[Second pass] guidance rescale", float, apply_field("diffusers_guidance_rescale")), + AxisOption("[Second pass] refiner start", float, apply_field("refiner_start")), + AxisOption("[Second pass] refiner start", float, apply_field("refiner_start")), + AxisOption("[TOME] Token merging ratio (txt2img)", float, apply_override('token_merging_ratio')), + AxisOption("[TOME] Token merging ratio (hires)", float, apply_override('token_merging_ratio_hr')), + AxisOption("[FreeU] 1st stage backbone factor", float, apply_setting('freeu_b1')), + AxisOption("[FreeU] 2nd stage backbone factor", float, apply_setting('freeu_b2')), + AxisOption("[FreeU] 1st stage skip factor", float, apply_setting('freeu_s1')), + AxisOption("[FreeU] 2nd stage skip factor", float, apply_setting('freeu_s2')), ] diff --git a/webui.py b/webui.py index fafa3aca8..daab8d82b 100644 --- a/webui.py +++ b/webui.py @@ -100,6 +100,7 @@ def initialize(): import modules.postprocess.codeformer_model as codeformer codeformer.setup_model(opts.codeformer_models_path) + sys.modules["modules.codeformer_model"] = codeformer import modules.postprocess.gfpgan_model as gfpgan gfpgan.setup_model(opts.gfpgan_models_path) timer.startup.record("face-restore") @@ -115,7 +116,7 @@ def initialize(): shared.opts.onchange("sd_vae", wrap_queued_call(lambda: modules.sd_vae.reload_vae_weights()), call=False) shared.opts.onchange("temp_dir", ui_tempdir.on_tmpdir_changed) - shared.opts.onchange("gradio_theme", shared.reload_gradio_theme) + # shared.opts.onchange("gradio_theme", shared.reload_gradio_theme) timer.startup.record("onchange") modules.textual_inversion.textual_inversion.list_textual_inversion_templates() @@ -215,7 +216,7 @@ def start_common(): log.info(f'Using data path: {shared.cmd_opts.data_dir}') if shared.cmd_opts.models_dir is not None and len(shared.cmd_opts.models_dir) > 0 and shared.cmd_opts.models_dir != 'models': log.info(f'Using models path: {shared.cmd_opts.models_dir}') - create_paths(opts, log) + create_paths(opts) async_policy() initialize() if shared.opts.clean_temp_dir_at_start: diff --git a/wiki b/wiki index 128faf8d7..58e8d11f9 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 128faf8d7fc9e8f94376fb211b0e44f30d15638f +Subproject commit 58e8d11f94503d86f0b9b3f73046c5996a2fc000