diff --git a/CHANGES.md b/CHANGES.md index 389ea7b7..b6a56186 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,8 @@ - **Major rewrite of the code** - We've switched to using diffusers under-the-hood, which allows us to release new features faster, and focus on making the UI and installer even easier to use. ### Detailed changelog +* 3.0.9 - 28 May 2024 - Slider for controlling the strength of controlnets. +* 3.0.8 - 27 May 2024 - SDXL ControlNets for Img2Img and Inpainting. * 3.0.7 - 11 Dec 2023 - Setting to enable/disable VAE tiling (in the Image Settings panel). Sometimes VAE tiling reduces the quality of the image, so this setting will help control that. * 3.0.6 - 18 Sep 2023 - Add thumbnails to embeddings from the UI, using the new `Upload Thumbnail` button in the Embeddings popup. Thanks @JeLuf. * 3.0.6 - 15 Sep 2023 - Fix broken embeddings dialog when LoRA information couldn't be fetched. diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 480d4e62..9a067e68 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -15,13 +15,16 @@ import traceback import shutil from pathlib import Path from pprint import pprint +import re os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0", "2.0.1"), "torchvision": ("0.12.0", "0.14.1", "0.15.1", "0.15.2"), - "sdkit": "2.0.15", + "setuptools": "69.5.1", + # "sdkit": "2.0.15.6", # checked later + # "diffusers": "0.21.4", # checked later "stable-diffusion-sdkit": "2.1.5", "rich": "12.6.0", "uvicorn": "0.19.0", @@ -32,7 +35,7 @@ modules_to_check = { "python-multipart": "0.0.6", # "xformers": "0.0.16", } -modules_to_log = ["torch", "torchvision", "sdkit", "stable-diffusion-sdkit"] +modules_to_log = ["torch", "torchvision", "sdkit", "stable-diffusion-sdkit", "diffusers"] def version(module_name: str) -> str: @@ -91,6 +94,14 @@ def update_modules(): allowed_versions, latest_version = get_allowed_versions(module_name, allowed_versions) + if module_name == "setuptools": + if os_name == "Windows": + allowed_versions = ("59.8.0",) + latest_version = "59.8.0" + else: + allowed_versions = ("69.0.0",) + latest_version = "69.0.0" + requires_install = False if module_name in ("torch", "torchvision"): if version(module_name) is None: # allow any torch version @@ -114,8 +125,110 @@ def update_modules(): f"WARNING! Tried to install {module_name}=={latest_version}, but the version is still {version(module_name)}!" ) - if module_name in modules_to_log: - print(f"{module_name}: {version(module_name)}") + # different sdkit versions, with the corresponding diffusers + # if sdkit is 2.0.15.x (or lower), then diffusers should be restricted to 0.21.4 (see below for the reason) + # otherwise use the current sdkit version (with the corresponding diffusers version) + + expected_sdkit_version_str = "2.0.20.4" + expected_diffusers_version_str = "0.28.2" + + legacy_sdkit_version_str = "2.0.15.7" + legacy_diffusers_version_str = "0.21.4" + + sdkit_version_str = version("sdkit") + if sdkit_version_str is None: # first install + _install("sdkit", expected_sdkit_version_str) + _install("diffusers", expected_diffusers_version_str) + else: + sdkit_version = version_str_to_tuple(sdkit_version_str) + legacy_sdkit_version = version_str_to_tuple(legacy_sdkit_version_str) + + if sdkit_version[:3] <= legacy_sdkit_version[:3]: # and torch_version < (0, 13): + # stick to diffusers 0.21.4, since it preserves torch 0.11+ compatibility. + # upgrading beyond this will result in a 2+ GB download of torch on older installations + # and a time-consuming chain of small package updates due to huggingface_hub upgrade. + # for now, the user will need to explicitly upgrade to a newer sdkit, to break this ceiling. + + install_pkg_if_necessary("sdkit", legacy_sdkit_version_str) + install_pkg_if_necessary("diffusers", legacy_diffusers_version_str) + else: + torch_version = version_str_to_tuple(version("torch")) + if torch_version < (1, 13): + # install the gpu-compatible torch (if necessary), instead of the default CPU-only one + # from the diffusers dependency chain + install("torch", modules_to_check["torch"][-1]) + install("torchvision", modules_to_check["torchvision"][-1]) + + install_pkg_if_necessary("sdkit", expected_sdkit_version_str) + install_pkg_if_necessary("diffusers", expected_diffusers_version_str) + + # hotfix accelerate + accelerate_version = version("accelerate") + if accelerate_version is None: + install("accelerate", "0.23.0") + else: + accelerate_version = accelerate_version.split(".") + accelerate_version = tuple(map(int, accelerate_version)) + if accelerate_version < (0, 23): + install("accelerate", "0.23.0") + + # hotfix - 29 May 2024. sdkit has stopped pulling its dependencies for some reason + # temporarily dumping sdkit's requirements here: + if os_name != "Windows": + sdkit_deps = [ + "gfpgan", + "piexif", + "realesrgan", + "requests", + "picklescan", + "safetensors==0.3.3", + "k-diffusion==0.0.12", + "compel==2.0.1", + "controlnet-aux==0.0.6", + "invisible-watermark==0.2.0", # required for SD XL + ] + + for mod in sdkit_deps: + mod_name = mod + mod_force_version_str = None + if "==" in mod: + mod_name, mod_force_version_str = mod.split("==") + + curr_mod_version_str = version(mod_name) + if curr_mod_version_str is None: + _install(mod_name, mod_force_version_str) + elif mod_force_version_str is not None: + curr_mod_version = version_str_to_tuple(curr_mod_version_str) + mod_force_version = version_str_to_tuple(mod_force_version_str) + + if curr_mod_version != mod_force_version: + _install(mod_name, mod_force_version_str) + + for module_name in modules_to_log: + print(f"{module_name}: {version(module_name)}") + + +def _install(module_name, module_version=None): + if module_version is None: + install_cmd = f"python -m pip install {module_name}" + else: + install_cmd = f"python -m pip install --upgrade {module_name}=={module_version}" + + print(">", install_cmd) + os.system(install_cmd) + + +def install_pkg_if_necessary(pkg_name, required_version): + pkg_version = version(pkg_name) + if pkg_version != required_version: + _install(pkg_name, required_version) + + +def version_str_to_tuple(ver_str): + ver_str = ver_str.split("+")[0] + ver_str = re.sub("[^0-9.]", "", ver_str) + ver = ver_str.split(".") + return tuple(map(int, ver)) ### utilities @@ -302,8 +415,16 @@ def launch_uvicorn(): setup_amd_environment() print("\nLaunching uvicorn\n") - os.system( - f'python -m uvicorn main:server_api --app-dir "{os.environ["SD_UI_PATH"]}" --port {listen_port} --host {bind_ip} --log-level error' + + import uvicorn + + uvicorn.run( + "main:server_api", + port=listen_port, + log_level="error", + app_dir=os.environ["SD_UI_PATH"], + host=bind_ip, + access_log=False, ) diff --git a/ui/easydiffusion/device_manager.py b/ui/easydiffusion/device_manager.py index dc705927..9b0f3f5e 100644 --- a/ui/easydiffusion/device_manager.py +++ b/ui/easydiffusion/device_manager.py @@ -243,7 +243,8 @@ def get_processor_name(): if platform.system() == "Windows": return platform.processor() elif platform.system() == "Darwin": - os.environ["PATH"] = os.environ["PATH"] + os.pathsep + "/usr/sbin" + if "/usr/sbin" not in os.environ["PATH"].split(os.pathsep): + os.environ["PATH"] = os.environ["PATH"] + os.pathsep + "/usr/sbin" command = "sysctl -n machdep.cpu.brand_string" return subprocess.check_output(command, shell=True).decode().strip() elif platform.system() == "Linux": diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py index 4bf66261..95298d47 100644 --- a/ui/easydiffusion/utils/save_utils.py +++ b/ui/easydiffusion/utils/save_utils.py @@ -31,6 +31,7 @@ TASK_TEXT_MAPPING = { "clip_skip": "Clip Skip", "use_controlnet_model": "ControlNet model", "control_filter_to_apply": "ControlNet Filter", + "control_alpha": "ControlNet Strength", "use_vae_model": "VAE model", "sampler_name": "Sampler", "width": "Width", diff --git a/ui/index.html b/ui/index.html index 216a8ee5..e81b6a5c 100644 --- a/ui/index.html +++ b/ui/index.html @@ -36,7 +36,7 @@

