feat(scripts): update pr options

This commit is contained in:
Dowon
2024-08-24 13:34:20 +09:00
parent a3506f3222
commit d705c6e30a
4 changed files with 71 additions and 83 deletions

View File

@@ -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,
]

View File

@@ -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]

View File

@@ -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",

View File

@@ -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(