Merge branch 'dev' into main

This commit is contained in:
Dowon
2023-10-30 22:19:07 +09:00
11 changed files with 137 additions and 69 deletions

View File

@@ -8,12 +8,12 @@ repos:
- id: mixed-line-ending
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.0.292"
rev: "v0.1.3"
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.9.1
rev: 23.10.1
hooks:
- id: black

View File

@@ -1,5 +1,18 @@
# Changelog
## 2023-10-30
- v23.11.0
- 이미지의 인덱스 계산방법 변경
- webui 1.1.0 미만에서 adetailer 실행 불가능하게 함
- 컨트롤넷 preprocessor 선택지 늘림
- 추가 yolo 모델 디렉터리를 설정할 수 있는 옵션 추가
- infotext에 `/`가 있는 항목이 exif에서 복원되지 않는 문제 수정
- 이전 버전에 생성된 이미지는 여전히 복원안됨
- 같은 탭에서 항상 같은 시드를 적용하게 하는 옵션 추가
- 컨트롤넷 1.1.411 (f2aafcf2beb99a03cbdf7db73852228ccd6bd1d6) 버전을 사용중일 경우,
webui 버전 1.6.0 미만에서 사용할 수 없다는 메세지 출력
## 2023-10-15
- v23.10.1

View File

@@ -1 +1 @@
__version__ = "23.10.1"
__version__ = "23.11.0"

View File

@@ -14,11 +14,11 @@ from pydantic import (
confloat,
conint,
constr,
root_validator,
validator,
)
cn_model_regex = r".*(inpaint|tile|scribble|lineart|openpose).*|^None$"
cn_module_regex = r".*(inpaint|tile|pidi|lineart|openpose).*|^None$"
class Arg(NamedTuple):
@@ -71,20 +71,12 @@ class ADetailerArgs(BaseModel, extra=Extra.forbid):
ad_clip_skip: conint(ge=1, le=12) = 1
ad_restore_face: bool = False
ad_controlnet_model: constr(regex=cn_model_regex) = "None"
ad_controlnet_module: Optional[constr(regex=r".*inpaint.*|^None$")] = None
ad_controlnet_module: constr(regex=cn_module_regex) = "None"
ad_controlnet_weight: confloat(ge=0.0, le=1.0) = 1.0
ad_controlnet_guidance_start: confloat(ge=0.0, le=1.0) = 0.0
ad_controlnet_guidance_end: confloat(ge=0.0, le=1.0) = 1.0
is_api: bool = True
@root_validator(skip_on_failure=True)
def ad_controlnt_module_validator(cls, values): # noqa: N805
cn_model = values.get("ad_controlnet_model", "None")
cn_module = values.get("ad_controlnet_module", None)
if "inpaint" not in cn_model or cn_module == "None":
values["ad_controlnet_module"] = None
return values
@validator("is_api", pre=True)
def is_api_validator(cls, v: Any): # noqa: N805
"tuple is json serializable but cannot be made with json deserialize."
@@ -122,12 +114,12 @@ class ADetailerArgs(BaseModel, extra=Extra.forbid):
ppop("ADetailer mask max ratio", cond=1.0)
ppop("ADetailer x offset", cond=0)
ppop("ADetailer y offset", cond=0)
ppop("ADetailer mask merge/invert", cond="None")
ppop("ADetailer mask merge invert", cond="None")
ppop("ADetailer inpaint only masked", ["ADetailer inpaint padding"])
ppop(
"ADetailer use inpaint width/height",
"ADetailer use inpaint width height",
[
"ADetailer use inpaint width/height",
"ADetailer use inpaint width height",
"ADetailer inpaint width",
"ADetailer inpaint height",
],
@@ -174,7 +166,7 @@ class ADetailerArgs(BaseModel, extra=Extra.forbid):
],
cond="None",
)
ppop("ADetailer ControlNet module")
ppop("ADetailer ControlNet module", cond="None")
ppop("ADetailer ControlNet weight", cond=1.0)
ppop("ADetailer ControlNet guidance start", cond=0.0)
ppop("ADetailer ControlNet guidance end", cond=1.0)
@@ -195,13 +187,13 @@ _all_args = [
("ad_mask_max_ratio", "ADetailer mask max ratio"),
("ad_x_offset", "ADetailer x offset"),
("ad_y_offset", "ADetailer y offset"),
("ad_dilate_erode", "ADetailer dilate/erode"),
("ad_mask_merge_invert", "ADetailer mask merge/invert"),
("ad_dilate_erode", "ADetailer dilate erode"),
("ad_mask_merge_invert", "ADetailer mask merge invert"),
("ad_mask_blur", "ADetailer mask blur"),
("ad_denoising_strength", "ADetailer denoising strength"),
("ad_inpaint_only_masked", "ADetailer inpaint only masked"),
("ad_inpaint_only_masked_padding", "ADetailer inpaint padding"),
("ad_use_inpaint_width_height", "ADetailer use inpaint width/height"),
("ad_use_inpaint_width_height", "ADetailer use inpaint width height"),
("ad_inpaint_width", "ADetailer inpaint width"),
("ad_inpaint_height", "ADetailer inpaint height"),
("ad_use_steps", "ADetailer use separate steps"),

View File

@@ -36,18 +36,16 @@ def hf_download(file: str):
return path
def scan_model_dir(path_: str | Path) -> list[Path]:
if not path_ or not (path := Path(path_)).is_dir():
return []
return [p for p in path.rglob("*") if p.is_file() and p.suffix in (".pt", ".pth")]
def get_models(
model_dir: Union[str, Path], huggingface: bool = True
) -> OrderedDict[str, Optional[str]]:
model_dir = Path(model_dir)
if model_dir.is_dir():
model_paths = [
p
for p in model_dir.rglob("*")
if p.is_file() and p.suffix in (".pt", ".pth")
]
else:
model_paths = []
model_dir: str | Path, extra_dir: str | Path = "", huggingface: bool = True
) -> OrderedDict[str, str | None]:
model_paths = [*scan_model_dir(model_dir), *scan_model_dir(extra_dir)]
models = OrderedDict()
if huggingface:

