Merge branch 'dev': v0.0.4
This commit is contained in:
@@ -21,7 +21,7 @@ def aspect_ratios_sorted(aspect_a: float, aspect_b: float, min_clamp: float = 1.
|
||||
return (aspect_b, aspect_a) if aspect_b > aspect_a else (aspect_a, aspect_b)
|
||||
|
||||
|
||||
def number_to_int(value: _t.Union[int, float], min: int = 1) -> int:
|
||||
def number_to_int(value: _t_number, min: int = 1) -> int:
|
||||
if not isinstance(value, int):
|
||||
if isinstance(value, float):
|
||||
sign = -1 if value < 0.0 else 1
|
||||
@@ -31,12 +31,17 @@ def number_to_int(value: _t.Union[int, float], min: int = 1) -> int:
|
||||
return max(value, min)
|
||||
|
||||
|
||||
def round_abs(abs_value: _t_number, step: int):
|
||||
def round_pos_int(value: float) -> int:
|
||||
"""Assuming a positive float is provided, rounds it to the nearest integer."""
|
||||
return int(value + 0.5)
|
||||
|
||||
|
||||
def round_abs_to_step(abs_value: _t_number, step: int):
|
||||
"""
|
||||
Assuming both args are positive and ``step`` is already an int, detect the closest positive (non-zero) value
|
||||
which is also divisible by step.
|
||||
"""
|
||||
n_steps = int(float(abs_value) / step + 0.5)
|
||||
n_steps = round_pos_int(float(abs_value) / step)
|
||||
n_steps = max(n_steps, 1)
|
||||
return step * n_steps, n_steps
|
||||
|
||||
@@ -46,12 +51,12 @@ def round_width_and_height_closest_to_the_ratio(width_f: _t_number, height_f: _t
|
||||
desired_width_to_height_ratio = float(width_f) / height_f
|
||||
|
||||
# First pass: directly from width_f and height_f
|
||||
width, n_steps_x = round_abs(width_f, step)
|
||||
height, n_steps_y = round_abs(height_f, step)
|
||||
width, n_steps_x = round_abs_to_step(width_f, step)
|
||||
height, n_steps_y = round_abs_to_step(height_f, step)
|
||||
|
||||
# Second pass: try calculating one side from already rounded another one:
|
||||
height_from_width, n_steps_y_from_x = round_abs(float(width) / desired_width_to_height_ratio, step)
|
||||
width_from_height, n_steps_x_from_y = round_abs(float(height) * desired_width_to_height_ratio, step)
|
||||
height_from_width, n_steps_y_from_x = round_abs_to_step(float(width) / desired_width_to_height_ratio, step)
|
||||
width_from_height, n_steps_x_from_y = round_abs_to_step(float(height) * desired_width_to_height_ratio, step)
|
||||
|
||||
# ... and select one of three options, closest to the perfect ratio:
|
||||
closest_delta = abs((float(width) / height) - desired_width_to_height_ratio)
|
||||
@@ -68,6 +73,23 @@ def round_width_and_height_closest_to_the_ratio(width_f: _t_number, height_f: _t
|
||||
return width, n_steps_x, height, n_steps_y
|
||||
|
||||
|
||||
def float_width_height_from_area(square_size: _t_number, landscape: bool, aspect_a: float, aspect_b: float):
|
||||
"""The main function for the regular (non-upscale) ``area``-subtype node."""
|
||||
# square_size = 1024; step = 48; landscape = True; aspect_a = 9.0; aspect_b = 16.0
|
||||
aspect_big, aspect_small = aspect_ratios_sorted(aspect_a, aspect_b)
|
||||
aspect_x, aspect_y = (aspect_big, aspect_small) if landscape else (aspect_small, aspect_big)
|
||||
|
||||
aspect_area = aspect_x * aspect_y
|
||||
aspect_norm_scale = 1.0 / _sqrt(aspect_area)
|
||||
aspect_x *= aspect_norm_scale
|
||||
aspect_y *= aspect_norm_scale
|
||||
# Now the two aspects produce a normalized rectangle - i.e., it's area is 1
|
||||
|
||||
width_f: float = aspect_x * square_size
|
||||
height_f: float = aspect_y * square_size
|
||||
return width_f, height_f
|
||||
|
||||
|
||||
def _format_report_square_part(
|
||||
width_f: float, height_f: _t_number,
|
||||
width: int, height: int,
|
||||
@@ -76,23 +98,23 @@ def _format_report_square_part(
|
||||
area_output = width * height
|
||||
if target_square_size is not None:
|
||||
target_square_size = max(abs(target_square_size), 1)
|
||||
target_square_size_i = int(target_square_size + 0.5)
|
||||
target_square_size_i = round_pos_int(target_square_size)
|
||||
if target_square_size_i * target_square_size_i == area_output:
|
||||
return f"=👑{target_square_size_i}🔳"
|
||||
# return f"=💯{target_square_size_i}🔳"
|
||||
# return f"=✨{target_square_size_i}🔳"
|
||||
if int(target_square_size * target_square_size + 0.5) == area_output:
|
||||
if round_pos_int(target_square_size * target_square_size) == area_output:
|
||||
return f"=✨{target_square_size:.2f}🔳"
|
||||
# return f"=🌟{target_square_size:.2f}🔳"
|
||||
|
||||
desired_square_area_f = float(width_f) * height_f
|
||||
desired_square_side_f = _sqrt(desired_square_area_f)
|
||||
desired_square_side_i = int(desired_square_side_f + 0.5)
|
||||
desired_square_side_i = round_pos_int(desired_square_side_f)
|
||||
if desired_square_side_i * desired_square_side_i == area_output:
|
||||
return f"=✅{desired_square_side_i}🔳"
|
||||
|
||||
actual_square_side_f = _sqrt(float(area_output))
|
||||
actual_square_side_i = int(actual_square_side_f + 0.5)
|
||||
actual_square_side_i = round_pos_int(actual_square_side_f)
|
||||
if abs(actual_square_side_f - actual_square_side_i) < 0.005:
|
||||
return f"~={actual_square_side_i}🔳"
|
||||
return f"~={actual_square_side_f:.2f}🔳"
|
||||
@@ -121,7 +143,7 @@ def format_report(
|
||||
|
||||
|
||||
def simple_result_from_approx_wh(
|
||||
width_f: float, height_f: _t_number, step: int,
|
||||
width_f: float, height_f: _t_number, step: int, show: bool = True,
|
||||
unique_id: str = None, target_square_size: _t_number = None
|
||||
):
|
||||
"""Final part of the main func for simple (non-upscale) nodes - when desired float/height are already calculated"""
|
||||
@@ -129,7 +151,13 @@ def simple_result_from_approx_wh(
|
||||
width, n_steps_x, height, n_steps_y = round_width_and_height_closest_to_the_ratio(width_f, height_f, step)
|
||||
|
||||
if unique_id:
|
||||
text = format_report(width_f, height_f, step, width, n_steps_x, height, n_steps_y, target_square_size)
|
||||
text = (
|
||||
format_report(width_f, height_f, step, width, n_steps_x, height, n_steps_y, target_square_size)
|
||||
if show
|
||||
# TODO: Planned for the future - currently, there's no point removing the text since it's box is shown anyway
|
||||
else '<span></span>' # An odd workaround since `send_progress_text()` doesn't want to update text when '' passed
|
||||
)
|
||||
# print(f"{unique_id} text: {text!r}")
|
||||
# Snatched from: https://github.com/comfyanonymous/ComfyUI/blob/27870ec3c30e56be9707d89a120eb7f0e2836be1/comfy_extras/nodes_images.py#L581-L582
|
||||
_PromptServer.instance.send_progress_text(text, unique_id)
|
||||
|
||||
|
||||
@@ -17,16 +17,17 @@ from .node_types import *
|
||||
from ._funcs import (
|
||||
aspect_ratios_sorted as _aspect_ratios_sorted,
|
||||
number_to_int as _number_to_int,
|
||||
simple_result_from_approx_wh as _simple_result_from_approx_wh
|
||||
simple_result_from_approx_wh as _simple_result_from_approx_wh,
|
||||
float_width_height_from_area as _float_width_height_from_area
|
||||
)
|
||||
|
||||
|
||||
# Tiny optimization by reusing the same immutable dict:
|
||||
_input_types_simple = deepfreeze({
|
||||
"required": {
|
||||
"width": (_IO.INT, dict(type_dict_res, **{"tooltip": "Approximate width"})),
|
||||
"height": (_IO.INT, dict(type_dict_res, **{"tooltip": "Approximate height"})),
|
||||
"step": (_IO.INT, dict(type_dict_step_init, **{"tooltip": (
|
||||
'required': {
|
||||
'width': (_IO.INT, dict(type_dict_res, **{'tooltip': "Approximate width"})),
|
||||
'height': (_IO.INT, dict(type_dict_res, **{'tooltip': "Approximate height"})),
|
||||
'step': (_IO.INT, dict(type_dict_step_init, **{'tooltip': (
|
||||
"Both width and height will be divisible by this value - by rounding them "
|
||||
"to the closest appropriate resolution.\n\n"
|
||||
"The default 48 is (8 * 3 * 2), so it's a safe choice because:\n"
|
||||
@@ -36,17 +37,22 @@ _input_types_simple = deepfreeze({
|
||||
"by 3 AND 9, which might become handy at that point, where you'll probably use UltimateSDUpscale.\n\n"
|
||||
"Other values worth trying first: 64, 96, 128."
|
||||
)})),
|
||||
# 'show': (_IO.BOOLEAN, {
|
||||
# 'default': True,
|
||||
# # 'label_on': 'on', 'label_off': 'off',
|
||||
# 'tooltip': "Show the result on the node itself?",
|
||||
# }),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID", # used for text display at the bottom of the node
|
||||
'hidden': {
|
||||
'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node
|
||||
},
|
||||
# "optional": {},
|
||||
# 'optional': {},
|
||||
})
|
||||
|
||||
# _return_types_simple = (_IO.INT, _IO.INT, _IO.STRING)
|
||||
# _return_names_simple = ("width", "height", "report")
|
||||
# _return_names_simple = ('width', 'height', 'report')
|
||||
_return_types_simple = (_IO.INT, _IO.INT)
|
||||
_return_names_simple = ("width", "height")
|
||||
_return_names_simple = ('width', 'height')
|
||||
|
||||
|
||||
class BestResolutionSimple:
|
||||
@@ -54,13 +60,13 @@ class BestResolutionSimple:
|
||||
The simplest node for easier selection of an optimal resolution:
|
||||
only rounding performed, desired image size specified directly.
|
||||
"""
|
||||
NODE_NAME = "BestResolutionSimple"
|
||||
NODE_NAME = 'BestResolutionSimple'
|
||||
CATEGORY = "utils/resolution"
|
||||
DESCRIPTION = _cleandoc(__doc__)
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
FUNCTION = "main"
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = _return_types_simple
|
||||
# RETURN_TYPES_TOOLTIPS = {}
|
||||
RETURN_NAMES = _return_names_simple
|
||||
@@ -69,8 +75,16 @@ class BestResolutionSimple:
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types_simple
|
||||
|
||||
def main(self, width: int, height: int, step: int, unique_id: str = None):
|
||||
return _simple_result_from_approx_wh(float(width), height, step, unique_id, _sqrt(width * height))
|
||||
def main(
|
||||
self, width: int, height: int, step: int,
|
||||
# show: bool,
|
||||
unique_id: str = None
|
||||
):
|
||||
return _simple_result_from_approx_wh(
|
||||
float(width), height, step,
|
||||
# show,
|
||||
unique_id=unique_id, target_square_size=_sqrt(width * height)
|
||||
)
|
||||
|
||||
|
||||
_tooltip_aspect = (
|
||||
@@ -80,36 +94,37 @@ _tooltip_aspect = (
|
||||
)
|
||||
|
||||
_input_types_orient = deepfreeze({
|
||||
"required": {
|
||||
"size": (_IO.INT, dict(type_dict_res, **{"tooltip": (
|
||||
'required': {
|
||||
'size': (_IO.INT, dict(type_dict_res, **{'tooltip': (
|
||||
"Approximate size of one of the image sides.\nWhich one - see the 'size_is_big' and 'landscape' toggles."
|
||||
)})),
|
||||
"step": _input_types_simple["required"]["step"],
|
||||
"size_is_big": (_IO.BOOLEAN, {
|
||||
"default": True, "label_on": "bigger", "label_off": "smaller",
|
||||
"tooltip": "When ON, size parameter represents the bigger image side.\nWhen OFF, it specifies the smaller one."
|
||||
'step': _input_types_simple['required']['step'],
|
||||
'size_is_big': (_IO.BOOLEAN, {
|
||||
'default': True, 'label_on': 'bigger', 'label_off': 'smaller',
|
||||
'tooltip': "When ON, size parameter represents the bigger image side.\nWhen OFF, it specifies the smaller one."
|
||||
}),
|
||||
"landscape": (_IO.BOOLEAN, {
|
||||
"default": True, "label_on": "landscape", "label_off": "portrait",
|
||||
"tooltip": (
|
||||
'landscape': (_IO.BOOLEAN, {
|
||||
'default': True, 'label_on': 'landscape', 'label_off': 'portrait',
|
||||
'tooltip': (
|
||||
"Specifies image orientation:\n\n"
|
||||
"When ON, width is bigger (image is horizontal).\n"
|
||||
"When OFF, height is bigger (image is vertical)."
|
||||
)
|
||||
),
|
||||
}),
|
||||
"aspect_a": (_IO.FLOAT, {
|
||||
"default": 16.0, "min": 1.0, "max": _sys.float_info.max, "step": 1.0, "round": 0.001,
|
||||
"tooltip": _tooltip_aspect
|
||||
'aspect_a': (_IO.FLOAT, {
|
||||
'default': 16.0, 'min': 1.0, 'max': _sys.float_info.max, 'step': 1.0, 'round': 0.001,
|
||||
'tooltip': _tooltip_aspect
|
||||
}),
|
||||
"aspect_b": (_IO.FLOAT, {
|
||||
"default": 9.0, "min": 1.0, "max": _sys.float_info.max, "step": 1.0, "round": 0.001,
|
||||
"tooltip": _tooltip_aspect
|
||||
'aspect_b': (_IO.FLOAT, {
|
||||
'default': 9.0, 'min': 1.0, 'max': _sys.float_info.max, 'step': 1.0, 'round': 0.001,
|
||||
'tooltip': _tooltip_aspect
|
||||
}),
|
||||
# 'show': _input_types_simple['required']['show'],
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
'hidden': {
|
||||
'unique_id': 'UNIQUE_ID',
|
||||
},
|
||||
# "optional": {},
|
||||
# 'optional': {},
|
||||
})
|
||||
|
||||
|
||||
@@ -118,13 +133,13 @@ class BestResolutionFromAspectRatio:
|
||||
A more advanced node for easier selection of an optimal resolution:
|
||||
image size selected indirectly - by one of the sides + aspect ratio.
|
||||
"""
|
||||
NODE_NAME = "BestResolutionFromAspectRatio"
|
||||
NODE_NAME = 'BestResolutionFromAspectRatio'
|
||||
CATEGORY = "utils/resolution"
|
||||
DESCRIPTION = _cleandoc(__doc__)
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
FUNCTION = "main"
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = _return_types_simple
|
||||
RETURN_NAMES = _return_names_simple
|
||||
|
||||
@@ -132,7 +147,12 @@ class BestResolutionFromAspectRatio:
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types_orient
|
||||
|
||||
def main(self, size: int, step: int, size_is_big: bool, landscape: bool, aspect_a: float, aspect_b: float, unique_id: str = None):
|
||||
def main(
|
||||
self,
|
||||
size: int, step: int, size_is_big: bool, landscape: bool, aspect_a: float, aspect_b: float,
|
||||
# show: bool,
|
||||
unique_id: str = None
|
||||
):
|
||||
aspect_big, aspect_small = _aspect_ratios_sorted(aspect_a, aspect_b)
|
||||
|
||||
side_main_f = float(size)
|
||||
@@ -145,27 +165,32 @@ class BestResolutionFromAspectRatio:
|
||||
# The opposite: height is bigger
|
||||
width_f, height_f = height_f, width_f
|
||||
|
||||
return _simple_result_from_approx_wh(width_f, height_f, step, unique_id, _sqrt(width_f * height_f))
|
||||
return _simple_result_from_approx_wh(
|
||||
width_f, height_f, step,
|
||||
# show,
|
||||
unique_id=unique_id, target_square_size=_sqrt(width_f * height_f)
|
||||
)
|
||||
|
||||
|
||||
_input_types_area = deepfreeze({
|
||||
"required": {
|
||||
"square_size": (_IO.INT, dict(type_dict_res, **{"tooltip": (
|
||||
'required': {
|
||||
'square_size': (_IO.INT, dict(type_dict_res, **{'tooltip': (
|
||||
"The total resolution of the image would be the same as of a square with this side.\n"
|
||||
"The width and height would be such to respect aspect ratio, but also be as close as possible to the "
|
||||
"total number of pixels as in this square image.\n\n"
|
||||
"- 512x512 square (SD 1.5): ~0.25 megapixels\n"
|
||||
"- 1024x1024 square (SDXL): ~1 megapixel"
|
||||
)})),
|
||||
"step": _input_types_simple["required"]["step"],
|
||||
"landscape": _input_types_orient["required"]["landscape"],
|
||||
"aspect_a": _input_types_orient["required"]["aspect_a"],
|
||||
"aspect_b": _input_types_orient["required"]["aspect_b"],
|
||||
'step': _input_types_simple['required']['step'],
|
||||
'landscape': _input_types_orient['required']['landscape'],
|
||||
'aspect_a': _input_types_orient['required']['aspect_a'],
|
||||
'aspect_b': _input_types_orient['required']['aspect_b'],
|
||||
# 'show': _input_types_simple['required']['show'],
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
'hidden': {
|
||||
'unique_id': 'UNIQUE_ID',
|
||||
},
|
||||
# "optional": {},
|
||||
# 'optional': {},
|
||||
})
|
||||
|
||||
|
||||
@@ -183,13 +208,13 @@ class BestResolutionFromArea:
|
||||
By simply providing this single number and setting your aspect ratio/orientation, you get the width and height to
|
||||
produce the closest total resolution to the training set, while also respecting image proportions and step-rounding.
|
||||
"""
|
||||
NODE_NAME = "BestResolutionFromArea"
|
||||
NODE_NAME = 'BestResolutionFromArea'
|
||||
CATEGORY = "utils/resolution"
|
||||
DESCRIPTION = _cleandoc(__doc__)
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
FUNCTION = "main"
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = _return_types_simple
|
||||
RETURN_NAMES = _return_names_simple
|
||||
|
||||
@@ -197,19 +222,16 @@ class BestResolutionFromArea:
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types_area
|
||||
|
||||
def main(self, square_size: int, step: int, landscape: bool, aspect_a: float, aspect_b: float, unique_id: str = None):
|
||||
# square_size = 1024; step = 48; landscape = True; aspect_a = 9.0; aspect_b = 16.0
|
||||
aspect_big, aspect_small = _aspect_ratios_sorted(aspect_a, aspect_b)
|
||||
aspect_x, aspect_y = (aspect_big, aspect_small) if landscape else (aspect_small, aspect_big)
|
||||
|
||||
aspect_area = aspect_x * aspect_y
|
||||
aspect_norm_scale = 1.0 / _sqrt(aspect_area)
|
||||
aspect_x *= aspect_norm_scale
|
||||
aspect_y *= aspect_norm_scale
|
||||
# Now the two aspects produce a normalized rectangle - i.e., it's area is 1
|
||||
|
||||
square_size = _number_to_int(square_size)
|
||||
width_f = aspect_x * square_size
|
||||
height_f = aspect_y * square_size
|
||||
|
||||
return _simple_result_from_approx_wh(width_f, height_f, step, unique_id, square_size)
|
||||
def main(
|
||||
self,
|
||||
square_size: int, step: int, landscape: bool, aspect_a: float, aspect_b: float,
|
||||
# show: bool,
|
||||
unique_id: str = None
|
||||
):
|
||||
square_size: int = _number_to_int(square_size)
|
||||
width_f, height_f = _float_width_height_from_area(square_size, landscape, aspect_a, aspect_b)
|
||||
return _simple_result_from_approx_wh(
|
||||
width_f, height_f, step,
|
||||
# show,
|
||||
unique_id=unique_id, target_square_size=square_size
|
||||
)
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "best-resolution"
|
||||
description = "QoL nodes for semi-automatic calculation of the best (most optimal) sampling resolution \n-compatible with ANY model (from now or the future), \n-accounting for upscale \n-and pixel-step."
|
||||
version = "0.0.3"
|
||||
description = "QoL nodes for semi-automatic calculation of the best (most optimal) sampling resolution \n• compatible with ANY model (from now or the future), \n• accounting for upscale \n• and pixel-step."
|
||||
version = "0.0.4"
|
||||
authors = [
|
||||
{name = "Lex Darlog"}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user