From 30330aa4d0a059515c8525072548663158e60143 Mon Sep 17 00:00:00 2001 From: Bingsu Date: Sat, 1 Jul 2023 09:43:00 +0900 Subject: [PATCH 1/9] feat: add `rich` package --- adetailer/__version__.py | 2 +- install.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/adetailer/__version__.py b/adetailer/__version__.py index 5da8775..4cd1184 100644 --- a/adetailer/__version__.py +++ b/adetailer/__version__.py @@ -1 +1 @@ -__version__ = "23.6.4" +__version__ = "23.6.5.dev0" diff --git a/install.py b/install.py index 7193feb..25bfba2 100644 --- a/install.py +++ b/install.py @@ -48,6 +48,7 @@ def install(): ("mediapipe", "0.10.0", None), ("huggingface_hub", None, None), ("pydantic", "1.10.8", None), + ("rich", "13.4.2", None), # mediapipe ("protobuf", "3.20.0", "3.20.9999"), ] From eec616afa35ea077c44bdca08a70e9a0ae1eb845 Mon Sep 17 00:00:00 2001 From: Bingsu Date: Sat, 1 Jul 2023 09:45:45 +0900 Subject: [PATCH 2/9] feat: rich print, traceback --- adetailer/common.py | 3 ++- adetailer/mediapipe.py | 2 +- scripts/!adetailer.py | 25 ++++++++++++++++++++----- sd_webui/script_callbacks.py | 4 ++++ 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/adetailer/common.py b/adetailer/common.py index 08477ed..481adbb 100644 --- a/adetailer/common.py +++ b/adetailer/common.py @@ -7,6 +7,7 @@ from typing import Optional, Union from huggingface_hub import hf_hub_download from PIL import Image, ImageDraw +from rich import print repo_id = "Bingsu/adetailer" @@ -22,7 +23,7 @@ def hf_download(file: str): try: path = hf_hub_download(repo_id, file) except Exception: - msg = f"[-] ADetailer: Failed to load model {file!r}" + msg = f"[-] ADetailer: Failed to load model {file!r} from huggingface" print(msg) path = "INVALID" return path diff --git a/adetailer/mediapipe.py b/adetailer/mediapipe.py index 7f949d3..0ec6b16 100644 --- a/adetailer/mediapipe.py +++ b/adetailer/mediapipe.py @@ -20,7 +20,7 @@ def mediapipe_predict( if model_type in mapping: func = mapping[model_type] return func(image, confidence) - msg = f"[-] ADetailer: Invalid mediapipe model type: {model_type}" + msg = f"[-] ADetailer: Invalid mediapipe model type: {model_type}, Available: {list(mapping.keys())!r}" raise RuntimeError(msg) diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 753f632..ade73a7 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -1,11 +1,12 @@ from __future__ import annotations +import io import os import platform import re import sys import traceback -from contextlib import contextmanager, suppress +from contextlib import contextmanager from copy import copy, deepcopy from functools import partial from pathlib import Path @@ -14,6 +15,8 @@ from typing import Any import gradio as gr import torch +from rich import print +from rich.console import Console import modules from adetailer import ( @@ -42,10 +45,6 @@ from sd_webui.processing import ( ) from sd_webui.shared import cmd_opts, opts, state -with suppress(ImportError): - from rich import print - - 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) @@ -84,6 +83,18 @@ def pause_total_tqdm(): opts.data["multiple_tqdm"] = orig +@contextmanager +def rich_traceback(): + string = io.StringIO() + console = Console(file=string, force_terminal=True) + try: + yield + except Exception as e: + console.print_exception(show_locals=True) + output = "\n" + string.getvalue() + raise RuntimeError(output) from e + + class AfterDetailerScript(scripts.Script): def __init__(self): super().__init__() @@ -519,6 +530,9 @@ class AfterDetailerScript(scripts.Script): if is_mediapipe: print(f"mediapipe: {steps} detected.") + _user_pt = p.prompt + _user_ng = p.negative_prompt + p2 = copy(i2i) for j in range(steps): p2.image_mask = masks[j] @@ -541,6 +555,7 @@ class AfterDetailerScript(scripts.Script): return False + @rich_traceback() def postprocess_image(self, p, pp, *args_): if getattr(p, "_disable_adetailer", False): return diff --git a/sd_webui/script_callbacks.py b/sd_webui/script_callbacks.py index 06f99fa..ebb3ac0 100644 --- a/sd_webui/script_callbacks.py +++ b/sd_webui/script_callbacks.py @@ -5,6 +5,9 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from typing import Callable + def on_app_started(callback: Callable): + pass + def on_ui_settings(callback: Callable): pass @@ -17,6 +20,7 @@ if TYPE_CHECKING: else: from modules.script_callbacks import ( on_after_component, + on_app_started, on_before_ui, on_ui_settings, ) From 90c4171e40c7ba9d19e6b490797755ead95d3b50 Mon Sep 17 00:00:00 2001 From: Bingsu Date: Sat, 1 Jul 2023 11:12:32 +0900 Subject: [PATCH 3/9] feat: ad_prompt wildcard trace --- scripts/!adetailer.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index ade73a7..67ecd3f 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -460,6 +460,16 @@ class AfterDetailerScript(scripts.Script): i2i.prompt = prompt i2i.negative_prompt = negative_prompt + @staticmethod + def compare_prompt(p, processed): + if p.prompt != processed.all_prompts[0]: + print(f"[-] ADetailer: applied ad_prompt: {processed.all_prompts[0]!r}") + + if p.negative_prompt != processed.all_negative_prompts[0]: + print( + f"[-] ADetailer: applied ad_negative_prompt: {processed.all_negative_prompts[0]!r}" + ) + def is_need_call_process(self, p) -> bool: i = p._idx n_iter = p.iteration @@ -543,6 +553,7 @@ class AfterDetailerScript(scripts.Script): cn_restore_unet_hook(p2, self.cn_latest_network) processed = process_images(p2) + self.compare_prompt(p2, processed) p2 = copy(i2i) p2.init_images = [processed.images[0]] From 1a1b0642de8a3c7273636bd1ab1e2f20991d5e22 Mon Sep 17 00:00:00 2001 From: Bingsu Date: Sat, 1 Jul 2023 13:51:11 +0900 Subject: [PATCH 4/9] feat: rich_traceback func --- adetailer/traceback.py | 119 +++++++++++++++++++++++++++++++++++++++++ scripts/!adetailer.py | 16 ++---- 2 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 adetailer/traceback.py diff --git a/adetailer/traceback.py b/adetailer/traceback.py new file mode 100644 index 0000000..2348152 --- /dev/null +++ b/adetailer/traceback.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import io +from typing import Any + +from rich.console import Console, Group +from rich.panel import Panel +from rich.table import Table +from rich.traceback import Traceback + + +def processing(*args): + try: + from modules.processing import ( + StableDiffusionProcessingImg2Img, + StableDiffusionProcessingTxt2Img, + ) + except ImportError: + return {} + + p = None + for arg in args: + if isinstance( + arg, (StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img) + ): + p = arg + break + + if not p: + return {} + + return { + "prompt": p.prompt, + "negative_prompt": p.negative_prompt, + "n_iter": p.n_iter, + "batch_size": p.batch_size, + "width": p.width, + "height": p.height, + "enable_hr": getattr(p, "enable_hr", False), + } + + +def ad_args(*args): + args = [ + arg + for arg in args + if isinstance(arg, dict) and arg.get("ad_model", "None") != "None" + ] + if not args: + return {} + + arg0 = args[0] + return { + "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"), + } + + +def sys_info(): + import platform + + try: + import launch + + version = launch.git_tag() + commit = launch.commit_hash() + except Exception: + return {} + + return { + "Platform": platform.platform(), + "Python": platform.python_version(), + "Version": version, + "Commit": commit, + } + + +def get_table(title: str, data: dict[str, Any]) -> Table: + table = Table(title=title, highlight=True) + for key, value in data.items(): + if not isinstance(value, str): + value = repr(value) + table.add_row(key, value) + + return table + + +def rich_traceback(func): + def wrapper(*args, **kwargs): + string = io.StringIO() + width = Console().width + width = width - 4 if width > 4 else None + console = Console(file=string, force_terminal=True, width=width) + try: + return func(*args, **kwargs) + except Exception as e: + tables = [ + get_table(title, data) + for title, data in [ + ("System info", sys_info()), + ("Inputs", processing(*args)), + ("ADetailer", ad_args(*args)), + ] + if data + ] + tables.append(Traceback()) + + console.print(Panel(Group(*tables))) + output = "\n" + string.getvalue() + + try: + error = e.__class__(output) + except Exception: + error = RuntimeError(output) + raise error from None + + return wrapper diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 67ecd3f..32d8c44 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -29,6 +29,7 @@ from adetailer import ( from adetailer.args import ALL_ARGS, BBOX_SORTBY, ADetailerArgs, EnableChecker from adetailer.common import PredictOutput from adetailer.mask import filter_by_ratio, mask_preprocess, sort_bboxes +from adetailer.traceback import rich_traceback from adetailer.ui import adui, ordinal, suffix from controlnet_ext import ControlNetExt, controlnet_exists, get_cn_models from controlnet_ext.restore import ( @@ -83,18 +84,6 @@ def pause_total_tqdm(): opts.data["multiple_tqdm"] = orig -@contextmanager -def rich_traceback(): - string = io.StringIO() - console = Console(file=string, force_terminal=True) - try: - yield - except Exception as e: - console.print_exception(show_locals=True) - output = "\n" + string.getvalue() - raise RuntimeError(output) from e - - class AfterDetailerScript(scripts.Script): def __init__(self): super().__init__() @@ -476,6 +465,7 @@ class AfterDetailerScript(scripts.Script): bs = p.batch_size return (i == (n_iter + 1) * bs - 1) and (i != len(p.all_prompts) - 1) + @rich_traceback def process(self, p, *args_): if getattr(p, "_disable_adetailer", False): return @@ -566,7 +556,7 @@ class AfterDetailerScript(scripts.Script): return False - @rich_traceback() + @rich_traceback def postprocess_image(self, p, pp, *args_): if getattr(p, "_disable_adetailer", False): return From 5f1ca4a27759a37d76b8f391d1ca5cd5669098fe Mon Sep 17 00:00:00 2001 From: Bingsu Date: Sat, 1 Jul 2023 15:09:27 +0900 Subject: [PATCH 5/9] feat: update traceback info --- adetailer/traceback.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/adetailer/traceback.py b/adetailer/traceback.py index 2348152..3ebc444 100644 --- a/adetailer/traceback.py +++ b/adetailer/traceback.py @@ -8,6 +8,8 @@ from rich.panel import Panel from rich.table import Table from rich.traceback import Traceback +from adetailer.__version__ import __version__ + def processing(*args): try: @@ -29,14 +31,34 @@ def processing(*args): if not p: return {} - return { + info = { "prompt": p.prompt, "negative_prompt": p.negative_prompt, "n_iter": p.n_iter, "batch_size": p.batch_size, "width": p.width, "height": p.height, + "sampler_name": p.sampler_name, "enable_hr": getattr(p, "enable_hr", False), + "hr_upscaler": getattr(p, "hr_upscaler", ""), + } + + info.update(sd_models()) + return info + + +def sd_models(): + try: + from modules import shared + + opts = shared.opts + except Exception: + return {} + + return { + "checkpoint": getattr(opts, "sd_model_checkpoint", "------"), + "vae": getattr(opts, "sd_vae", "------"), + "unet": getattr(opts, "sd_unet", "------"), } @@ -51,6 +73,7 @@ def ad_args(*args): arg0 = args[0] return { + "version": __version__, "ad_model": arg0["ad_model"], "ad_prompt": arg0.get("ad_prompt", ""), "ad_negative_prompt": arg0.get("ad_negative_prompt", ""), @@ -60,6 +83,7 @@ def ad_args(*args): def sys_info(): import platform + import sys try: import launch @@ -67,18 +91,21 @@ def sys_info(): version = launch.git_tag() commit = launch.commit_hash() except Exception: - return {} + version = commit = "------" return { "Platform": platform.platform(), - "Python": platform.python_version(), + "Python": sys.version, "Version": version, "Commit": commit, + "Commandline": sys.argv, } def get_table(title: str, data: dict[str, Any]) -> Table: table = Table(title=title, highlight=True) + table.add_column(" ", style="dim") + table.add_column("Value") for key, value in data.items(): if not isinstance(value, str): value = repr(value) From 2bd4db94d5f36da78b71de0c1bfc7804d990adf2 Mon Sep 17 00:00:00 2001 From: Bingsu Date: Sun, 2 Jul 2023 13:11:03 +0900 Subject: [PATCH 6/9] fix: prevent chaning values during generation --- adetailer/ui.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/adetailer/ui.py b/adetailer/ui.py index 5547578..865ecd1 100644 --- a/adetailer/ui.py +++ b/adetailer/ui.py @@ -175,11 +175,6 @@ def one_ui_group( with gr.Group(): controlnet(w, n, is_img2img) - for attr in ALL_ARGS.attrs: - widget = getattr(w, attr) - on_change = partial(on_widget_change, attr=attr) - widget.change(fn=on_change, inputs=[state, widget], outputs=state, queue=False) - all_inputs = [state, *w.tolist()] target_button = i2i_button if is_img2img else t2i_button target_button.click( From 3402c29978763dd18b4aebb43e2a105b208102b8 Mon Sep 17 00:00:00 2001 From: Bingsu Date: Sun, 2 Jul 2023 13:21:28 +0900 Subject: [PATCH 7/9] feat: ignore NansException --- scripts/!adetailer.py | 9 ++++++++- sd_webui/devices.py | 11 +++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 sd_webui/devices.py diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 32d8c44..a9cfaa4 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -38,6 +38,7 @@ from controlnet_ext.restore import ( cn_restore_unet_hook, ) from sd_webui import images, safe, script_callbacks, scripts, shared +from sd_webui.devices import NansException from sd_webui.paths import data_path, models_path from sd_webui.processing import ( StableDiffusionProcessingImg2Img, @@ -541,7 +542,13 @@ class AfterDetailerScript(scripts.Script): if not re.match(r"^\s*\[SKIP\]\s*$", p2.prompt): if args.ad_controlnet_model == "None": cn_restore_unet_hook(p2, self.cn_latest_network) - processed = process_images(p2) + + try: + processed = process_images(p2) + except NansException as e: + msg = f"[-] ADetailer: 'NansException' occurred with {ordinal(n + 1)} settings.\n{e}" + print(msg) + return False self.compare_prompt(p2, processed) p2 = copy(i2i) diff --git a/sd_webui/devices.py b/sd_webui/devices.py new file mode 100644 index 0000000..51d0569 --- /dev/null +++ b/sd_webui/devices.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + + class NansException(Exception): # noqa: N818 + pass + +else: + from modules.devices import NansException From f8c5f0f74ce3f1d5e5a410fd86fd6828577f91ee Mon Sep 17 00:00:00 2001 From: Bingsu Date: Sun, 2 Jul 2023 13:32:23 +0900 Subject: [PATCH 8/9] fix: misc --- adetailer/traceback.py | 40 +++++++++++++++++++++++++--------------- scripts/!adetailer.py | 17 ++++++++++------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/adetailer/traceback.py b/adetailer/traceback.py index 3ebc444..46dc85f 100644 --- a/adetailer/traceback.py +++ b/adetailer/traceback.py @@ -1,7 +1,9 @@ from __future__ import annotations import io -from typing import Any +import platform +import sys +from typing import Any, Callable from rich.console import Console, Group from rich.panel import Panel @@ -11,7 +13,7 @@ from rich.traceback import Traceback from adetailer.__version__ import __version__ -def processing(*args): +def processing(*args: Any) -> dict[str, Any]: try: from modules.processing import ( StableDiffusionProcessingImg2Img, @@ -28,7 +30,7 @@ def processing(*args): p = arg break - if not p: + if p is None: return {} info = { @@ -47,7 +49,7 @@ def processing(*args): return info -def sd_models(): +def sd_models() -> dict[str, str]: try: from modules import shared @@ -62,16 +64,16 @@ def sd_models(): } -def ad_args(*args): - args = [ +def ad_args(*args: Any) -> dict[str, str]: + ad_args = [ arg for arg in args if isinstance(arg, dict) and arg.get("ad_model", "None") != "None" ] - if not args: + if not ad_args: return {} - arg0 = args[0] + arg0 = ad_args[0] return { "version": __version__, "ad_model": arg0["ad_model"], @@ -81,10 +83,7 @@ def ad_args(*args): } -def sys_info(): - import platform - import sys - +def sys_info() -> dict[str, Any]: try: import launch @@ -104,7 +103,7 @@ def sys_info(): def get_table(title: str, data: dict[str, Any]) -> Table: table = Table(title=title, highlight=True) - table.add_column(" ", style="dim") + table.add_column(" ", justify="right", style="dim") table.add_column("Value") for key, value in data.items(): if not isinstance(value, str): @@ -114,12 +113,23 @@ def get_table(title: str, data: dict[str, Any]) -> Table: return table -def rich_traceback(func): +def force_terminal_value(): + try: + from modules.shared import cmd_opts + + return True if hasattr(cmd_opts, "skip_torch_cuda_test") else None + except Exception: + return None + + +def rich_traceback(func: Callable) -> Callable: + force_terminal = force_terminal_value() + def wrapper(*args, **kwargs): string = io.StringIO() width = Console().width width = width - 4 if width > 4 else None - console = Console(file=string, force_terminal=True, width=width) + console = Console(file=string, force_terminal=force_terminal, width=width) try: return func(*args, **kwargs) except Exception as e: diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index a9cfaa4..461b9cf 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -1,6 +1,5 @@ from __future__ import annotations -import io import os import platform import re @@ -16,7 +15,6 @@ from typing import Any import gradio as gr import torch from rich import print -from rich.console import Console import modules from adetailer import ( @@ -94,6 +92,9 @@ class AfterDetailerScript(scripts.Script): self.cn_script = None self.cn_latest_network = None + def __repr__(self): + return f"{self.__class__.__name__}(version={__version__})" + def title(self): return AFTER_DETAILER @@ -451,13 +452,15 @@ class AfterDetailerScript(scripts.Script): i2i.negative_prompt = negative_prompt @staticmethod - def compare_prompt(p, processed): + def compare_prompt(p, processed, n: int = 0): if p.prompt != processed.all_prompts[0]: - print(f"[-] ADetailer: applied ad_prompt: {processed.all_prompts[0]!r}") + print( + f"[-] ADetailer: applied {ordinal(n + 1)} ad_prompt: {processed.all_prompts[0]!r}" + ) if p.negative_prompt != processed.all_negative_prompts[0]: print( - f"[-] ADetailer: applied ad_negative_prompt: {processed.all_negative_prompts[0]!r}" + f"[-] ADetailer: applied {ordinal(n + 1)} ad_negative_prompt: {processed.all_negative_prompts[0]!r}" ) def is_need_call_process(self, p) -> bool: @@ -547,10 +550,10 @@ class AfterDetailerScript(scripts.Script): processed = process_images(p2) except NansException as e: msg = f"[-] ADetailer: 'NansException' occurred with {ordinal(n + 1)} settings.\n{e}" - print(msg) + print(msg, file=sys.stderr) return False - self.compare_prompt(p2, processed) + self.compare_prompt(p2, processed, n=n) p2 = copy(i2i) p2.init_images = [processed.images[0]] From 15cd8ce7d21503892def5bfb46130a3696d9e935 Mon Sep 17 00:00:00 2001 From: Bingsu Date: Sun, 2 Jul 2023 15:09:45 +0900 Subject: [PATCH 9/9] chore: v23.7.0 --- CHANGELOG.md | 8 ++++++++ adetailer/__version__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44daffc..f92f104 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 2023-07-02 + +- v23.7.0 +- `NansException`이 발생하면 로그에 표시하고 원본 이미지를 반환하게 설정 +- `rich`를 사용한 에러 트레이싱 + - install.py에 `rich` 추가 +- 생성 중에 컴포넌트의 값을 변경하면 args의 값도 함께 변경되는 문제 수정 (issue #180) + ## 2023-06-28 - v23.6.4 diff --git a/adetailer/__version__.py b/adetailer/__version__.py index 4cd1184..f4744b8 100644 --- a/adetailer/__version__.py +++ b/adetailer/__version__.py @@ -1 +1 @@ -__version__ = "23.6.5.dev0" +__version__ = "23.7.0"