mirror of
https://github.com/Bing-su/adetailer.git
synced 2026-01-26 11:19:53 +00:00
Merge branch 'dev'
This commit is contained in:
@@ -8,6 +8,7 @@ on:
|
||||
jobs:
|
||||
lint:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -194,3 +194,4 @@ pyrightconfig.json
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode
|
||||
*.ipynb
|
||||
node_modules
|
||||
|
||||
@@ -16,13 +16,13 @@ repos:
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: "v4.0.0-alpha.8"
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: v3.2.5
|
||||
hooks:
|
||||
- id: prettier
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.3.7
|
||||
rev: v0.4.4
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## 2024-05-19
|
||||
|
||||
- v24.5.0
|
||||
- 개별 탭 활성화/비활성화 체크박스 추가
|
||||
- ad_extra_model_dir 옵션에 |로 구분된 여러 디렉토리를 추가할 수 있게 함 (PR #596)
|
||||
- `hypertile` 빌트인 확장이 지원되도록 함
|
||||
- 항상 cond 캐시를 비움
|
||||
- 설치 스크립트에 uv를 사용함
|
||||
- mediapipe 최소 버전을 올려 protobuf 버전 4를 사용하게 함
|
||||
|
||||
## 2024-04-17
|
||||
|
||||
- v24.4.2
|
||||
|
||||
@@ -10,6 +10,7 @@ tasks:
|
||||
cmds:
|
||||
- echo "$PYTHON"
|
||||
- echo "$WEBUI"
|
||||
- echo "$UV_PYTHON"
|
||||
silent: true
|
||||
|
||||
launch:
|
||||
@@ -24,8 +25,8 @@ tasks:
|
||||
|
||||
update:
|
||||
cmds:
|
||||
- "{{.PYTHON}} -m pip install -U ultralytics mediapipe ruff pre-commit black devtools pytest"
|
||||
- "{{.PYTHON}} -m uv pip install -U ultralytics mediapipe ruff pre-commit black devtools pytest"
|
||||
|
||||
update-torch:
|
||||
cmds:
|
||||
- "{{.PYTHON}} -m pip install -U torch torchvision torchaudio -f https://download.pytorch.org/whl/torch_stable.html"
|
||||
- "{{.PYTHON}} -m uv pip install -U torch torchvision torchaudio -f https://download.pytorch.org/whl/torch_stable.html"
|
||||
|
||||
@@ -28,3 +28,7 @@ def get_i(p) -> int:
|
||||
bs = p.batch_size
|
||||
i = p.batch_index
|
||||
return it * bs + i
|
||||
|
||||
|
||||
def is_skip_img2img(p) -> bool:
|
||||
return getattr(p, "_ad_skip_img2img", False)
|
||||
|
||||
@@ -12,6 +12,7 @@ from rich.table import Table
|
||||
from rich.traceback import Traceback
|
||||
|
||||
from adetailer.__version__ import __version__
|
||||
from adetailer.args import ADetailerArgs
|
||||
|
||||
|
||||
def processing(*args: Any) -> dict[str, Any]:
|
||||
@@ -66,23 +67,30 @@ def sd_models() -> dict[str, str]:
|
||||
|
||||
|
||||
def ad_args(*args: Any) -> dict[str, Any]:
|
||||
ad_args = [
|
||||
arg
|
||||
for arg in args
|
||||
if isinstance(arg, dict) and arg.get("ad_model", "None") != "None"
|
||||
]
|
||||
ad_args = []
|
||||
for arg in args:
|
||||
if not isinstance(arg, dict):
|
||||
continue
|
||||
|
||||
try:
|
||||
a = ADetailerArgs(**arg)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if not a.need_skip():
|
||||
ad_args.append(a)
|
||||
|
||||
if not ad_args:
|
||||
return {}
|
||||
|
||||
arg0 = ad_args[0]
|
||||
is_api = arg0.get("is_api", True)
|
||||
return {
|
||||
"version": __version__,
|
||||
"ad_model": arg0["ad_model"],
|
||||
"ad_prompt": arg0.get("ad_prompt", ""),
|
||||
"ad_negative_prompt": arg0.get("ad_negative_prompt", ""),
|
||||
"ad_controlnet_model": arg0.get("ad_controlnet_model", "None"),
|
||||
"is_api": type(is_api) is not tuple,
|
||||
"ad_model": arg0.ad_model,
|
||||
"ad_prompt": arg0.ad_prompt,
|
||||
"ad_negative_prompt": arg0.ad_negative_prompt,
|
||||
"ad_controlnet_model": arg0.ad_controlnet_model,
|
||||
"is_api": arg0.is_api,
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ def adui(
|
||||
states.append(state)
|
||||
infotext_fields.extend(infofields)
|
||||
|
||||
# components: [bool, dict, dict, ...]
|
||||
# components: [bool, bool, dict, dict, ...]
|
||||
components = [ad_enable, ad_skip_img2img, *states]
|
||||
return components, infotext_fields
|
||||
|
||||
@@ -171,26 +171,35 @@ def one_ui_group(n: int, is_img2img: bool, webui_info: WebuiInfo):
|
||||
w = Widgets()
|
||||
eid = partial(elem_id, n=n, is_img2img=is_img2img)
|
||||
|
||||
model_choices = (
|
||||
[*webui_info.ad_model_list, "None"]
|
||||
if n == 0
|
||||
else ["None", *webui_info.ad_model_list]
|
||||
)
|
||||
|
||||
with gr.Group():
|
||||
with gr.Row():
|
||||
model_choices = (
|
||||
[*webui_info.ad_model_list, "None"]
|
||||
if n == 0
|
||||
else ["None", *webui_info.ad_model_list]
|
||||
with gr.Row(variant="compact"):
|
||||
w.ad_tap_enable = gr.Checkbox(
|
||||
label=f"Enable this tap ({ordinal(n + 1)})",
|
||||
value=True,
|
||||
visible=True,
|
||||
elem_id=eid("ad_tap_enable"),
|
||||
)
|
||||
|
||||
with gr.Row():
|
||||
w.ad_model = gr.Dropdown(
|
||||
label="ADetailer model" + suffix(n),
|
||||
label="ADetailer detector" + suffix(n),
|
||||
choices=model_choices,
|
||||
value=model_choices[0],
|
||||
visible=True,
|
||||
type="value",
|
||||
elem_id=eid("ad_model"),
|
||||
info="Select a model to use for detection.",
|
||||
)
|
||||
|
||||
with gr.Row():
|
||||
w.ad_model_classes = gr.Textbox(
|
||||
label="ADetailer model classes" + suffix(n),
|
||||
label="ADetailer detector classes" + suffix(n),
|
||||
value="",
|
||||
visible=False,
|
||||
elem_id=eid("ad_classes"),
|
||||
@@ -354,6 +363,7 @@ def mask_preprocessing(w: Widgets, n: int, is_img2img: bool):
|
||||
choices=MASK_MERGE_INVERT,
|
||||
value="None",
|
||||
elem_id=eid("ad_mask_merge_invert"),
|
||||
info="None: do nothing, Merge: merge masks, Merge and Invert: merge all masks and invert",
|
||||
)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "24.4.2"
|
||||
__version__ = "24.5.0"
|
||||
|
||||
@@ -55,6 +55,7 @@ class ArgsList(UserList):
|
||||
class ADetailerArgs(BaseModel, extra=Extra.forbid):
|
||||
ad_model: str = "None"
|
||||
ad_model_classes: str = ""
|
||||
ad_tap_enable: bool = True
|
||||
ad_prompt: str = ""
|
||||
ad_negative_prompt: str = ""
|
||||
ad_confidence: confloat(ge=0.0, le=1.0) = 0.3
|
||||
@@ -119,7 +120,7 @@ class ADetailerArgs(BaseModel, extra=Extra.forbid):
|
||||
p.pop(k, None)
|
||||
|
||||
def extra_params(self, suffix: str = "") -> dict[str, Any]:
|
||||
if self.ad_model == "None":
|
||||
if self.need_skip():
|
||||
return {}
|
||||
|
||||
p = {name: getattr(self, attr) for attr, name in ALL_ARGS}
|
||||
@@ -128,6 +129,7 @@ class ADetailerArgs(BaseModel, extra=Extra.forbid):
|
||||
ppop("ADetailer model classes")
|
||||
ppop("ADetailer prompt")
|
||||
ppop("ADetailer negative prompt")
|
||||
p.pop("ADetailer tap enable", None) # always pop
|
||||
ppop("ADetailer mask only top k largest", cond=0)
|
||||
ppop("ADetailer mask min ratio", cond=0.0)
|
||||
ppop("ADetailer mask max ratio", cond=1.0)
|
||||
@@ -200,10 +202,17 @@ class ADetailerArgs(BaseModel, extra=Extra.forbid):
|
||||
|
||||
return p
|
||||
|
||||
def is_mediapipe(self) -> bool:
|
||||
return self.ad_model.lower().startswith("mediapipe")
|
||||
|
||||
def need_skip(self) -> bool:
|
||||
return self.ad_model == "None" or self.ad_tap_enable is False
|
||||
|
||||
|
||||
_all_args = [
|
||||
("ad_model", "ADetailer model"),
|
||||
("ad_model_classes", "ADetailer model classes"),
|
||||
("ad_tap_enable", "ADetailer tap enable"),
|
||||
("ad_prompt", "ADetailer prompt"),
|
||||
("ad_negative_prompt", "ADetailer negative prompt"),
|
||||
("ad_confidence", "ADetailer confidence"),
|
||||
@@ -262,6 +271,8 @@ _script_default = (
|
||||
"wildcards",
|
||||
"lora_block_weight",
|
||||
"negpip",
|
||||
"soft_inpainting",
|
||||
)
|
||||
SCRIPT_DEFAULT = ",".join(sorted(_script_default))
|
||||
|
||||
_builtin_script = ("soft_inpainting", "hypertile_script")
|
||||
BUILTIN_SCRIPT = ",".join(sorted(_builtin_script))
|
||||
|
||||
22
install.py
22
install.py
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from importlib.metadata import version # python >= 3.8
|
||||
@@ -38,19 +39,29 @@ def is_installed(
|
||||
|
||||
|
||||
def run_pip(*args):
|
||||
subprocess.run([sys.executable, "-m", "pip", "install", *args])
|
||||
subprocess.run([sys.executable, "-m", "pip", "install", *args], check=False)
|
||||
|
||||
|
||||
def run_uv_pip(*args):
|
||||
subprocess.run([sys.executable, "-m", "uv", "pip", "install", *args], check=False)
|
||||
|
||||
|
||||
def install():
|
||||
deps = [
|
||||
# requirements
|
||||
("ultralytics", "8.1.29", None),
|
||||
("mediapipe", "0.10.9", None),
|
||||
("ultralytics", "8.2.0", None),
|
||||
("mediapipe", "0.10.13", None),
|
||||
("rich", "13.0.0", None),
|
||||
# mediapipe
|
||||
("protobuf", "3.20", "3.9999"),
|
||||
("protobuf", "4.25.3", "4.9999"),
|
||||
]
|
||||
|
||||
if not is_installed("uv", "0.1.44", None):
|
||||
run_pip("uv>=0.1.44")
|
||||
|
||||
os.environ["UV_PYTHON"] = sys.executable
|
||||
|
||||
pkgs = []
|
||||
for pkg, low, high in deps:
|
||||
if not is_installed(pkg, low, high):
|
||||
if low and high:
|
||||
@@ -61,8 +72,9 @@ def install():
|
||||
cmd = f"{pkg}<={high}"
|
||||
else:
|
||||
cmd = pkg
|
||||
pkgs.append(cmd)
|
||||
|
||||
run_pip("-U", cmd)
|
||||
run_uv_pip(*pkgs)
|
||||
|
||||
|
||||
try:
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
name = "adetailer"
|
||||
description = "An object detection and auto-mask extension for stable diffusion webui."
|
||||
authors = [{ name = "dowon", email = "ks2515@naver.com" }]
|
||||
requires-python = ">=3.8,<3.13"
|
||||
requires-python = ">=3.8"
|
||||
readme = "README.md"
|
||||
license = { text = "AGPL-3.0" }
|
||||
dependencies = [
|
||||
"ultralytics>=8.1",
|
||||
"ultralytics>=8.2",
|
||||
"mediapipe>=0.10",
|
||||
"pydantic<3",
|
||||
"rich>=13",
|
||||
|
||||
@@ -26,9 +26,12 @@ from aaaaaa.p_method import (
|
||||
get_i,
|
||||
is_img2img_inpaint,
|
||||
is_inpaint_only_masked,
|
||||
is_skip_img2img,
|
||||
need_call_postprocess,
|
||||
need_call_process,
|
||||
)
|
||||
from aaaaaa.traceback import rich_traceback
|
||||
from aaaaaa.ui import WebuiInfo, adui, ordinal, suffix
|
||||
from adetailer import (
|
||||
AFTER_DETAILER,
|
||||
__version__,
|
||||
@@ -36,7 +39,13 @@ from adetailer import (
|
||||
mediapipe_predict,
|
||||
ultralytics_predict,
|
||||
)
|
||||
from adetailer.args import BBOX_SORTBY, SCRIPT_DEFAULT, ADetailerArgs, SkipImg2ImgOrig
|
||||
from adetailer.args import (
|
||||
BBOX_SORTBY,
|
||||
BUILTIN_SCRIPT,
|
||||
SCRIPT_DEFAULT,
|
||||
ADetailerArgs,
|
||||
SkipImg2ImgOrig,
|
||||
)
|
||||
from adetailer.common import PredictOutput, ensure_pil_image, safe_mkdir
|
||||
from adetailer.mask import (
|
||||
filter_by_ratio,
|
||||
@@ -46,8 +55,6 @@ from adetailer.mask import (
|
||||
mask_preprocess,
|
||||
sort_bboxes,
|
||||
)
|
||||
from adetailer.traceback import rich_traceback
|
||||
from adetailer.ui import WebuiInfo, adui, ordinal, suffix
|
||||
from controlnet_ext import (
|
||||
CNHijackRestore,
|
||||
ControlNetExt,
|
||||
@@ -76,10 +83,10 @@ 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", "")
|
||||
extra_models_dirs = shared.opts.data.get("ad_extra_models_dir", "")
|
||||
model_mapping = get_models(
|
||||
adetailer_dir,
|
||||
extra_models_dir,
|
||||
*extra_models_dirs.split("|"),
|
||||
huggingface=not no_huggingface,
|
||||
)
|
||||
|
||||
@@ -180,7 +187,13 @@ class AfterDetailerScript(scripts.Script):
|
||||
return False
|
||||
|
||||
ad_enabled = args_[0] if isinstance(args_[0], bool) else True
|
||||
not_none = any(arg.get("ad_model", "None") != "None" for arg in arg_list)
|
||||
pydantic_args = []
|
||||
for arg in arg_list:
|
||||
try:
|
||||
pydantic_args.append(ADetailerArgs(**arg))
|
||||
except ValueError: # noqa: PERF203
|
||||
continue
|
||||
not_none = not all(arg.need_skip() for arg in pydantic_args)
|
||||
return ad_enabled and not_none
|
||||
|
||||
def set_skip_img2img(self, p, *args_) -> None:
|
||||
@@ -441,10 +454,11 @@ class AfterDetailerScript(scripts.Script):
|
||||
if not ad_only_seleted_scripts:
|
||||
return script_runner, script_args
|
||||
|
||||
ad_script_names = opts.data.get("ad_script_names", SCRIPT_DEFAULT)
|
||||
ad_script_names_string: str = opts.data.get("ad_script_names", SCRIPT_DEFAULT)
|
||||
ad_script_names = ad_script_names_string.split(",") + BUILTIN_SCRIPT.split(",")
|
||||
script_names_set = {
|
||||
name
|
||||
for script_name in ad_script_names.split(",")
|
||||
for script_name in ad_script_names
|
||||
for name in (script_name, script_name.strip())
|
||||
}
|
||||
|
||||
@@ -625,7 +639,7 @@ class AfterDetailerScript(scripts.Script):
|
||||
|
||||
@staticmethod
|
||||
def get_i2i_init_image(p, pp):
|
||||
if getattr(p, "_ad_skip_img2img", False):
|
||||
if is_skip_img2img(p):
|
||||
return p.init_images[0]
|
||||
return pp.image
|
||||
|
||||
@@ -649,7 +663,7 @@ class AfterDetailerScript(scripts.Script):
|
||||
mask = ImageChops.invert(mask)
|
||||
mask = create_binary_mask(mask)
|
||||
|
||||
if getattr(p, "_ad_skip_img2img", False):
|
||||
if is_skip_img2img(p):
|
||||
if hasattr(p, "init_images") and p.init_images:
|
||||
width, height = p.init_images[0].size
|
||||
else:
|
||||
@@ -712,7 +726,7 @@ class AfterDetailerScript(scripts.Script):
|
||||
seed, subseed = self.get_seed(p)
|
||||
ad_prompts, ad_negatives = self.get_prompt(p, args)
|
||||
|
||||
is_mediapipe = args.ad_model.lower().startswith("mediapipe")
|
||||
is_mediapipe = args.is_mediapipe()
|
||||
|
||||
kwargs = {}
|
||||
if is_mediapipe:
|
||||
@@ -762,6 +776,8 @@ class AfterDetailerScript(scripts.Script):
|
||||
p2.seed = self.get_each_tap_seed(seed, j)
|
||||
p2.subseed = self.get_each_tap_seed(subseed, j)
|
||||
|
||||
p2.cached_c = [None, None]
|
||||
p2.cached_uc = [None, None]
|
||||
try:
|
||||
processed = process_images(p2)
|
||||
except NansException as e:
|
||||
@@ -800,11 +816,11 @@ class AfterDetailerScript(scripts.Script):
|
||||
is_processed = False
|
||||
with CNHijackRestore(), pause_total_tqdm(), cn_allow_script_control():
|
||||
for n, args in enumerate(arg_list):
|
||||
if args.ad_model == "None":
|
||||
if args.need_skip():
|
||||
continue
|
||||
is_processed |= self._postprocess_image_inner(p, pp, args, n=n)
|
||||
|
||||
if is_processed and not getattr(p, "_ad_skip_img2img", False):
|
||||
if is_processed and not is_skip_img2img(p):
|
||||
self.save_image(
|
||||
p, init_image, condition="ad_save_images_before", suffix="-ad-before"
|
||||
)
|
||||
@@ -849,10 +865,12 @@ def on_ui_settings():
|
||||
"ad_extra_models_dir",
|
||||
shared.OptionInfo(
|
||||
default="",
|
||||
label="Extra path to scan adetailer models",
|
||||
label="Extra paths to scan adetailer models seperated by vertical bars(|)",
|
||||
component=gr.Textbox,
|
||||
section=section,
|
||||
),
|
||||
)
|
||||
.info("eg. path\\to\\models|C:\\path\\to\\models|another/path/to/models")
|
||||
.needs_reload_ui(),
|
||||
)
|
||||
|
||||
shared.opts.add_option(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from adetailer.args import ALL_ARGS, ADetailerArgs
|
||||
|
||||
|
||||
@@ -12,3 +14,35 @@ def test_all_args() -> None:
|
||||
if attr == "is_api":
|
||||
continue
|
||||
assert attr in ALL_ARGS.attrs, attr
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ad_model", "expect"),
|
||||
[("mediapipe_face_full", True), ("face_yolov8n.pt", False)],
|
||||
)
|
||||
def test_is_mediapipe(ad_model: str, expect: bool) -> None:
|
||||
args = ADetailerArgs(ad_model=ad_model)
|
||||
assert args.is_mediapipe() is expect
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ad_model", "expect"),
|
||||
[("mediapipe_face_full", False), ("face_yolov8n.pt", False), ("None", True)],
|
||||
)
|
||||
def test_need_skip(ad_model: str, expect: bool) -> None:
|
||||
args = ADetailerArgs(ad_model=ad_model)
|
||||
assert args.need_skip() is expect
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ad_model", "ad_tap_enable", "expect"),
|
||||
[
|
||||
("face_yolov8n.pt", False, True),
|
||||
("mediapipe_face_full", False, True),
|
||||
("None", True, True),
|
||||
("ace_yolov8s.pt", True, False),
|
||||
],
|
||||
)
|
||||
def test_need_skip_tap_enable(ad_model: str, ad_tap_enable: bool, expect: bool) -> None:
|
||||
args = ADetailerArgs(ad_model=ad_model, ad_tap_enable=ad_tap_enable)
|
||||
assert args.need_skip() is expect
|
||||
|
||||
Reference in New Issue
Block a user