From 596951846da9346298d637f25800b787ce1875a0 Mon Sep 17 00:00:00 2001 From: Dowon Date: Sat, 13 Apr 2024 15:23:25 +0900 Subject: [PATCH 01/19] chore(pre-commit): update, black -> ruff format --- .pre-commit-config.yaml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 07062c0..c3b0f8d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,18 +15,14 @@ repos: - id: end-of-file-fixer - id: mixed-line-ending - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.5 - hooks: - - id: ruff - args: [--fix, --exit-non-zero-on-fix] - - repo: https://github.com/pre-commit/mirrors-prettier rev: "v4.0.0-alpha.8" hooks: - id: prettier - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.3.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.7 hooks: - - id: black + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format From e390875198271ef6f2e16469fd159f4fbad694a2 Mon Sep 17 00:00:00 2001 From: Dowon Date: Sat, 13 Apr 2024 15:48:27 +0900 Subject: [PATCH 02/19] fix(scripts): fix SCRIPT_DEFAULT --- adetailer/args.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/adetailer/args.py b/adetailer/args.py index 5dd4577..da21d80 100644 --- a/adetailer/args.py +++ b/adetailer/args.py @@ -247,3 +247,14 @@ BBOX_SORTBY = [ "Area (large to small)", ] MASK_MERGE_INVERT = ["None", "Merge", "Merge and Invert"] + +_script_default = [ + "dynamic_prompting", + "dynamic_thresholding", + "wildcard_recursive", + "wildcards", + "lora_block_weight", + "negpip", + "soft_inpainting", +] +SCRIPT_DEFAULT = "".join(sorted(_script_default)) From 89ee33027166297291f431ba8280f97a3a1eb587 Mon Sep 17 00:00:00 2001 From: Dowon Date: Sat, 13 Apr 2024 15:49:03 +0900 Subject: [PATCH 03/19] fix(scripts): fix paths --- adetailer/common.py | 22 +++++++++++++++++----- scripts/!adetailer.py | 24 +++++++++++------------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/adetailer/common.py b/adetailer/common.py index 5fc6b53..dfd7952 100644 --- a/adetailer/common.py +++ b/adetailer/common.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os from collections import OrderedDict from dataclasses import dataclass, field from pathlib import Path @@ -37,16 +38,27 @@ def hf_download(file: str, repo_id: str = REPO_ID) -> str | None: return path -def scan_model_dir(path_: str | Path) -> list[Path]: - if not path_ or not (path := Path(path_)).is_dir(): +def safe_mkdir(path: str | os.PathLike[str]) -> None: + path = Path(path) + if not path.exists() and path.parent.exists() and os.access(path.parent, os.W_OK): + path.mkdir() + + +def scan_model_dir(path: Path) -> list[Path]: + if not path.is_dir(): return [] - return [p for p in path.rglob("*") if p.is_file() and p.suffix in (".pt", ".pth")] + return [p for p in path.rglob("*") if p.is_file() and p.suffix == ".pt"] def get_models( - model_dir: str | Path, extra_dir: str | Path = "", huggingface: bool = True + *dirs: str | os.PathLike[str], huggingface: bool = True ) -> OrderedDict[str, str]: - model_paths = [*scan_model_dir(model_dir), *scan_model_dir(extra_dir)] + model_paths = [] + + for dir_ in dirs: + if not dir_: + continue + model_paths.extend(scan_model_dir(Path(dir_))) models = OrderedDict() if huggingface: diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index fddb0bf..eb46a1d 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -1,6 +1,5 @@ from __future__ import annotations -import os import platform import re import sys @@ -10,7 +9,7 @@ from copy import copy from functools import partial from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Any, NamedTuple +from typing import TYPE_CHECKING, Any, NamedTuple, cast import gradio as gr import torch @@ -25,8 +24,8 @@ from adetailer import ( mediapipe_predict, ultralytics_predict, ) -from adetailer.args import BBOX_SORTBY, ADetailerArgs, SkipImg2ImgOrig -from adetailer.common import PredictOutput, ensure_pil_image +from adetailer.args import BBOX_SORTBY, SCRIPT_DEFAULT, ADetailerArgs, SkipImg2ImgOrig +from adetailer.common import PredictOutput, ensure_pil_image, safe_mkdir from adetailer.mask import ( filter_by_ratio, filter_k_largest, @@ -69,19 +68,18 @@ if TYPE_CHECKING: no_huggingface = getattr(cmd_opts, "ad_no_huggingface", False) adetailer_dir = Path(paths.models_path, "adetailer") +safe_mkdir(adetailer_dir) + extra_models_dir = shared.opts.data.get("ad_extra_models_dir", "") model_mapping = get_models( - adetailer_dir, extra_dir=extra_models_dir, huggingface=not no_huggingface + adetailer_dir, + extra_models_dir, + huggingface=not no_huggingface, ) -txt2img_submit_button = img2img_submit_button = None -SCRIPT_DEFAULT = "dynamic_prompting,dynamic_thresholding,wildcard_recursive,wildcards,lora_block_weight,negpip,soft_inpainting" -if ( - not adetailer_dir.exists() - and adetailer_dir.parent.exists() - and os.access(adetailer_dir.parent, os.W_OK) -): - adetailer_dir.mkdir() +txt2img_submit_button = img2img_submit_button = None +txt2img_submit_button = cast(gr.Button, txt2img_submit_button) +img2img_submit_button = cast(gr.Button, img2img_submit_button) print( f"[-] ADetailer initialized. version: {__version__}, num models: {len(model_mapping)}" From 7d7dfb76a509405342e0579b587ba2f7a8a44fa1 Mon Sep 17 00:00:00 2001 From: Dowon Date: Sat, 13 Apr 2024 16:04:40 +0900 Subject: [PATCH 04/19] fix(scripts): move contextmanagers --- aaaaaa/__init__.py | 0 aaaaaa/helper.py | 55 ++++++++++++++++++++++++++++++++++++++++ adetailer/__version__.py | 2 +- scripts/!adetailer.py | 37 +++------------------------ 4 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 aaaaaa/__init__.py create mode 100644 aaaaaa/helper.py diff --git a/aaaaaa/__init__.py b/aaaaaa/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aaaaaa/helper.py b/aaaaaa/helper.py new file mode 100644 index 0000000..aa9f7f6 --- /dev/null +++ b/aaaaaa/helper.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from contextlib import contextmanager +from copy import copy +from typing import TYPE_CHECKING + +import torch + +from modules import safe +from modules.shared import opts + +if TYPE_CHECKING: + # 타입 체커가 빨간 줄을 긋지 않게 하는 편법 + from types import SimpleNamespace + + StableDiffusionProcessingTxt2Img = SimpleNamespace + StableDiffusionProcessingImg2Img = SimpleNamespace +else: + from modules.processing import ( + StableDiffusionProcessingImg2Img, + StableDiffusionProcessingTxt2Img, + ) + +PT = StableDiffusionProcessingTxt2Img | StableDiffusionProcessingImg2Img + + +@contextmanager +def change_torch_load(): + orig = torch.load + try: + torch.load = safe.unsafe_torch_load + yield + finally: + torch.load = orig + + +@contextmanager +def pause_total_tqdm(): + orig = opts.data.get("multiple_tqdm", True) + try: + opts.data["multiple_tqdm"] = False + yield + finally: + opts.data["multiple_tqdm"] = orig + + +@contextmanager +def preseve_prompts(p: PT): + all_pt = copy(p.all_prompts) + all_ng = copy(p.all_negative_prompts) + try: + yield + finally: + p.all_prompts = all_pt + p.all_negative_prompts = all_ng diff --git a/adetailer/__version__.py b/adetailer/__version__.py index b43269d..4e34747 100644 --- a/adetailer/__version__.py +++ b/adetailer/__version__.py @@ -1 +1 @@ -__version__ = "24.4.0" +__version__ = "24.4.1-dev.0" diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index eb46a1d..36b8c34 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -4,7 +4,7 @@ import platform import re import sys import traceback -from contextlib import contextmanager, suppress +from contextlib import suppress from copy import copy from functools import partial from pathlib import Path @@ -12,11 +12,11 @@ from textwrap import dedent from typing import TYPE_CHECKING, Any, NamedTuple, cast import gradio as gr -import torch from PIL import Image, ImageChops from rich import print import modules +from aaaaaa.helper import change_torch_load, pause_total_tqdm, preseve_prompts from adetailer import ( AFTER_DETAILER, __version__, @@ -44,7 +44,7 @@ from controlnet_ext import ( controlnet_type, get_cn_models, ) -from modules import images, paths, safe, script_callbacks, scripts, shared +from modules import images, paths, script_callbacks, scripts, shared from modules.devices import NansException from modules.processing import ( Processed, @@ -86,37 +86,6 @@ print( ) -@contextmanager -def change_torch_load(): - orig = torch.load - try: - torch.load = safe.unsafe_torch_load - yield - finally: - torch.load = orig - - -@contextmanager -def pause_total_tqdm(): - orig = opts.data.get("multiple_tqdm", True) - try: - opts.data["multiple_tqdm"] = False - yield - finally: - opts.data["multiple_tqdm"] = orig - - -@contextmanager -def preseve_prompts(p): - all_pt = copy(p.all_prompts) - all_ng = copy(p.all_negative_prompts) - try: - yield - finally: - p.all_prompts = all_pt - p.all_negative_prompts = all_ng - - class AfterDetailerScript(scripts.Script): def __init__(self): super().__init__() From 890aa5ab34144dcca92e19ef92d310d5082006ff Mon Sep 17 00:00:00 2001 From: Dowon Date: Sat, 13 Apr 2024 16:32:00 +0900 Subject: [PATCH 05/19] fix(scripts): remove spaces on both side of [PROMPT] --- scripts/!adetailer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 36b8c34..5e0a5f1 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -290,7 +290,7 @@ class AfterDetailerScript(scripts.Script): if not prompts[n]: prompts[n] = blank_replacement elif "[PROMPT]" in prompts[n]: - prompts[n] = prompts[n].replace("[PROMPT]", f" {blank_replacement} ") + prompts[n] = prompts[n].replace("[PROMPT]", blank_replacement) for pair in replacements: prompts[n] = prompts[n].replace(pair.s, pair.r) From cdd758fa630b1abfa9c1b4ed490f04378e0f2df6 Mon Sep 17 00:00:00 2001 From: Dowon Date: Sat, 13 Apr 2024 18:37:12 +0900 Subject: [PATCH 06/19] fix(scripts): fix script default join string --- adetailer/args.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adetailer/args.py b/adetailer/args.py index da21d80..e67f543 100644 --- a/adetailer/args.py +++ b/adetailer/args.py @@ -248,7 +248,7 @@ BBOX_SORTBY = [ ] MASK_MERGE_INVERT = ["None", "Merge", "Merge and Invert"] -_script_default = [ +_script_default = ( "dynamic_prompting", "dynamic_thresholding", "wildcard_recursive", @@ -256,5 +256,5 @@ _script_default = [ "lora_block_weight", "negpip", "soft_inpainting", -] -SCRIPT_DEFAULT = "".join(sorted(_script_default)) +) +SCRIPT_DEFAULT = ",".join(sorted(_script_default)) From b1cafa9425ddbd122c63a396cd1dfe3ccb258f49 Mon Sep 17 00:00:00 2001 From: Natans <52584161+Natans8@users.noreply.github.com> Date: Sat, 13 Apr 2024 18:28:32 +0300 Subject: [PATCH 07/19] =?UTF-8?q?Made=20=D0=A1ontrolNet=20model=20RegEx=20?= =?UTF-8?q?case=20insensitive=20(#577)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controlnet_ext/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controlnet_ext/common.py b/controlnet_ext/common.py index beeb60e..b78dbf3 100644 --- a/controlnet_ext/common.py +++ b/controlnet_ext/common.py @@ -8,4 +8,4 @@ cn_model_module = { "tile": "tile_resample", "depth": "depth_midas", } -cn_model_regex = re.compile("|".join(cn_model_module.keys())) +cn_model_regex = re.compile("|".join(cn_model_module.keys()), flags=re.I) From a2a49b3137f4d12f917afe44425cbb42c4ad0c4c Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 00:37:14 +0900 Subject: [PATCH 08/19] fix(scripts): move some p funcs --- aaaaaa/p_method.py | 30 ++++++++++++++++++++++++ scripts/!adetailer.py | 54 +++++++++++++------------------------------ 2 files changed, 46 insertions(+), 38 deletions(-) create mode 100644 aaaaaa/p_method.py diff --git a/aaaaaa/p_method.py b/aaaaaa/p_method.py new file mode 100644 index 0000000..9a87e7c --- /dev/null +++ b/aaaaaa/p_method.py @@ -0,0 +1,30 @@ +from __future__ import annotations + + +def need_call_process(p) -> bool: + if p.scripts is None: + return False + i = p.batch_index + bs = p.batch_size + return i == bs - 1 + + +def need_call_postprocess(p) -> bool: + if p.scripts is None: + return False + return p.batch_index == 0 + + +def is_img2img_inpaint(p) -> bool: + return hasattr(p, "image_mask") and p.image_mask is not None + + +def is_inpaint_only_masked(p) -> bool: + return hasattr(p, "inpaint_full_res") and p.inpaint_full_res + + +def get_i(p) -> int: + it = p.iteration + bs = p.batch_size + i = p.batch_index + return it * bs + i diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 5e0a5f1..2c6cc90 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -17,6 +17,13 @@ from rich import print import modules from aaaaaa.helper import change_torch_load, pause_total_tqdm, preseve_prompts +from aaaaaa.p_method import ( + get_i, + is_img2img_inpaint, + is_inpaint_only_masked, + need_call_postprocess, + need_call_process, +) from adetailer import ( AFTER_DETAILER, __version__, @@ -191,7 +198,7 @@ class AfterDetailerScript(scripts.Script): if not p._ad_skip_img2img: return - if self.is_img2img_inpaint(p): + if is_img2img_inpaint(p): p._ad_disabled = True msg = "[-] ADetailer: img2img inpainting with skip img2img is not supported. (because it's buggy)" print(msg) @@ -208,13 +215,6 @@ class AfterDetailerScript(scripts.Script): p.width = 128 p.height = 128 - @staticmethod - def get_i(p) -> int: - it = p.iteration - bs = p.batch_size - i = p.batch_index - return it * bs + i - def get_args(self, p, *args_) -> list[ADetailerArgs]: """ `args_` is at least 1 in length by `is_ad_enabled` immediately above @@ -297,7 +297,7 @@ class AfterDetailerScript(scripts.Script): return prompts def get_prompt(self, p, args: ADetailerArgs) -> tuple[list[str], list[str]]: - i = self.get_i(p) + i = get_i(p) prompt_sr = p._ad_xyz_prompt_sr if hasattr(p, "_ad_xyz_prompt_sr") else [] prompt = self._get_prompt( @@ -318,7 +318,7 @@ class AfterDetailerScript(scripts.Script): return prompt, negative_prompt def get_seed(self, p) -> tuple[int, int]: - i = self.get_i(p) + i = get_i(p) if not p.all_seeds: seed = p.seed @@ -519,7 +519,7 @@ class AfterDetailerScript(scripts.Script): return i2i def save_image(self, p, image, *, condition: str, suffix: str) -> None: - i = self.get_i(p) + i = get_i(p) if p.all_prompts: i %= len(p.all_prompts) save_prompt = p.all_prompts[i] @@ -565,7 +565,7 @@ class AfterDetailerScript(scripts.Script): merge_invert=args.ad_mask_merge_invert, ) - if self.is_img2img_inpaint(p) and not self.is_inpaint_only_masked(p): + if is_img2img_inpaint(p) and not is_inpaint_only_masked(p): image_mask = self.get_image_mask(p) masks = self.inpaint_mask_filter(image_mask, masks) return masks @@ -600,20 +600,6 @@ class AfterDetailerScript(scripts.Script): ) p._ad_extra_params_result[ng] = processed.all_negative_prompts[0] - @staticmethod - def need_call_process(p) -> bool: - if p.scripts is None: - return False - i = p.batch_index - bs = p.batch_size - return i == bs - 1 - - @staticmethod - def need_call_postprocess(p) -> bool: - if p.scripts is None: - return False - return p.batch_index == 0 - @staticmethod def get_i2i_init_image(p, pp): if getattr(p, "_ad_skip_img2img", False): @@ -625,14 +611,6 @@ class AfterDetailerScript(scripts.Script): use_same_seed = shared.opts.data.get("ad_same_seed_for_each_tap", False) return seed if use_same_seed else seed + i - @staticmethod - def is_img2img_inpaint(p) -> bool: - return hasattr(p, "image_mask") and p.image_mask is not None - - @staticmethod - def is_inpaint_only_masked(p) -> bool: - return hasattr(p, "inpaint_full_res") and p.inpaint_full_res - @staticmethod def inpaint_mask_filter( img2img_mask: Image.Image, ad_mask: list[Image.Image] @@ -663,7 +641,7 @@ class AfterDetailerScript(scripts.Script): if getattr(p, "_ad_disabled", False): return - if self.is_img2img_inpaint(p) and is_all_black(self.get_image_mask(p)): + if is_img2img_inpaint(p) and is_all_black(self.get_image_mask(p)): p._ad_disabled = True msg = ( "[-] ADetailer: img2img inpainting with no mask -- adetailer disabled." @@ -705,7 +683,7 @@ class AfterDetailerScript(scripts.Script): if state.interrupted or state.skipped: return False - i = self.get_i(p) + i = get_i(p) i2i = self.get_i2i_p(p, args, pp.image) seed, subseed = self.get_seed(p) @@ -791,7 +769,7 @@ class AfterDetailerScript(scripts.Script): arg_list = self.get_args(p, *args_) params_txt_content = Path(paths.data_path, "params.txt").read_text("utf-8") - if self.need_call_postprocess(p): + if need_call_postprocess(p): dummy = Processed(p, [], p.seed, "") with preseve_prompts(p): p.scripts.postprocess(copy(p), dummy) @@ -808,7 +786,7 @@ class AfterDetailerScript(scripts.Script): p, init_image, condition="ad_save_images_before", suffix="-ad-before" ) - if self.need_call_process(p): + if need_call_process(p): with preseve_prompts(p): copy_p = copy(p) if hasattr(p.scripts, "before_process"): From a40c4c77db976cec8b717503f3706215009ed9ea Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 00:45:23 +0900 Subject: [PATCH 09/19] fix(scripts): copy only non-callable extra params --- aaaaaa/helper.py | 6 +++++- scripts/!adetailer.py | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/aaaaaa/helper.py b/aaaaaa/helper.py index aa9f7f6..2bcda32 100644 --- a/aaaaaa/helper.py +++ b/aaaaaa/helper.py @@ -2,7 +2,7 @@ from __future__ import annotations from contextlib import contextmanager from copy import copy -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import torch @@ -53,3 +53,7 @@ def preseve_prompts(p: PT): finally: p.all_prompts = all_pt p.all_negative_prompts = all_ng + + +def copy_extra_params(extra_params: dict[str, Any]) -> dict[str, Any]: + return {k: v for k, v in extra_params.items() if not callable(v)} diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 2c6cc90..7c4810c 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -16,7 +16,12 @@ from PIL import Image, ImageChops from rich import print import modules -from aaaaaa.helper import change_torch_load, pause_total_tqdm, preseve_prompts +from aaaaaa.helper import ( + change_torch_load, + copy_extra_params, + pause_total_tqdm, + preseve_prompts, +) from aaaaaa.p_method import ( get_i, is_img2img_inpaint, @@ -496,7 +501,7 @@ class AfterDetailerScript(scripts.Script): height=height, restore_faces=args.ad_restore_face, tiling=p.tiling, - extra_generation_params=p.extra_generation_params.copy(), + extra_generation_params=copy_extra_params(p.extra_generation_params), do_not_save_samples=True, do_not_save_grid=True, override_settings=override_settings, From 38e369305e59c9910b8763ff93eb3e420237c08d Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 01:21:03 +0900 Subject: [PATCH 10/19] feat(scripts): add scheduler option --- adetailer/args.py | 9 ++++++++- adetailer/ui.py | 18 ++++++++++++++++-- scripts/!adetailer.py | 27 +++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/adetailer/args.py b/adetailer/args.py index e67f543..a54ac6c 100644 --- a/adetailer/args.py +++ b/adetailer/args.py @@ -82,6 +82,7 @@ class ADetailerArgs(BaseModel, extra=Extra.forbid): ad_vae: Optional[str] = None ad_use_sampler: bool = False ad_sampler: str = "DPM++ 2M Karras" + ad_scheduler: str = "Use same scheduler" ad_use_noise_multiplier: bool = False ad_noise_multiplier: confloat(ge=0.5, le=1.5) = 1.0 ad_use_clip_skip: bool = False @@ -160,8 +161,13 @@ class ADetailerArgs(BaseModel, extra=Extra.forbid): ) ppop( "ADetailer use separate sampler", - ["ADetailer use separate sampler", "ADetailer sampler"], + [ + "ADetailer use separate sampler", + "ADetailer sampler", + "ADetailer scheduler", + ], ) + ppop("ADetailer scheduler", cond="Use same scheduler") ppop( "ADetailer use separate noise multiplier", ["ADetailer use separate noise multiplier", "ADetailer noise multiplier"], @@ -225,6 +231,7 @@ _all_args = [ ("ad_vae", "ADetailer VAE"), ("ad_use_sampler", "ADetailer use separate sampler"), ("ad_sampler", "ADetailer sampler"), + ("ad_scheduler", "ADetailer scheduler"), ("ad_use_noise_multiplier", "ADetailer use separate noise multiplier"), ("ad_noise_multiplier", "ADetailer noise multiplier"), ("ad_use_clip_skip", "ADetailer use separate CLIP skip"), diff --git a/adetailer/ui.py b/adetailer/ui.py index 04379d5..a4f87fd 100644 --- a/adetailer/ui.py +++ b/adetailer/ui.py @@ -51,6 +51,7 @@ class Widgets(SimpleNamespace): class WebuiInfo: ad_model_list: list[str] sampler_names: list[str] + scheduler_names: list[str] t2i_button: gr.Button i2i_button: gr.Button checkpoints_list: list[str] @@ -545,10 +546,23 @@ def inpainting(w: Widgets, n: int, is_img2img: bool, webui_info: WebuiInfo): elem_id=eid("ad_sampler"), ) + scheduler_names = [ + "Use same scheduler", + "Automatic", + *webui_info.scheduler_names, + ] + w.ad_scheduler = gr.Dropdown( + label="ADetailer scheduler" + suffix(n), + choices=webui_info.scheduler_names, + value=webui_info.scheduler_names[0], + visible=len(scheduler_names) > 2, + elem_id=eid("ad_scheduler"), + ) + w.ad_use_sampler.change( - gr_interactive, + lambda value: (gr_interactive(value), gr_interactive(value)), inputs=w.ad_use_sampler, - outputs=w.ad_sampler, + outputs=[w.ad_sampler, w.ad_scheduler], queue=False, ) diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 7c4810c..77ede1d 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -75,6 +75,15 @@ except ImportError: return image.convert("L") +try: + from modules.sd_schedulers import sd_schedulers + + scheduler_available = True +except ImportError: + sd_schedulers = [] + scheduler_available = False + + if TYPE_CHECKING: from fastapi import FastAPI @@ -118,6 +127,7 @@ class AfterDetailerScript(scripts.Script): num_models = opts.data.get("ad_max_models", 2) ad_model_list = list(model_mapping.keys()) sampler_names = [sampler.name for sampler in all_samplers] + scheduler_names = [x.label for x in sd_schedulers] try: checkpoint_list = modules.sd_models.checkpoint_tiles(use_shorts=True) @@ -128,6 +138,7 @@ class AfterDetailerScript(scripts.Script): webui_info = WebuiInfo( ad_model_list=ad_model_list, sampler_names=sampler_names, + scheduler_names=scheduler_names, t2i_button=txt2img_submit_button, i2i_button=img2img_submit_button, checkpoints_list=checkpoint_list, @@ -373,6 +384,17 @@ class AfterDetailerScript(scripts.Script): return p._ad_orig.sampler_name return p.sampler_name + def get_scheduler(self, p, args: ADetailerArgs) -> dict[str, str]: + "webui >= 1.9.0" + if not args.ad_use_sampler: + return {} + + if args.ad_scheduler == "Use same scheduler": + value = getattr(p, "scheduler", "Automatic") + else: + value = args.ad_scheduler + return {"scheduler": value} + def get_override_settings(self, p, args: ADetailerArgs) -> dict[str, Any]: d = {} @@ -470,6 +492,10 @@ class AfterDetailerScript(scripts.Script): sampler_name = self.get_sampler(p, args) override_settings = self.get_override_settings(p, args) + version_args = {} + if scheduler_available: + version_args.update(self.get_scheduler(p, args)) + i2i = StableDiffusionProcessingImg2Img( init_images=[image], resize_mode=0, @@ -505,6 +531,7 @@ class AfterDetailerScript(scripts.Script): do_not_save_samples=True, do_not_save_grid=True, override_settings=override_settings, + **version_args, ) i2i.cached_c = [None, None] From d8df3b8111d0e94db6eb7d9674e63424b7e84125 Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 09:18:57 +0900 Subject: [PATCH 11/19] fix(scripts): scheduler import error --- adetailer/ui.py | 4 ++-- scripts/!adetailer.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/adetailer/ui.py b/adetailer/ui.py index a4f87fd..3b96a81 100644 --- a/adetailer/ui.py +++ b/adetailer/ui.py @@ -553,8 +553,8 @@ def inpainting(w: Widgets, n: int, is_img2img: bool, webui_info: WebuiInfo): ] w.ad_scheduler = gr.Dropdown( label="ADetailer scheduler" + suffix(n), - choices=webui_info.scheduler_names, - value=webui_info.scheduler_names[0], + choices=scheduler_names, + value=scheduler_names[0], visible=len(scheduler_names) > 2, elem_id=eid("ad_scheduler"), ) diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 77ede1d..b24ad81 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -76,11 +76,11 @@ except ImportError: try: - from modules.sd_schedulers import sd_schedulers + from modules.sd_schedulers import schedulers scheduler_available = True except ImportError: - sd_schedulers = [] + schedulers = [] scheduler_available = False @@ -127,7 +127,7 @@ class AfterDetailerScript(scripts.Script): num_models = opts.data.get("ad_max_models", 2) ad_model_list = list(model_mapping.keys()) sampler_names = [sampler.name for sampler in all_samplers] - scheduler_names = [x.label for x in sd_schedulers] + scheduler_names = [x.label for x in schedulers] try: checkpoint_list = modules.sd_models.checkpoint_tiles(use_shorts=True) From 3c27ab30ce606bef126bb8ec7333748b77cc4107 Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 16:46:59 +0900 Subject: [PATCH 12/19] fix(scripts): fix live preview None error --- scripts/!adetailer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index b24ad81..2f822ec 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -736,15 +736,15 @@ class AfterDetailerScript(scripts.Script): with change_torch_load(): pred = predictor(ad_model, pp.image, args.ad_confidence, **kwargs) - masks = self.pred_preprocessing(p, pred, args) - shared.state.assign_current_image(pred.preview) - - if not masks: + if pred.preview is None: print( f"[-] ADetailer: nothing detected on image {i + 1} with {ordinal(n + 1)} settings." ) return False + masks = self.pred_preprocessing(p, pred, args) + shared.state.assign_current_image(pred.preview) + self.save_image( p, pred.preview, From 0204ae315087f3d1ea1a27d611b5f73ab75352f9 Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 17:00:55 +0900 Subject: [PATCH 13/19] fix(modules): conditional imports --- aaaaaa/conditional.py | 16 ++++++++++++++++ scripts/!adetailer.py | 20 ++------------------ 2 files changed, 18 insertions(+), 18 deletions(-) create mode 100644 aaaaaa/conditional.py diff --git a/aaaaaa/conditional.py b/aaaaaa/conditional.py new file mode 100644 index 0000000..a16a17a --- /dev/null +++ b/aaaaaa/conditional.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from PIL import Image + +try: + from modules.processing import create_binary_mask +except ImportError: + + def create_binary_mask(image: Image.Image): + return image.convert("L") + + +try: + from modules.sd_schedulers import schedulers +except ImportError: + schedulers = [] diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 2f822ec..0ec861d 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -16,6 +16,7 @@ from PIL import Image, ImageChops from rich import print import modules +from aaaaaa.conditional import create_binary_mask, schedulers from aaaaaa.helper import ( change_torch_load, copy_extra_params, @@ -67,23 +68,6 @@ from modules.processing import ( from modules.sd_samplers import all_samplers from modules.shared import cmd_opts, opts, state -try: - from modules.processing import create_binary_mask -except ImportError: - - def create_binary_mask(image: Image.Image): - return image.convert("L") - - -try: - from modules.sd_schedulers import schedulers - - scheduler_available = True -except ImportError: - schedulers = [] - scheduler_available = False - - if TYPE_CHECKING: from fastapi import FastAPI @@ -493,7 +477,7 @@ class AfterDetailerScript(scripts.Script): override_settings = self.get_override_settings(p, args) version_args = {} - if scheduler_available: + if schedulers: version_args.update(self.get_scheduler(p, args)) i2i = StableDiffusionProcessingImg2Img( From ee1670ad12d23ad85e0e0749ef6c3fb18b002c84 Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 17:07:19 +0900 Subject: [PATCH 14/19] fix(ui): scheduler ui --- adetailer/ui.py | 52 ++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/adetailer/ui.py b/adetailer/ui.py index 3b96a81..7ec2f85 100644 --- a/adetailer/ui.py +++ b/adetailer/ui.py @@ -530,7 +530,7 @@ def inpainting(w: Widgets, n: int, is_img2img: bool, webui_info: WebuiInfo): elem_id=eid("ad_vae"), ) - with gr.Row(), gr.Column(variant="compact"): + with gr.Row(): w.ad_use_sampler = gr.Checkbox( label="Use separate sampler" + suffix(n), value=False, @@ -538,33 +538,33 @@ def inpainting(w: Widgets, n: int, is_img2img: bool, webui_info: WebuiInfo): elem_id=eid("ad_use_sampler"), ) - w.ad_sampler = gr.Dropdown( - label="ADetailer sampler" + suffix(n), - choices=webui_info.sampler_names, - value=webui_info.sampler_names[0], - visible=True, - elem_id=eid("ad_sampler"), - ) + with gr.Row(): + w.ad_sampler = gr.Dropdown( + label="ADetailer sampler" + suffix(n), + choices=webui_info.sampler_names, + value=webui_info.sampler_names[0], + visible=True, + elem_id=eid("ad_sampler"), + ) - scheduler_names = [ - "Use same scheduler", - "Automatic", - *webui_info.scheduler_names, - ] - w.ad_scheduler = gr.Dropdown( - label="ADetailer scheduler" + suffix(n), - choices=scheduler_names, - value=scheduler_names[0], - visible=len(scheduler_names) > 2, - elem_id=eid("ad_scheduler"), - ) + scheduler_names = [ + "Use same scheduler", + *webui_info.scheduler_names, + ] + w.ad_scheduler = gr.Dropdown( + label="ADetailer scheduler" + suffix(n), + choices=scheduler_names, + value=scheduler_names[0], + visible=len(scheduler_names) > 1, + elem_id=eid("ad_scheduler"), + ) - w.ad_use_sampler.change( - lambda value: (gr_interactive(value), gr_interactive(value)), - inputs=w.ad_use_sampler, - outputs=[w.ad_sampler, w.ad_scheduler], - queue=False, - ) + w.ad_use_sampler.change( + lambda value: (gr_interactive(value), gr_interactive(value)), + inputs=w.ad_use_sampler, + outputs=[w.ad_sampler, w.ad_scheduler], + queue=False, + ) with gr.Row(): with gr.Column(variant="compact"): From 48af2214cc915ac97cd098834b3e08a03093b5ea Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 17:13:53 +0900 Subject: [PATCH 15/19] test: args test --- tests/test_args.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/test_args.py diff --git a/tests/test_args.py b/tests/test_args.py new file mode 100644 index 0000000..89b0c2b --- /dev/null +++ b/tests/test_args.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from adetailer.args import ALL_ARGS, ADetailerArgs + + +def test_all_args() -> None: + args = ADetailerArgs() + for attr, _ in ALL_ARGS: + assert hasattr(args, attr), attr + + for attr, _ in args: + if attr == "is_api": + continue + assert attr in ALL_ARGS.attrs, attr From 4821bdf483733a23cee79d5e9df155483d194263 Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 17:30:22 +0900 Subject: [PATCH 16/19] ci: fix lint action --- .github/workflows/lint.yml | 34 ---------------------------------- .github/workflows/notlint.yml | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 34 deletions(-) delete mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/notlint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 3279467..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Lint - -on: - pull_request: - paths: - - "**.py" - -jobs: - lint: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Setup python - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - - name: Install python packages - run: pip install black ruff pre-commit-hooks - - - name: Run pre-commit-hooks - run: | - check-ast - trailing-whitespace-fixer --markdown-linebreak-ext=md - end-of-file-fixer - mixed-line-ending - - - name: Run black - run: black --check . - - - name: Run ruff - run: ruff check . diff --git a/.github/workflows/notlint.yml b/.github/workflows/notlint.yml new file mode 100644 index 0000000..faa3343 --- /dev/null +++ b/.github/workflows/notlint.yml @@ -0,0 +1,19 @@ +name: Not Lint + +on: + pull_request: + types: + - opened + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: wow-actions/auto-comment@v1 + with: + GITHUB_TOKEN: ${{ github.token }} + pullRequestOpened: | + ![Imgur](https://i.imgur.com/ESow3BL.png) + + LGTM From a992360ddaaf17ac1c8a69f9b2730e2707b5b6ee Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 17:35:29 +0900 Subject: [PATCH 17/19] docs: replace install image --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a18a42d..0be6eb5 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ ADetailer is an extension for the stable diffusion webui that does automatic mas ## Install +You can install it directly from the Extensions tab. + +![image](https://i.imgur.com/qaXtoI6.png) + +Or + (from Mikubill/sd-webui-controlnet) 1. Open "Extensions" tab. @@ -14,10 +20,6 @@ ADetailer is an extension for the stable diffusion webui that does automatic mas 6. Go to "Installed" tab, click "Check for updates", and then click "Apply and restart UI". (The next time you can also use this method to update extensions.) 7. Completely restart A1111 webui including your terminal. (If you do not know what is a "terminal", you can reboot your computer: turn your computer off and turn it on again.) -You can now install it directly from the Extensions tab. - -![image](https://i.imgur.com/g6GdRBT.png) - ## Options | Model, Prompts | | | @@ -63,8 +65,8 @@ API request example: [wiki/REST-API](https://github.com/Bing-su/adetailer/wiki/R ## Media -- 🎥 [どこよりも詳しいAfter Detailer (adetailer)の使い方① 【Stable Diffusion】](https://youtu.be/sF3POwPUWCE) -- 🎥 [どこよりも詳しいAfter Detailer (adetailer)の使い方② 【Stable Diffusion】](https://youtu.be/urNISRdbIEg) +- 🎥 [どこよりも詳しい After Detailer (adetailer)の使い方 ① 【Stable Diffusion】](https://youtu.be/sF3POwPUWCE) +- 🎥 [どこよりも詳しい After Detailer (adetailer)の使い方 ② 【Stable Diffusion】](https://youtu.be/urNISRdbIEg) - 📜 [ADetailer Installation and 5 Usage Methods](https://kindanai.com/en/manual-adetailer/) From 846e96a64bb36082c9aff8051134b792b5aeaed6 Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 17:51:04 +0900 Subject: [PATCH 18/19] fix(ui): scheduler ui... --- adetailer/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adetailer/ui.py b/adetailer/ui.py index 7ec2f85..f17c52c 100644 --- a/adetailer/ui.py +++ b/adetailer/ui.py @@ -530,7 +530,7 @@ def inpainting(w: Widgets, n: int, is_img2img: bool, webui_info: WebuiInfo): elem_id=eid("ad_vae"), ) - with gr.Row(): + with gr.Row(), gr.Column(variant="compact"): w.ad_use_sampler = gr.Checkbox( label="Use separate sampler" + suffix(n), value=False, From 1bc633e66dfea264807a721e8d89726923d2797e Mon Sep 17 00:00:00 2001 From: Dowon Date: Sun, 14 Apr 2024 18:05:39 +0900 Subject: [PATCH 19/19] chore: v24.4.1 --- CHANGELOG.md | 10 ++++++++++ adetailer/__version__.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f6e145..70572b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 2024-04-14 + +- v24.4.1 +- webui 1.9.0에서 발생한 에러 수정 + - extra generation params에 callable이 들어와서 생긴 문제 + - assign_current_image에 None이 들어갈 수 있던 문제 +- webui 1.9.0에서 변경된 scheduler 지원 +- 컨트롤넷 모델을 찾을 때, 대소문자 구분을 하지 않음 (PR #577) +- 몇몇 기능을 스크립트에서 분리하여 별도 파일로 빼냄 + ## 2024-04-10 - v24.4.0 diff --git a/adetailer/__version__.py b/adetailer/__version__.py index 4e34747..de57016 100644 --- a/adetailer/__version__.py +++ b/adetailer/__version__.py @@ -1 +1 @@ -__version__ = "24.4.1-dev.0" +__version__ = "24.4.1"