diff --git a/CHANGELOG.md b/CHANGELOG.md index 12d26d2..8d25234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 2023-07-19 + +- v23.7.7 +- 인페인팅 단계에서 별도의 샘플러를 선택할 수 있게 옵션을 추가함 (xyz그리드에도 추가) +- webui 1.0.0-pre 이하 버전에서 batch index 문제 수정 +- 스크립트에 `paste_field_names`을 추가함. 사용되는지는 모르겠음 + ## 2023-07-16 - v23.7.6 diff --git a/adetailer/__version__.py b/adetailer/__version__.py index 5e61c29..577a2cb 100644 --- a/adetailer/__version__.py +++ b/adetailer/__version__.py @@ -1 +1 @@ -__version__ = "23.7.6" +__version__ = "23.7.7" diff --git a/adetailer/args.py b/adetailer/args.py index e7d4178..0e60d86 100644 --- a/adetailer/args.py +++ b/adetailer/args.py @@ -57,6 +57,8 @@ class ADetailerArgs(BaseModel, extra=Extra.forbid): ad_steps: PositiveInt = 28 ad_use_cfg_scale: bool = False ad_cfg_scale: NonNegativeFloat = 7.0 + ad_use_sampler: bool = False + ad_sampler: str = "DPM++ 2M Karras" ad_use_noise_multiplier: bool = False ad_noise_multiplier: confloat(ge=0.5, le=1.5) = 1.0 ad_restore_face: bool = False @@ -129,6 +131,10 @@ class ADetailerArgs(BaseModel, extra=Extra.forbid): "ADetailer use separate CFG scale", ["ADetailer use separate CFG scale", "ADetailer CFG scale"], ) + ppop( + "ADetailer use separate sampler", + ["ADetailer use separate sampler", "ADetailer sampler"], + ) ppop( "ADetailer use separate noise multiplier", ["ADetailer use separate noise multiplier", "ADetailer noise multiplier"], @@ -191,6 +197,8 @@ _all_args = [ ("ad_steps", "ADetailer steps"), ("ad_use_cfg_scale", "ADetailer use separate CFG scale"), ("ad_cfg_scale", "ADetailer CFG scale"), + ("ad_use_sampler", "ADetailer use separate sampler"), + ("ad_sampler", "ADetailer sampler"), ("ad_use_noise_multiplier", "ADetailer use separate noise multiplier"), ("ad_noise_multiplier", "ADetailer noise multiplier"), ("ad_restore_face", "ADetailer restore face"), diff --git a/adetailer/traceback.py b/adetailer/traceback.py index 03e9afe..7811d46 100644 --- a/adetailer/traceback.py +++ b/adetailer/traceback.py @@ -92,7 +92,8 @@ def sys_info() -> dict[str, Any]: version = launch.git_tag() commit = launch.commit_hash() except Exception: - version = commit = "------" + version = "Unknown (too old or vladmandic)" + commit = "-------" return { "Platform": platform.platform(), diff --git a/adetailer/ui.py b/adetailer/ui.py index 876a910..e3ee32c 100644 --- a/adetailer/ui.py +++ b/adetailer/ui.py @@ -65,6 +65,7 @@ def adui( num_models: int, is_img2img: bool, model_list: list[str], + samplers: list[str], t2i_button: gr.Button, i2i_button: gr.Button, ): @@ -97,6 +98,7 @@ def adui( n=n, is_img2img=is_img2img, model_list=model_list, + samplers=samplers, t2i_button=t2i_button, i2i_button=i2i_button, ) @@ -113,6 +115,7 @@ def one_ui_group( n: int, is_img2img: bool, model_list: list[str], + samplers: list[str], t2i_button: gr.Button, i2i_button: gr.Button, ): @@ -171,7 +174,7 @@ def one_ui_group( with gr.Accordion( "Inpainting", open=False, elem_id=eid("ad_inpainting_accordion") ): - inpainting(w, n, is_img2img) + inpainting(w, n, is_img2img, samplers) with gr.Group(): controlnet(w, n, is_img2img) @@ -268,7 +271,7 @@ def mask_preprocessing(w: Widgets, n: int, is_img2img: bool): ) -def inpainting(w: Widgets, n: int, is_img2img: bool): +def inpainting(w: Widgets, n: int, is_img2img: bool, samplers: list[str]): eid = partial(elem_id, n=n, is_img2img=is_img2img) with gr.Group(): @@ -405,6 +408,29 @@ def inpainting(w: Widgets, n: int, is_img2img: bool): ) with gr.Row(): + with gr.Column(variant="compact"): + w.ad_use_sampler = gr.Checkbox( + label="Use separate sampler" + suffix(n), + value=False, + visible=True, + elem_id=eid("ad_use_sampler"), + ) + + w.ad_sampler = gr.Dropdown( + label="ADetailer sampler" + suffix(n), + choices=samplers, + value=samplers[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, + ) + with gr.Column(variant="compact"): w.ad_use_noise_multiplier = gr.Checkbox( label="Use separate noise multiplier" + suffix(n), @@ -430,6 +456,7 @@ def inpainting(w: Widgets, n: int, is_img2img: bool): queue=False, ) + with gr.Row(): w.ad_restore_face = gr.Checkbox( label="Restore faces after ADetailer" + suffix(n), value=False, diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 7251262..3c0c0f6 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -43,6 +43,7 @@ from sd_webui.processing import ( create_infotext, process_images, ) +from sd_webui.sd_samplers import all_samplers from sd_webui.shared import cmd_opts, opts, state no_huggingface = getattr(cmd_opts, "ad_no_huggingface", False) @@ -115,16 +116,19 @@ class AfterDetailerScript(scripts.Script): def ui(self, is_img2img): num_models = opts.data.get("ad_max_models", 2) model_list = list(model_mapping.keys()) + samplers = [sampler.name for sampler in all_samplers] components, infotext_fields = adui( num_models, is_img2img, model_list, + samplers, txt2img_submit_button, img2img_submit_button, ) self.infotext_fields = infotext_fields + self.paste_field_names = [name for _, name in infotext_fields] return components def init_controlnet_ext(self) -> None: @@ -296,6 +300,13 @@ class AfterDetailerScript(scripts.Script): return args.ad_cfg_scale return p.cfg_scale + def get_sampler(self, p, args: ADetailerArgs) -> str: + sampler = args.ad_sampler if args.ad_use_sampler else p.sampler + + if sampler in ["PLMS", "UniPC"]: + sampler = "Euler" + return sampler + def get_initial_noise_multiplier(self, p, args: ADetailerArgs) -> float | None: if args.ad_use_noise_multiplier: return args.ad_noise_multiplier @@ -361,10 +372,7 @@ class AfterDetailerScript(scripts.Script): steps = self.get_steps(p, args) cfg_scale = self.get_cfg_scale(p, args) initial_noise_multiplier = self.get_initial_noise_multiplier(p, args) - - sampler_name = p.sampler_name - if sampler_name in ["PLMS", "UniPC"]: - sampler_name = "Euler" + sampler_name = self.get_sampler(p, args) i2i = StableDiffusionProcessingImg2Img( init_images=[image], @@ -491,14 +499,15 @@ class AfterDetailerScript(scripts.Script): @staticmethod def need_call_process(p) -> bool: - i = p.batch_index + i = p._ad_idx bs = p.batch_size - return i == bs - 1 + return i % bs == bs - 1 @staticmethod def need_call_postprocess(p) -> bool: - i = p.batch_index - return i == 0 + i = p._ad_idx + bs = p.batch_size + return i % bs == 0 @rich_traceback def process(self, p, *args_): @@ -718,6 +727,7 @@ def make_axis_on_xyz_grid(): return model_list = ["None", *model_mapping.keys()] + samplers = [sampler.name for sampler in all_samplers] def set_value(p, x, xs, *, field: str): if not hasattr(p, "adetailer_xyz"): @@ -762,6 +772,12 @@ def make_axis_on_xyz_grid(): int, partial(set_value, field="ad_inpaint_only_masked_padding"), ), + xyz_grid.AxisOption( + "[ADetailer] ADetailer sampler 1st", + str, + partial(set_value, field="ad_sampler"), + choices=lambda: samplers, + ), xyz_grid.AxisOption( "[ADetailer] ControlNet model 1st", str, diff --git a/sd_webui/sd_samplers.py b/sd_webui/sd_samplers.py new file mode 100644 index 0000000..168198f --- /dev/null +++ b/sd_webui/sd_samplers.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Callable, NamedTuple + + class SamplerData(NamedTuple): + name: str + constructor: Callable + aliases: list[str] + options: dict[str, Any] + + all_samplers: list[SamplerData] = [] + +else: + from modules.sd_samplers import all_samplers