Easy Diffusion - v3.0.7 + v3.0.9

@@ -239,6 +239,8 @@
+
+
diff --git a/ui/media/js/auto-save.js b/ui/media/js/auto-save.js index aa05536b..cacf33e8 100644 --- a/ui/media/js/auto-save.js +++ b/ui/media/js/auto-save.js @@ -59,6 +59,7 @@ const SETTINGS_IDS_LIST = [ "embedding-card-size-selector", "lora_model", "enable_vae_tiling", + "controlnet_alpha", ] const IGNORE_BY_DEFAULT = ["prompt"] diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index cf082406..5cb517fe 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -309,10 +309,21 @@ const TASK_MAPPING = { readUI: () => controlImageFilterField.value, parse: (val) => val, }, + control_alpha: { + name: "ControlNet Strength", + setUI: (control_alpha) => { + control_alpha = control_alpha || 1.0 + controlAlphaField.value = control_alpha + updateControlAlphaSlider() + }, + readUI: () => parseFloat(controlAlphaField.value), + parse: (val) => val === null ? 1.0 : parseFloat(val), + }, use_lora_model: { name: "LoRA model", setUI: (use_lora_model) => { let modelPaths = [] + use_lora_model = use_lora_model === null ? "" : use_lora_model use_lora_model = Array.isArray(use_lora_model) ? use_lora_model : [use_lora_model] use_lora_model.forEach((m) => { if (m.includes("models\\lora\\")) { @@ -529,6 +540,11 @@ function restoreTaskToUI(task, fieldsToSkip) { // listen for inpainter loading event, which happens AFTER the main image loads (which reloads the inpai controlImagePreview.src = task.reqBody.control_image } + + if ("use_controlnet_model" in task.reqBody && task.reqBody.use_controlnet_model && !("control_alpha" in task.reqBody)) { + controlAlphaField.value = 1.0 + updateControlAlphaSlider() + } } function readUI() { const reqBody = {} @@ -587,6 +603,7 @@ const TASK_TEXT_MAPPING = { lora_alpha: "LoRA Strength", use_controlnet_model: "ControlNet model", control_filter_to_apply: "ControlNet Filter", + control_alpha: "ControlNet Strength", tiling: "Seamless Tiling", } function parseTaskFromText(str) { diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 12704876..3f3403ea 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -51,6 +51,10 @@ const taskConfigSetup = { preserve_init_image_color_profile: "Preserve Color Profile", strict_mask_border: "Strict Mask Border", use_controlnet_model: "ControlNet Model", + control_alpha: { + label: "ControlNet Strength", + visible: ({ reqBody }) => !!reqBody?.use_controlnet_model, + }, }, pluginTaskConfig: {}, getCSSKey: (key) => @@ -99,6 +103,8 @@ let controlImagePreview = document.querySelector("#control_image_preview") let controlImageClearBtn = document.querySelector(".control_image_clear") let controlImageContainer = document.querySelector("#control_image_wrapper") let controlImageFilterField = document.querySelector("#control_image_filter") +let controlAlphaSlider = document.querySelector("#controlnet_alpha_slider") +let controlAlphaField = document.querySelector("#controlnet_alpha") let applyColorCorrectionField = document.querySelector("#apply_color_correction") let strictMaskBorderField = document.querySelector("#strict_mask_border") let colorCorrectionSetting = document.querySelector("#apply_color_correction_setting") @@ -1753,6 +1759,7 @@ function getCurrentUserRequest() { if (controlnetModelField.value !== "" && IMAGE_REGEX.test(controlImagePreview.src)) { newTask.reqBody.use_controlnet_model = controlnetModelField.value newTask.reqBody.control_image = controlImagePreview.src + newTask.reqBody.control_alpha = parseFloat(controlAlphaField.value) if (controlImageFilterField.value !== "") { newTask.reqBody.control_filter_to_apply = controlImageFilterField.value } @@ -2374,6 +2381,27 @@ function updateHypernetworkStrengthContainer() { hypernetworkModelField.addEventListener("change", updateHypernetworkStrengthContainer) updateHypernetworkStrengthContainer() +/********************* Controlnet Alpha **************************/ +function updateControlAlpha() { + controlAlphaField.value = controlAlphaSlider.value / 10 + controlAlphaField.dispatchEvent(new Event("change")) +} + +function updateControlAlphaSlider() { + if (controlAlphaField.value < 0) { + controlAlphaField.value = 0 + } else if (controlAlphaField.value > 10) { + controlAlphaField.value = 10 + } + + controlAlphaSlider.value = controlAlphaField.value * 10 + controlAlphaSlider.dispatchEvent(new Event("change")) +} + +controlAlphaSlider.addEventListener("input", updateControlAlpha) +controlAlphaField.addEventListener("input", updateControlAlphaSlider) +updateControlAlpha() + /********************* JPEG/WEBP Quality **********************/ function updateOutputQuality() { outputQualityField.value = 0 | outputQualitySlider.value