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:
34
.github/workflows/lint.yml
vendored
34
.github/workflows/lint.yml
vendored
@@ -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 .
|
||||
19
.github/workflows/notlint.yml
vendored
Normal file
19
.github/workflows/notlint.yml
vendored
Normal file
@@ -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: |
|
||||

|
||||
|
||||
LGTM
|
||||
@@ -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
|
||||
|
||||
10
CHANGELOG.md
10
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
|
||||
|
||||
14
README.md
14
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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/)
|
||||
|
||||
|
||||
0
aaaaaa/__init__.py
Normal file
0
aaaaaa/__init__.py
Normal file
16
aaaaaa/conditional.py
Normal file
16
aaaaaa/conditional.py
Normal file
@@ -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 = []
|
||||
59
aaaaaa/helper.py
Normal file
59
aaaaaa/helper.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from copy import copy
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
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
|
||||
|
||||
|
||||
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)}
|
||||
30
aaaaaa/p_method.py
Normal file
30
aaaaaa/p_method.py
Normal file
@@ -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
|
||||
@@ -1 +1 @@
|
||||
__version__ = "24.4.0"
|
||||
__version__ = "24.4.1"
|
||||
|
||||
@@ -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"),
|
||||
@@ -247,3 +254,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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]
|
||||
@@ -537,20 +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"),
|
||||
)
|
||||
|
||||
w.ad_use_sampler.change(
|
||||
gr_interactive,
|
||||
inputs=w.ad_use_sampler,
|
||||
outputs=w.ad_sampler,
|
||||
queue=False,
|
||||
)
|
||||
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,
|
||||
)
|
||||
|
||||
with gr.Row():
|
||||
with gr.Column(variant="compact"):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
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
|
||||
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
|
||||
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,
|
||||
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__,
|
||||
@@ -25,8 +37,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,
|
||||
@@ -45,7 +57,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,
|
||||
@@ -56,69 +68,29 @@ 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")
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastapi import FastAPI
|
||||
|
||||
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)}"
|
||||
)
|
||||
|
||||
|
||||
@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__()
|
||||
@@ -139,6 +111,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 schedulers]
|
||||
|
||||
try:
|
||||
checkpoint_list = modules.sd_models.checkpoint_tiles(use_shorts=True)
|
||||
@@ -149,6 +122,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,
|
||||
@@ -224,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)
|
||||
@@ -241,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
|
||||
@@ -323,14 +290,14 @@ 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)
|
||||
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(
|
||||
@@ -351,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
|
||||
@@ -401,6 +368,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 = {}
|
||||
|
||||
@@ -498,6 +476,10 @@ class AfterDetailerScript(scripts.Script):
|
||||
sampler_name = self.get_sampler(p, args)
|
||||
override_settings = self.get_override_settings(p, args)
|
||||
|
||||
version_args = {}
|
||||
if schedulers:
|
||||
version_args.update(self.get_scheduler(p, args))
|
||||
|
||||
i2i = StableDiffusionProcessingImg2Img(
|
||||
init_images=[image],
|
||||
resize_mode=0,
|
||||
@@ -529,10 +511,11 @@ 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,
|
||||
**version_args,
|
||||
)
|
||||
|
||||
i2i.cached_c = [None, None]
|
||||
@@ -552,7 +535,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]
|
||||
@@ -598,7 +581,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
|
||||
@@ -633,20 +616,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):
|
||||
@@ -658,14 +627,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]
|
||||
@@ -696,7 +657,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."
|
||||
@@ -738,7 +699,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)
|
||||
@@ -759,15 +720,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,
|
||||
@@ -824,7 +785,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)
|
||||
@@ -841,7 +802,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"):
|
||||
|
||||
14
tests/test_args.py
Normal file
14
tests/test_args.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user