mirror of
https://github.com/Bing-su/adetailer.git
synced 2026-01-26 11:19:53 +00:00
feat(scripts): update pr options
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections import UserList
|
from collections import UserList
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
from functools import cached_property, partial
|
from functools import cached_property, partial
|
||||||
from typing import Any, Literal, NamedTuple, Optional
|
from typing import Any, Literal, NamedTuple, Optional
|
||||||
|
|
||||||
@@ -262,11 +263,7 @@ BBOX_SORTBY = [
|
|||||||
"Position (center to edge)",
|
"Position (center to edge)",
|
||||||
"Area (large to small)",
|
"Area (large to small)",
|
||||||
]
|
]
|
||||||
INPAINT_BBOX_MATCH_MODES = [
|
|
||||||
"Off",
|
|
||||||
"Strict (SDXL only)",
|
|
||||||
"Free",
|
|
||||||
]
|
|
||||||
MASK_MERGE_INVERT = ["None", "Merge", "Merge and Invert"]
|
MASK_MERGE_INVERT = ["None", "Merge", "Merge and Invert"]
|
||||||
|
|
||||||
_script_default = (
|
_script_default = (
|
||||||
@@ -281,3 +278,16 @@ SCRIPT_DEFAULT = ",".join(sorted(_script_default))
|
|||||||
|
|
||||||
_builtin_script = ("soft_inpainting", "hypertile_script")
|
_builtin_script = ("soft_inpainting", "hypertile_script")
|
||||||
BUILTIN_SCRIPT = ",".join(sorted(_builtin_script))
|
BUILTIN_SCRIPT = ",".join(sorted(_builtin_script))
|
||||||
|
|
||||||
|
|
||||||
|
class InpaintBBoxMatchMode(Enum):
|
||||||
|
OFF = "Off"
|
||||||
|
STRICT = "Strict (SDXL only)"
|
||||||
|
FREE = "Free"
|
||||||
|
|
||||||
|
|
||||||
|
INPAINT_BBOX_MATCH_MODES = [
|
||||||
|
InpaintBBoxMatchMode.OFF.value,
|
||||||
|
InpaintBBoxMatchMode.STRICT.value,
|
||||||
|
InpaintBBoxMatchMode.FREE.value,
|
||||||
|
]
|
||||||
|
|||||||
@@ -8,12 +8,16 @@ import numpy as np
|
|||||||
T = TypeVar("T", int, float)
|
T = TypeVar("T", int, float)
|
||||||
|
|
||||||
|
|
||||||
def get_dynamic_denoise_strength(
|
def dynamic_denoise_strength(
|
||||||
denoise_power: float,
|
denoise_power: float,
|
||||||
denoise_strength: float,
|
denoise_strength: float,
|
||||||
bbox: Sequence[T],
|
bbox: Sequence[T],
|
||||||
image_size: tuple[int, int],
|
image_size: tuple[int, int],
|
||||||
) -> float:
|
) -> float:
|
||||||
|
if len(bbox) != 4:
|
||||||
|
msg = f"bbox length must be 4, got {len(bbox)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
if np.isclose(denoise_power, 0.0) or len(bbox) != 4:
|
if np.isclose(denoise_power, 0.0) or len(bbox) != 4:
|
||||||
return denoise_strength
|
return denoise_strength
|
||||||
|
|
||||||
@@ -45,7 +49,8 @@ class _OptimalCropSize:
|
|||||||
self, inpaint_width: int, inpaint_height: int, bbox: Sequence[T]
|
self, inpaint_width: int, inpaint_height: int, bbox: Sequence[T]
|
||||||
) -> tuple[int, int]:
|
) -> tuple[int, int]:
|
||||||
if len(bbox) != 4:
|
if len(bbox) != 4:
|
||||||
return inpaint_width, inpaint_height
|
msg = f"bbox length must be 4, got {len(bbox)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
bbox_width = bbox[2] - bbox[0]
|
bbox_width = bbox[2] - bbox[0]
|
||||||
bbox_height = bbox[3] - bbox[1]
|
bbox_height = bbox[3] - bbox[1]
|
||||||
@@ -70,7 +75,8 @@ class _OptimalCropSize:
|
|||||||
self, inpaint_width: int, inpaint_height: int, bbox: Sequence[T]
|
self, inpaint_width: int, inpaint_height: int, bbox: Sequence[T]
|
||||||
) -> tuple[int, int]:
|
) -> tuple[int, int]:
|
||||||
if len(bbox) != 4:
|
if len(bbox) != 4:
|
||||||
return inpaint_width, inpaint_height
|
msg = f"bbox length must be 4, got {len(bbox)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
bbox_width = bbox[2] - bbox[0]
|
bbox_width = bbox[2] - bbox[0]
|
||||||
bbox_height = bbox[3] - bbox[1]
|
bbox_height = bbox[3] - bbox[1]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import platform
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
from collections.abc import Sequence
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -45,6 +46,7 @@ from adetailer.args import (
|
|||||||
INPAINT_BBOX_MATCH_MODES,
|
INPAINT_BBOX_MATCH_MODES,
|
||||||
SCRIPT_DEFAULT,
|
SCRIPT_DEFAULT,
|
||||||
ADetailerArgs,
|
ADetailerArgs,
|
||||||
|
InpaintBBoxMatchMode,
|
||||||
SkipImg2ImgOrig,
|
SkipImg2ImgOrig,
|
||||||
)
|
)
|
||||||
from adetailer.common import PredictOutput, ensure_pil_image, safe_mkdir
|
from adetailer.common import PredictOutput, ensure_pil_image, safe_mkdir
|
||||||
@@ -56,6 +58,7 @@ from adetailer.mask import (
|
|||||||
mask_preprocess,
|
mask_preprocess,
|
||||||
sort_bboxes,
|
sort_bboxes,
|
||||||
)
|
)
|
||||||
|
from adetailer.opts import dynamic_denoise_strength, optimal_crop_size
|
||||||
from controlnet_ext import (
|
from controlnet_ext import (
|
||||||
CNHijackRestore,
|
CNHijackRestore,
|
||||||
ControlNetExt,
|
ControlNetExt,
|
||||||
@@ -675,84 +678,54 @@ class AfterDetailerScript(scripts.Script):
|
|||||||
return images.resize_image(p.resize_mode, mask, width, height)
|
return images.resize_image(p.resize_mode, mask, width, height)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_dynamic_denoise_strength(denoise_strength, bbox, image):
|
def get_dynamic_denoise_strength(
|
||||||
|
denoise_strength: float, bbox: Sequence[Any], image_size: tuple[int, int]
|
||||||
|
):
|
||||||
denoise_power = opts.data.get("ad_dynamic_denoise_power", 0)
|
denoise_power = opts.data.get("ad_dynamic_denoise_power", 0)
|
||||||
if denoise_power == 0:
|
if denoise_power == 0:
|
||||||
return denoise_strength
|
return denoise_strength
|
||||||
|
|
||||||
image_pixels = image.width * image.height
|
modified_strength = dynamic_denoise_strength(
|
||||||
bbox_pixels = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])
|
denoise_power=denoise_power,
|
||||||
|
denoise_strength=denoise_strength,
|
||||||
normalized_area = bbox_pixels / image_pixels
|
bbox=bbox,
|
||||||
denoise_modifier = (1.0 - normalized_area) ** denoise_power
|
image_size=image_size,
|
||||||
|
|
||||||
print(
|
|
||||||
f"[-] ADetailer: dynamic denoising -- {denoise_modifier:.2f} * {denoise_strength:.2f} = {denoise_strength * denoise_modifier:.2f}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return denoise_strength * denoise_modifier
|
print(
|
||||||
|
f"[-] ADetailer: dynamic denoising -- {denoise_strength:.2f} -> {modified_strength:.2f}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return modified_strength
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_optimal_crop_image_size(inpaint_width, inpaint_height, bbox):
|
def get_optimal_crop_image_size(
|
||||||
calculate_optimal_crop = opts.data.get("ad_match_inpaint_bbox_size", "Off")
|
inpaint_width: int, inpaint_height: int, bbox: Sequence[Any]
|
||||||
if calculate_optimal_crop == "Off":
|
):
|
||||||
|
calculate_optimal_crop = opts.data.get(
|
||||||
|
"ad_match_inpaint_bbox_size", InpaintBBoxMatchMode.OFF.value
|
||||||
|
)
|
||||||
|
|
||||||
|
if calculate_optimal_crop == InpaintBBoxMatchMode.OFF.value:
|
||||||
return (inpaint_width, inpaint_height)
|
return (inpaint_width, inpaint_height)
|
||||||
|
|
||||||
optimal_resolution = None
|
optimal_resolution: tuple[int, int] | None = None
|
||||||
|
|
||||||
bbox_width = bbox[2] - bbox[0]
|
if calculate_optimal_crop == InpaintBBoxMatchMode.STRICT.value:
|
||||||
bbox_height = bbox[3] - bbox[1]
|
|
||||||
bbox_aspect_ratio = bbox_width / bbox_height
|
|
||||||
|
|
||||||
if calculate_optimal_crop == "Strict (SDXL only)":
|
|
||||||
if not shared.sd_model.is_sdxl:
|
if not shared.sd_model.is_sdxl:
|
||||||
msg = "[-] ADetailer: strict inpaint bounding box size matching is only available for SDXL. Use Free mode instead."
|
msg = "[-] ADetailer: strict inpaint bounding box size matching is only available for SDXL. Use Free mode instead."
|
||||||
print(msg)
|
print(msg)
|
||||||
return (inpaint_width, inpaint_height)
|
return (inpaint_width, inpaint_height)
|
||||||
|
|
||||||
# Limit resolutions to those SDXL was trained on.
|
optimal_resolution = optimal_crop_size.sdxl(
|
||||||
resolutions = [
|
inpaint_width, inpaint_height, bbox
|
||||||
(1024, 1024),
|
|
||||||
(1152, 896),
|
|
||||||
(896, 1152),
|
|
||||||
(1216, 832),
|
|
||||||
(832, 1216),
|
|
||||||
(1344, 768),
|
|
||||||
(768, 1344),
|
|
||||||
(1536, 640),
|
|
||||||
(640, 1536),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Filter resolutions smaller than bbox, and any that could result in a total pixel size smaller than the current inpaint dimensions.
|
|
||||||
resolutions = [
|
|
||||||
res
|
|
||||||
for res in resolutions
|
|
||||||
if (res[0] >= bbox_width and res[1] >= bbox_height)
|
|
||||||
and (res[0] >= inpaint_width or res[1] >= inpaint_height)
|
|
||||||
]
|
|
||||||
|
|
||||||
if not resolutions:
|
|
||||||
return (inpaint_width, inpaint_height)
|
|
||||||
|
|
||||||
optimal_resolution = min(
|
|
||||||
resolutions,
|
|
||||||
key=lambda res: abs((res[0] / res[1]) - bbox_aspect_ratio),
|
|
||||||
)
|
)
|
||||||
elif calculate_optimal_crop == "Free":
|
|
||||||
scale_size = max(inpaint_width, inpaint_height)
|
|
||||||
|
|
||||||
if bbox_aspect_ratio > 1:
|
elif calculate_optimal_crop == InpaintBBoxMatchMode.FREE.value:
|
||||||
optimal_width = scale_size
|
optimal_resolution = optimal_crop_size.free(
|
||||||
optimal_height = scale_size / bbox_aspect_ratio
|
inpaint_width, inpaint_height, bbox
|
||||||
else:
|
)
|
||||||
optimal_width = scale_size * bbox_aspect_ratio
|
|
||||||
optimal_height = scale_size
|
|
||||||
|
|
||||||
# Round up to the nearest multiple of 8 to make the dimensions friendly for upscaling/diffusion.
|
|
||||||
optimal_width = ((optimal_width + 8 - 1) // 8) * 8
|
|
||||||
optimal_height = ((optimal_height + 8 - 1) // 8) * 8
|
|
||||||
|
|
||||||
optimal_resolution = (int(optimal_width), int(optimal_height))
|
|
||||||
else:
|
else:
|
||||||
msg = "[-] ADetailer: unsupported inpaint bounding box match mode. Original inpainting dimensions will be used."
|
msg = "[-] ADetailer: unsupported inpaint bounding box match mode. Original inpainting dimensions will be used."
|
||||||
print(msg)
|
print(msg)
|
||||||
@@ -873,14 +846,13 @@ class AfterDetailerScript(scripts.Script):
|
|||||||
|
|
||||||
p2.seed = self.get_each_tab_seed(seed, j)
|
p2.seed = self.get_each_tab_seed(seed, j)
|
||||||
p2.subseed = self.get_each_tab_seed(subseed, j)
|
p2.subseed = self.get_each_tab_seed(subseed, j)
|
||||||
|
p2.denoising_strength = self.get_dynamic_denoise_strength(
|
||||||
|
p2.denoising_strength, pred.bboxes[j], pp.image.size
|
||||||
|
)
|
||||||
|
|
||||||
p2.cached_c = [None, None]
|
p2.cached_c = [None, None]
|
||||||
p2.cached_uc = [None, None]
|
p2.cached_uc = [None, None]
|
||||||
|
|
||||||
p2.denoising_strength = self.get_dynamic_denoise_strength(
|
|
||||||
p2.denoising_strength, pred.bboxes[j], pp.image
|
|
||||||
)
|
|
||||||
|
|
||||||
# Don't override user-defined dimensions.
|
# Don't override user-defined dimensions.
|
||||||
if not args.ad_use_inpaint_width_height:
|
if not args.ad_use_inpaint_width_height:
|
||||||
p2.width, p2.height = self.get_optimal_crop_image_size(
|
p2.width, p2.height = self.get_optimal_crop_image_size(
|
||||||
@@ -1055,7 +1027,7 @@ def on_ui_settings():
|
|||||||
shared.opts.add_option(
|
shared.opts.add_option(
|
||||||
"ad_match_inpaint_bbox_size",
|
"ad_match_inpaint_bbox_size",
|
||||||
shared.OptionInfo(
|
shared.OptionInfo(
|
||||||
default="Off",
|
default=InpaintBBoxMatchMode.OFF.value, # Off
|
||||||
component=gr.Radio,
|
component=gr.Radio,
|
||||||
component_args={"choices": INPAINT_BBOX_MATCH_MODES},
|
component_args={"choices": INPAINT_BBOX_MATCH_MODES},
|
||||||
label="Try to match inpainting size to bounding box size, if 'Use separate width/height' is not set",
|
label="Try to match inpainting size to bounding box size, if 'Use separate width/height' is not set",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import pytest
|
|||||||
from hypothesis import assume, given
|
from hypothesis import assume, given
|
||||||
from hypothesis import strategies as st
|
from hypothesis import strategies as st
|
||||||
|
|
||||||
from adetailer.opts import get_dynamic_denoise_strength, optimal_crop_size
|
from adetailer.opts import dynamic_denoise_strength, optimal_crop_size
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -17,29 +17,29 @@ from adetailer.opts import get_dynamic_denoise_strength, optimal_crop_size
|
|||||||
(-0.5, 0.5, [0, 0, 100, 100], (1000, 1000), 0.502518907629606),
|
(-0.5, 0.5, [0, 0, 100, 100], (1000, 1000), 0.502518907629606),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_get_dynamic_denoise_strength(
|
def test_dynamic_denoise_strength(
|
||||||
denoise_power: float,
|
denoise_power: float,
|
||||||
denoise_strength: float,
|
denoise_strength: float,
|
||||||
bbox: list[int],
|
bbox: list[int],
|
||||||
image_size: tuple[int, int],
|
image_size: tuple[int, int],
|
||||||
expected_result: float,
|
expected_result: float,
|
||||||
):
|
):
|
||||||
result = get_dynamic_denoise_strength(
|
result = dynamic_denoise_strength(denoise_power, denoise_strength, bbox, image_size)
|
||||||
denoise_power, denoise_strength, bbox, image_size
|
|
||||||
)
|
|
||||||
assert np.isclose(result, expected_result)
|
assert np.isclose(result, expected_result)
|
||||||
|
|
||||||
|
|
||||||
@given(denoise_strength=st.floats(allow_nan=False))
|
@given(denoise_strength=st.floats(allow_nan=False))
|
||||||
def test_get_dynamic_denoise_strength_no_bbox(denoise_strength: float):
|
def test_dynamic_denoise_strength_no_bbox(denoise_strength: float):
|
||||||
result = get_dynamic_denoise_strength(0.5, denoise_strength, [], (1000, 1000))
|
with pytest.raises(ValueError, match="bbox length must be 4, got 0"):
|
||||||
assert result == denoise_strength
|
dynamic_denoise_strength(0.5, denoise_strength, [], (1000, 1000))
|
||||||
|
|
||||||
|
|
||||||
@given(denoise_strength=st.floats(allow_nan=False))
|
@given(denoise_strength=st.floats(allow_nan=False))
|
||||||
def test_get_dynamic_denoise_strength_zero_power(denoise_strength: float):
|
def test_dynamic_denoise_strength_zero_power(denoise_strength: float):
|
||||||
result = get_dynamic_denoise_strength(0.0, denoise_strength, [], (1000, 1000))
|
result = dynamic_denoise_strength(
|
||||||
assert result == denoise_strength
|
0.0, denoise_strength, [0, 0, 100, 100], (1000, 1000)
|
||||||
|
)
|
||||||
|
assert np.isclose(result, denoise_strength)
|
||||||
|
|
||||||
|
|
||||||
@given(
|
@given(
|
||||||
|
|||||||
Reference in New Issue
Block a user