View File

@@ -220,6 +220,7 @@ def filter_k_largest(pred: PredictOutput, k: int = 0) -> PredictOutput:
return pred
areas = [bbox_area(bbox) for bbox in pred.bboxes]
idx = np.argsort(areas)[-k:]
idx = idx[::-1]
pred.bboxes = [pred.bboxes[i] for i in idx]
pred.masks = [pred.masks[i] for i in idx]
return pred

View File

@@ -92,7 +92,7 @@ def library_version():
for lib in libraries:
try:
d[lib] = version(lib)
except Exception:
except Exception: # noqa: PERF203
d[lib] = "Unknown"
return d
@@ -147,7 +147,7 @@ def rich_traceback(func: Callable) -> Callable:
]
if data
]
tables.append(Traceback())
tables.append(Traceback(extra_lines=1))
console.print(Panel(Group(*tables)))
output = "\n" + string.getvalue()

View File

@@ -11,11 +11,22 @@ from adetailer import AFTER_DETAILER, __version__
from adetailer.args import ALL_ARGS, MASK_MERGE_INVERT
from controlnet_ext import controlnet_exists, get_cn_models
cn_module_choices = [
"inpaint_global_harmonious",
"inpaint_only",
"inpaint_only+lama",
]
cn_module_choices = {
"inpaint": [
"inpaint_global_harmonious",
"inpaint_only",
"inpaint_only+lama",
],
"lineart": [
"lineart_coarse",
"lineart_realistic",
"lineart_anime",
"lineart_anime_denoise",
],
"openpose": ["openpose_full", "dw_openpose_full"],
"tile": ["tile_resample", "tile_colorfix", "tile_colorfix+sharp"],
"scribble": ["t2ia_sketch_pidi"],
}
class Widgets(SimpleNamespace):
@@ -58,11 +69,11 @@ def on_generate_click(state: dict, *values: Any):
return state
def on_cn_model_update(cn_model: str):
if "inpaint" in cn_model:
return gr.update(
visible=True, choices=cn_module_choices, value=cn_module_choices[0]
)
def on_cn_model_update(cn_model_name: str):
for t in cn_module_choices:
if t in cn_model_name:
choices = cn_module_choices[t]
return gr.update(visible=True, choices=choices, value=choices[0])
return gr.update(visible=False, choices=["None"], value="None")
@@ -564,8 +575,8 @@ def controlnet(w: Widgets, n: int, is_img2img: bool):
w.ad_controlnet_module = gr.Dropdown(
label="ControlNet module" + suffix(n),
choices=cn_module_choices,
value="inpaint_global_harmonious",
choices=["None"],
value="None",
visible=False,
type="value",
interactive=controlnet_exists,

View File

@@ -4,12 +4,22 @@ import importlib
import re
from functools import lru_cache
from pathlib import Path
from textwrap import dedent
from modules import extensions, sd_models, shared
from modules.paths import data_path, models_path, script_path
ext_path = Path(data_path, "extensions")
ext_builtin_path = Path(script_path, "extensions-builtin")
try:
from modules.paths import extensions_builtin_dir, extensions_dir, models_path
except ImportError as e:
msg = """
[-] ADetailer: `stable-diffusion-webui < 1.1.0` is no longer supported.
Please upgrade to stable-diffusion-webui >= 1.1.0.
or you can use ADetailer v23.10.1 (https://github.com/Bing-su/adetailer/archive/refs/tags/v23.10.1.zip)
"""
raise RuntimeError(dedent(msg)) from e
ext_path = Path(extensions_dir)
ext_builtin_path = Path(extensions_builtin_dir)
controlnet_exists = False
controlnet_path = None
cn_base_path = ""
@@ -29,7 +39,7 @@ cn_model_module = {
"scribble": "t2ia_sketch_pidi",
"lineart": "lineart_coarse",
"openpose": "openpose_full",
"tile": None,
"tile": "tile_resample",
}
cn_model_regex = re.compile("|".join(cn_model_module.keys()))
@@ -60,11 +70,13 @@ class ControlNetExt:
if (not self.cn_available) or model == "None":
return
if module is None:
if module is None or module == "None":
for m, v in cn_model_module.items():
if m in model:
module = v
break
else:
module = None
cn_units = [
self.external_cn.ControlNetUnit(
@@ -78,7 +90,13 @@ class ControlNetExt:
)
]
self.external_cn.update_cn_script_in_processing(p, cn_units)
try:
self.external_cn.update_cn_script_in_processing(p, cn_units)
except AttributeError as e:
if "script_args_value" not in str(e):
raise
msg = "[-] Adetailer: ControlNet option not available in WEBUI version lower than 1.6.0 due to updates in ControlNet"
raise RuntimeError(msg) from e
def get_cn_model_dirs() -> list[Path]:
@@ -91,9 +109,9 @@ def get_cn_model_dirs() -> list[Path]:
ext_dir2 = getattr(shared.cmd_opts, "controlnet_dir", "")
dirs = [cn_model_dir]
for ext_dir in [cn_model_dir_old, ext_dir1, ext_dir2]:
if ext_dir:
dirs.append(Path(ext_dir))
dirs += [
Path(ext_dir) for ext_dir in [cn_model_dir_old, ext_dir1, ext_dir2] if ext_dir
]
return dirs

View File

@@ -26,8 +26,10 @@ select = [
"I001",
"ISC",
"N",
"PERF",
"PIE",
"PT",
"PTH",
"RET",
"RUF",
"SIM",

View File

@@ -53,7 +53,10 @@ from modules.shared import cmd_opts, opts, state
no_huggingface = getattr(cmd_opts, "ad_no_huggingface", False)
adetailer_dir = Path(models_path, "adetailer")
model_mapping = get_models(adetailer_dir, huggingface=not no_huggingface)
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
)
txt2img_submit_button = img2img_submit_button = None
SCRIPT_DEFAULT = "dynamic_prompting,dynamic_thresholding,wildcard_recursive,wildcards,lora_block_weight"
@@ -223,6 +226,13 @@ class AfterDetailerScript(scripts.Script):
else:
p._ad_skip_img2img = False
@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
@@ -308,7 +318,7 @@ class AfterDetailerScript(scripts.Script):
return prompts
def get_prompt(self, p, args: ADetailerArgs) -> tuple[list[str], list[str]]:
i = p._ad_idx
i = self.get_i(p)
prompt_sr = p._ad_xyz_prompt_sr if hasattr(p, "_ad_xyz_prompt_sr") else []
prompt = self._get_prompt(args.ad_prompt, p.all_prompts, i, p.prompt, prompt_sr)
@@ -323,7 +333,7 @@ class AfterDetailerScript(scripts.Script):
return prompt, negative_prompt
def get_seed(self, p) -> tuple[int, int]:
i = p._ad_idx
i = self.get_i(p)
if not p.all_seeds:
seed = p.seed
@@ -401,7 +411,7 @@ class AfterDetailerScript(scripts.Script):
)
def write_params_txt(self, p) -> None:
i = p._ad_idx
i = self.get_i(p)
lenp = len(p.all_prompts)
if i % lenp != lenp - 1:
return
@@ -503,6 +513,7 @@ class AfterDetailerScript(scripts.Script):
i2i.cached_uc = [None, None]
i2i.scripts, i2i.script_args = self.script_filter(p, args)
i2i._ad_disabled = True
i2i._ad_inner = True
if args.ad_controlnet_model != "None":
self.update_controlnet_args(i2i, args)
@@ -512,7 +523,7 @@ class AfterDetailerScript(scripts.Script):
return i2i
def save_image(self, p, image, *, condition: str, suffix: str) -> None:
i = p._ad_idx
i = self.get_i(p)
if p.all_prompts:
i %= len(p.all_prompts)
save_prompt = p.all_prompts[i]
@@ -591,17 +602,15 @@ class AfterDetailerScript(scripts.Script):
def need_call_process(p) -> bool:
if p.scripts is None:
return False
i = p._ad_idx
i = p.batch_index
bs = p.batch_size
return i % bs == bs - 1
return i == bs - 1
@staticmethod
def need_call_postprocess(p) -> bool:
if p.scripts is None:
return False
i = p._ad_idx
bs = p.batch_size
return i % bs == 0
return p.batch_index == 0
@staticmethod
def get_i2i_init_image(p, pp):
@@ -609,6 +618,11 @@ class AfterDetailerScript(scripts.Script):
return p.init_images[0]
return pp.image
@staticmethod
def get_each_tap_seed(seed: int, i: int):
use_same_seed = shared.opts.data.get("ad_same_seed_for_each_tap", False)
return seed if use_same_seed else seed + i
@rich_traceback
def process(self, p, *args_):
if getattr(p, "_ad_disabled", False):
@@ -635,7 +649,7 @@ class AfterDetailerScript(scripts.Script):
if state.interrupted or state.skipped:
return False
i = p._ad_idx
i = self.get_i(p)
pp.image = self.get_i2i_init_image(p, pp)
i2i = self.get_i2i_p(p, args, pp.image)
@@ -688,8 +702,8 @@ class AfterDetailerScript(scripts.Script):
if re.match(r"^\s*\[SKIP\]\s*$", p2.prompt):
continue
p2.seed = seed + j
p2.subseed = subseed + j
p2.seed = self.get_each_tap_seed(seed, j)
p2.subseed = self.get_each_tap_seed(subseed, j)
try:
processed = process_images(p2)
@@ -715,7 +729,6 @@ class AfterDetailerScript(scripts.Script):
if getattr(p, "_ad_disabled", False) or not self.is_ad_enabled(*args_):
return
p._ad_idx = getattr(p, "_ad_idx", -1) + 1
init_image = copy(pp.image)
arg_list = self.get_args(p, *args_)
@@ -738,7 +751,10 @@ class AfterDetailerScript(scripts.Script):
if self.need_call_process(p):
with preseve_prompts(p):
p.scripts.process(copy(p))
copy_p = copy(p)
if hasattr(p.scripts, "before_process"):
p.scripts.before_process(copy_p)
p.scripts.process(copy_p)
self.write_params_txt(p)
@@ -766,6 +782,16 @@ def on_ui_settings():
),
)
shared.opts.add_option(
"ad_extra_models_dir",
shared.OptionInfo(
default="",
label="Extra path to scan adetailer models",
component=gr.Textbox,
section=section,
),
)
shared.opts.add_option(
"ad_save_previews",
shared.OptionInfo(False, "Save mask previews", section=section),
@@ -810,6 +836,13 @@ def on_ui_settings():
),
)
shared.opts.add_option(
"ad_same_seed_for_each_tap",
shared.OptionInfo(
False, "Use same seed for each tab in adetailer", section=section
),
)
# xyz_grid