Rework ImageAutosize for ComfyUI v3 API
Rebuild ImageAutosize to use ComfyUI V3 node API: add a proper ComfyExtension entrypoint and io.Schema-based node. Rename display to "Image/Mask Autosize", accept IMAGE or MASK inputs, and replace multiplier output with scale_x/scale_y. Add constraint_priority (min_size/max_size), anchored crop handling, divisible-by rounding protection, and new interpolation modes. Update README, add CHANGELOG, bump package version to 0.2.0, refresh workflow image, and include comprehensive unit tests for sizing, cropping, and mask behavior.
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## 0.2.0 - 2026 July 31
|
||||
|
||||
### Changed
|
||||
|
||||
- Rebuilt Image Autosize with ComfyUI's V3 node API.
|
||||
- Renamed the node display name to `Image/Mask Autosize`.
|
||||
- Accept `IMAGE` or `MASK` inputs and return the matching type.
|
||||
- Add `constraint_priority` with `min_size` and `max_size` policies.
|
||||
- Use ComfyUI's shared resize implementation to preserve device, dtype, and
|
||||
tensor precision for native interpolation modes.
|
||||
- Replace the `multiplier` output with `scale_x` and append `scale_y`.
|
||||
- Report scales from the actual intermediate resize dimensions before an
|
||||
anchored crop.
|
||||
- Prevent divisible-dimension rounding from producing a zero-sized output.
|
||||
- Replace the Pillow interpolation list with `nearest-exact`, `bilinear`,
|
||||
`area`, `bicubic`, and `lanczos`.
|
||||
|
||||
### Compatibility
|
||||
|
||||
- Existing output index 3 now returns `scale_x`; `scale_y` is output index 4.
|
||||
- The default `min_size` priority preserves the previous sizing order.
|
||||
- Workflows using `nearest`, `box`, or `hamming` must select a supported
|
||||
interpolation mode.
|
||||
- Existing `IMAGE` workflows, input names, node ID, and the first three output
|
||||
positions remain unchanged.
|
||||
@@ -1,31 +1,101 @@
|
||||
# ComfyUI-ImageAutosize
|
||||
|
||||
A node for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) that provides a convenient way of resizing or cropping an image for diffusion tasks.
|
||||
A set-and-forget image and mask resizer for diffusion workflows in
|
||||
[ComfyUI](https://github.com/Comfy-Org/ComfyUI).
|
||||
|
||||
This node was designed with the goal of being easy to "set and forget," allowing you to pre-process any input image without having to frequently adjust your parameters.
|
||||
Image/Mask Autosize combines a longer-dimension target, a shorter-dimension
|
||||
target, configurable constraint priority, and divisible output dimensions in
|
||||
one operation. It also provides anchored cropping and reports the exact
|
||||
horizontal and vertical resize scales.
|
||||
|
||||

|
||||

|
||||
|
||||
### Installation
|
||||
## Sizing behavior
|
||||
|
||||
Simply drag the image above into ComfyUI and use [ComfyUI Manager » Install Missing Custom Nodes](https://github.com/ltdrdata/ComfyUI-Manager).
|
||||
The node calculates two aspect-preserving resize scales:
|
||||
|
||||
### Inputs
|
||||
- The scale that makes the longer dimension equal `max_size`.
|
||||
- The scale that makes the shorter dimension equal `min_size`.
|
||||
|
||||
- `image`: The image to modify.
|
||||
- `max_size` (int): The node will resize the **larger dimension** of the `image` to this size in pixels, preserving aspect ratio.
|
||||
- `min_size` (int): After evaluating `max_size`, the node will check the **smaller dimension** of the `image`. If it's less than `min_size`, the smaller dimension will be upscaled to this value, preserving aspect ratio.
|
||||
- `divisible_by` (int): After evaluating both `max_size` and `min_size`, the dimensions of the image will be rounded to values that are multiples of `divisible_by`. This is useful for diffusion models, which often expect sizes that are divisible by 32.
|
||||
- `interpolation_mode` (string): Name of the resizing method.
|
||||
- `crop_mode` (string): Cropping is often needed to satisfy `divisble_by`, and this input lets you choose the origin of the crop. Set it to `none` to disable cropping altogether.
|
||||
`constraint_priority` determines which scale is used:
|
||||
|
||||
### Outputs
|
||||
- `min_size`: Uses the larger scale. This is equivalent to applying `max_size`
|
||||
first and then enlarging as needed to enforce `min_size`. The shorter
|
||||
dimension wins when the constraints are incompatible.
|
||||
- `max_size`: Uses the smaller scale. This is equivalent to applying `min_size`
|
||||
first and then shrinking as needed to enforce `max_size`. The longer
|
||||
dimension wins when the constraints are incompatible.
|
||||
|
||||
- `image` (image): The modified image.
|
||||
- `width` (int): The resulting image width in pixels.
|
||||
- `height` (int): The resulting image height in pixels.
|
||||
- `multiplier` (float): The multiplier applied to the original image's dimensions. Useful if you want to resize another image or mask by the same amount.
|
||||
Both dimensions are then rounded to the nearest multiple of `divisible_by`.
|
||||
This final rounding can move a dimension slightly beyond its nominal
|
||||
constraint.
|
||||
|
||||
When `crop_mode` is an anchor such as `center` or `top_left`, the input is
|
||||
resized to cover the calculated dimensions and then cropped from that
|
||||
position. The aspect ratio is preserved.
|
||||
|
||||
When `crop_mode` is `none`, the input is resized directly to the calculated
|
||||
dimensions. Divisibility rounding can therefore produce slightly different
|
||||
horizontal and vertical scales.
|
||||
|
||||
## Inputs
|
||||
|
||||
- `image`: The `IMAGE` or `MASK` to resize.
|
||||
- `max_size`: Target for the longer dimension.
|
||||
- `min_size`: Target for the shorter dimension.
|
||||
- `constraint_priority`: Selects whether `min_size` or `max_size` wins and
|
||||
whether the larger or smaller candidate resize scale is used.
|
||||
- `divisible_by`: Rounds both output dimensions to the nearest multiple of this
|
||||
value.
|
||||
- `interpolation_mode`: `nearest-exact`, `bilinear`, `area`, `bicubic`, or
|
||||
`lanczos`.
|
||||
- `crop_mode`: `none`, center, an edge, or a corner.
|
||||
|
||||
## Outputs
|
||||
|
||||
- `resized`: The resized value, with the same ComfyUI type as the input.
|
||||
- `width`: Final output width.
|
||||
- `height`: Final output height.
|
||||
- `scale_x`: Actual intermediate resize width divided by the original width.
|
||||
- `scale_y`: Actual intermediate resize height divided by the original height.
|
||||
|
||||
For anchored cropping, `scale_x` and `scale_y` describe the resize performed
|
||||
before the crop. A crop also introduces an offset, so apply the same settings
|
||||
to related images and masks when their pixels must remain aligned.
|
||||
|
||||
## Image quality and device behavior
|
||||
|
||||
Image/Mask Autosize uses ComfyUI's shared resize implementation. Tensor-native
|
||||
interpolation modes preserve the input device and dtype. ComfyUI's Lanczos
|
||||
implementation uses Pillow internally but returns the result to the original
|
||||
device and dtype.
|
||||
|
||||
## Relationship to Resize Image/Mask
|
||||
|
||||
ComfyUI's native `Resize Image/Mask` is the better choice for exact dimensions,
|
||||
fixed multipliers, megapixel targets, or matching another input. Image/Mask
|
||||
Autosize is focused on automatic diffusion sizing where longer-edge targeting,
|
||||
shorter-edge constraints, explicit constraint priority, divisible dimensions,
|
||||
anchored cropping, and scale metadata should happen together.
|
||||
|
||||
## Requirements
|
||||
|
||||
A recent ComfyUI build with V3 `MatchType` support is required. The extension
|
||||
has no third-party dependencies beyond ComfyUI's existing requirements.
|
||||
|
||||
## Installation
|
||||
|
||||
Clone the repository into `ComfyUI/custom_nodes` and restart ComfyUI:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/SparknightLLC/ComfyUI-ImageAutosize.git
|
||||
```
|
||||
|
||||
The node appears under `image`.
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for migration notes.
|
||||
|
||||
---
|
||||
|
||||
This node was adapted from the `[img2img_autosize]` shortcode of [Unprompted](https://github.com/ThereforeGames/unprompted), my Automatic1111 extension.
|
||||
This node was adapted from the `[img2img_autosize]` shortcode in
|
||||
[Unprompted](https://github.com/ThereforeGames/unprompted).
|
||||
|
||||
+203
-124
@@ -1,138 +1,217 @@
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import torch
|
||||
import comfy.utils
|
||||
from comfy_api.latest import ComfyExtension, io
|
||||
|
||||
|
||||
class ImageAutosize:
|
||||
SCALE_METHODS = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"]
|
||||
CONSTRAINT_PRIORITIES = ["min_size", "max_size"]
|
||||
CROP_MODES = [
|
||||
"none",
|
||||
"center",
|
||||
"top",
|
||||
"bottom",
|
||||
"left",
|
||||
"right",
|
||||
"top_left",
|
||||
"top_right",
|
||||
"bottom_left",
|
||||
"bottom_right",
|
||||
]
|
||||
|
||||
|
||||
def _calculate_target_dimensions(
|
||||
width: int,
|
||||
height: int,
|
||||
max_size: int,
|
||||
min_size: int,
|
||||
constraint_priority: str,
|
||||
divisible_by: int,
|
||||
) -> tuple[int, int]:
|
||||
max_scale = max_size / max(width, height)
|
||||
min_scale = min_size / min(width, height)
|
||||
if constraint_priority == "min_size":
|
||||
scale = max(max_scale, min_scale)
|
||||
else:
|
||||
scale = min(min_scale, max_scale)
|
||||
|
||||
target_width = max(divisible_by, round(width * scale / divisible_by) * divisible_by)
|
||||
target_height = max(divisible_by, round(height * scale / divisible_by) * divisible_by)
|
||||
return target_width, target_height
|
||||
|
||||
|
||||
def _get_crop_origin(
|
||||
width: int,
|
||||
height: int,
|
||||
target_width: int,
|
||||
target_height: int,
|
||||
crop_mode: str,
|
||||
) -> tuple[int, int]:
|
||||
if crop_mode in ("left", "top_left", "bottom_left"):
|
||||
x = 0
|
||||
elif crop_mode in ("right", "top_right", "bottom_right"):
|
||||
x = width - target_width
|
||||
else:
|
||||
x = (width - target_width) // 2
|
||||
|
||||
if crop_mode in ("top", "top_left", "top_right"):
|
||||
y = 0
|
||||
elif crop_mode in ("bottom", "bottom_left", "bottom_right"):
|
||||
y = height - target_height
|
||||
else:
|
||||
y = (height - target_height) // 2
|
||||
|
||||
return x, y
|
||||
|
||||
|
||||
class ImageAutosize(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
image_type = io.MatchType.Template("image_type", [io.Image, io.Mask])
|
||||
|
||||
return io.Schema(
|
||||
node_id="ImageAutosize",
|
||||
display_name="Image/Mask Autosize",
|
||||
category="image",
|
||||
description="Automatically resizes an image or mask for diffusion workflows.",
|
||||
search_aliases=[
|
||||
"autosize",
|
||||
"image mask autosize",
|
||||
"auto resize",
|
||||
"resize to multiple",
|
||||
"resize image",
|
||||
"resize mask",
|
||||
],
|
||||
inputs=[
|
||||
io.MatchType.Input(
|
||||
"image",
|
||||
template=image_type,
|
||||
tooltip="The image or mask to resize.",
|
||||
),
|
||||
io.Int.Input(
|
||||
"max_size",
|
||||
default=1280,
|
||||
min=1,
|
||||
max=8192,
|
||||
step=1,
|
||||
tooltip="Longer-dimension target used to calculate one candidate resize scale.",
|
||||
),
|
||||
io.Int.Input(
|
||||
"min_size",
|
||||
default=512,
|
||||
min=1,
|
||||
max=4096,
|
||||
step=1,
|
||||
tooltip="Shorter-dimension target used to calculate one candidate resize scale.",
|
||||
),
|
||||
io.Int.Input(
|
||||
"divisible_by",
|
||||
default=32,
|
||||
min=1,
|
||||
max=8192,
|
||||
step=1,
|
||||
tooltip="Rounds both output dimensions to the nearest multiple of this value.",
|
||||
),
|
||||
io.Combo.Input(
|
||||
"interpolation_mode",
|
||||
options=SCALE_METHODS,
|
||||
default="lanczos",
|
||||
tooltip="Interpolation algorithm used for resizing.",
|
||||
),
|
||||
io.Combo.Input(
|
||||
"crop_mode",
|
||||
options=CROP_MODES,
|
||||
default="center",
|
||||
tooltip="Preserves aspect ratio by cropping from this position. None stretches to the output dimensions.",
|
||||
),
|
||||
io.Combo.Input(
|
||||
"constraint_priority",
|
||||
options=CONSTRAINT_PRIORITIES,
|
||||
default="min_size",
|
||||
tooltip="Chooses the candidate scale. min_size uses the larger scale; max_size uses the smaller scale. Divisibility rounding runs afterward.",
|
||||
),
|
||||
],
|
||||
outputs=[
|
||||
io.MatchType.Output(template=image_type, display_name="resized"),
|
||||
io.Int.Output(display_name="width"),
|
||||
io.Int.Output(display_name="height"),
|
||||
io.Float.Output(display_name="scale_x"),
|
||||
io.Float.Output(display_name="scale_y"),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE", ),
|
||||
"max_size": ("INT", {
|
||||
"default": 1280,
|
||||
"min": 1,
|
||||
"max": 8192,
|
||||
"step": 1
|
||||
}),
|
||||
"min_size": ("INT", {
|
||||
"default": 512,
|
||||
"max": 4096,
|
||||
"min": 1,
|
||||
"step": 1
|
||||
}),
|
||||
"divisible_by": ("INT", {
|
||||
"default": 32,
|
||||
"min": 1
|
||||
}),
|
||||
"interpolation_mode": (["nearest", "lanczos", "bilinear", "bicubic", "box", "hamming"], {
|
||||
"default": "lanczos",
|
||||
}),
|
||||
"crop_mode": (["none", "center", "top", "bottom", "left", "right", "top_left", "top_right", "bottom_left", "bottom_right"], {
|
||||
"default": "center",
|
||||
})
|
||||
}
|
||||
}
|
||||
def execute(
|
||||
cls,
|
||||
image: io.Image.Type | io.Mask.Type,
|
||||
max_size: int,
|
||||
min_size: int,
|
||||
constraint_priority: str,
|
||||
divisible_by: int,
|
||||
interpolation_mode: str,
|
||||
crop_mode: str,
|
||||
) -> io.NodeOutput:
|
||||
is_image = len(image.shape) == 4
|
||||
if is_image:
|
||||
_, height, width, _ = image.shape
|
||||
samples = image.movedim(-1, 1)
|
||||
else:
|
||||
_, height, width = image.shape
|
||||
samples = image.unsqueeze(1)
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "INT", "INT", "FLOAT")
|
||||
RETURN_NAMES = ("image", "width", "height", "multiplier")
|
||||
FUNCTION = "op"
|
||||
CATEGORY = "image"
|
||||
DESCRIPTION = """Resizes the input image"""
|
||||
target_width, target_height = _calculate_target_dimensions(
|
||||
width,
|
||||
height,
|
||||
max_size,
|
||||
min_size,
|
||||
constraint_priority,
|
||||
divisible_by,
|
||||
)
|
||||
|
||||
def get_crop_coords(self, img_width, img_height, target_width, target_height, crop_mode):
|
||||
if crop_mode == "center":
|
||||
left = (img_width - target_width) // 2
|
||||
top = (img_height - target_height) // 2
|
||||
elif crop_mode == "top":
|
||||
left = (img_width - target_width) // 2
|
||||
top = 0
|
||||
elif crop_mode == "bottom":
|
||||
left = (img_width - target_width) // 2
|
||||
top = img_height - target_height
|
||||
elif crop_mode == "left":
|
||||
left = 0
|
||||
top = (img_height - target_height) // 2
|
||||
elif crop_mode == "right":
|
||||
left = img_width - target_width
|
||||
top = (img_height - target_height) // 2
|
||||
elif crop_mode == "top_left":
|
||||
left = 0
|
||||
top = 0
|
||||
elif crop_mode == "top_right":
|
||||
left = img_width - target_width
|
||||
top = 0
|
||||
elif crop_mode == "bottom_left":
|
||||
left = 0
|
||||
top = img_height - target_height
|
||||
elif crop_mode == "bottom_right":
|
||||
left = img_width - target_width
|
||||
top = img_height - target_height
|
||||
else: # fallback to center
|
||||
left = (img_width - target_width) // 2
|
||||
top = (img_height - target_height) // 2
|
||||
resize_width = target_width
|
||||
resize_height = target_height
|
||||
if crop_mode != "none":
|
||||
scale = max(target_width / width, target_height / height)
|
||||
resize_width = max(target_width, round(width * scale))
|
||||
resize_height = max(target_height, round(height * scale))
|
||||
|
||||
right = left + target_width
|
||||
bottom = top + target_height
|
||||
return left, top, right, bottom
|
||||
if resize_width != width or resize_height != height:
|
||||
samples = comfy.utils.common_upscale(
|
||||
samples,
|
||||
resize_width,
|
||||
resize_height,
|
||||
interpolation_mode,
|
||||
"disabled",
|
||||
)
|
||||
if len(samples.shape) == 3:
|
||||
samples = samples.unsqueeze(1)
|
||||
|
||||
def op(self, image, max_size, min_size, divisible_by, interpolation_mode, crop_mode):
|
||||
total_images = image.shape[0]
|
||||
out_images = []
|
||||
interpolation_mode = getattr(Image, interpolation_mode.upper(), Image.LANCZOS)
|
||||
if crop_mode != "none":
|
||||
x, y = _get_crop_origin(
|
||||
resize_width,
|
||||
resize_height,
|
||||
target_width,
|
||||
target_height,
|
||||
crop_mode,
|
||||
)
|
||||
samples = samples[:, :, y:y + target_height, x:x + target_width]
|
||||
|
||||
for i in range(total_images):
|
||||
img_array = 255. * image[i].cpu().numpy()
|
||||
width = img_array.shape[1]
|
||||
height = img_array.shape[0]
|
||||
if is_image:
|
||||
output = samples.movedim(1, -1)
|
||||
else:
|
||||
output = samples.squeeze(1)
|
||||
|
||||
larger_dimension = max(width, height)
|
||||
multiplier = max_size / larger_dimension
|
||||
width *= multiplier
|
||||
height *= multiplier
|
||||
|
||||
smaller_dimension = min(width, height)
|
||||
if (smaller_dimension < min_size):
|
||||
multiplier = min_size / smaller_dimension
|
||||
width *= multiplier
|
||||
height *= multiplier
|
||||
|
||||
width = int(round(width / divisible_by) * divisible_by)
|
||||
height = int(round(height / divisible_by) * divisible_by)
|
||||
|
||||
final_multiplier = width / img_array.shape[1]
|
||||
|
||||
img = Image.fromarray(img_array.astype(np.uint8))
|
||||
|
||||
if crop_mode != "none":
|
||||
scale = max(width / img.width, height / img.height)
|
||||
new_size = (int(round(img.width * scale)), int(round(img.height * scale)))
|
||||
img = img.resize(new_size, interpolation_mode)
|
||||
|
||||
left, top, right, bottom = self.get_crop_coords(img.width, img.height, width, height, crop_mode)
|
||||
img = img.crop((left, top, right, bottom))
|
||||
else:
|
||||
img = img.resize((width, height), interpolation_mode)
|
||||
|
||||
img_array = np.clip(np.array(img), 0, 255).astype(np.uint8)
|
||||
out_images.append(img_array)
|
||||
|
||||
restored_img_np = np.array(out_images).astype(np.float32) / 255.0
|
||||
restored_img_tensor = torch.from_numpy(restored_img_np)
|
||||
|
||||
return (
|
||||
restored_img_tensor,
|
||||
int(width),
|
||||
int(height),
|
||||
float(final_multiplier),
|
||||
return io.NodeOutput(
|
||||
output,
|
||||
target_width,
|
||||
target_height,
|
||||
resize_width / width,
|
||||
resize_height / height,
|
||||
)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ImageAutosize": ImageAutosize,
|
||||
}
|
||||
class ImageAutosizeExtension(ComfyExtension):
|
||||
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
||||
return [ImageAutosize]
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImageAutosize": "Image Autosize",
|
||||
}
|
||||
|
||||
async def comfy_entrypoint() -> ImageAutosizeExtension:
|
||||
return ImageAutosizeExtension()
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 418 KiB |
+1
-1
@@ -2,7 +2,7 @@
|
||||
[project]
|
||||
name = "comfyui-imageautosize" # Unique identifier for your node. Immutable after creation.
|
||||
description = "A node for ComfyUI that provides a convenient way of resizing or cropping an image for diffusion tasks."
|
||||
version = "0.0.2" # Custom node version. Must be semantically versioned.
|
||||
version = "0.2.0" # Custom node version. Must be semantically versioned.
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = [] # Filled in from requirements.txt
|
||||
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
import asyncio
|
||||
import importlib.util
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
PACKAGE_ROOT = Path(__file__).parents[1]
|
||||
COMFYUI_ROOT = PACKAGE_ROOT.parents[1]
|
||||
sys.path.insert(0, str(COMFYUI_ROOT))
|
||||
SPEC = importlib.util.spec_from_file_location("image_autosize", PACKAGE_ROOT / "__init__.py")
|
||||
MODULE = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(MODULE)
|
||||
|
||||
|
||||
class ImageAutosizeTests(unittest.TestCase):
|
||||
def test_extension_registers_node(self):
|
||||
extension = asyncio.run(MODULE.comfy_entrypoint())
|
||||
nodes = asyncio.run(extension.get_node_list())
|
||||
|
||||
self.assertEqual(nodes, [MODULE.ImageAutosize])
|
||||
|
||||
def test_schema_accepts_images_and_masks(self):
|
||||
inputs = MODULE.ImageAutosize.INPUT_TYPES()
|
||||
image_options = inputs["required"]["image"][1]
|
||||
priority_options = inputs["required"]["constraint_priority"][1]
|
||||
|
||||
self.assertEqual(image_options["template"]["allowed_types"], "IMAGE,MASK")
|
||||
self.assertEqual(
|
||||
list(inputs["required"]),
|
||||
[
|
||||
"image",
|
||||
"max_size",
|
||||
"min_size",
|
||||
"divisible_by",
|
||||
"interpolation_mode",
|
||||
"crop_mode",
|
||||
"constraint_priority",
|
||||
],
|
||||
)
|
||||
self.assertEqual(priority_options["options"], ["min_size", "max_size"])
|
||||
self.assertEqual(priority_options["default"], "min_size")
|
||||
self.assertEqual(MODULE.ImageAutosize.define_schema().display_name, "Image/Mask Autosize")
|
||||
self.assertEqual(
|
||||
MODULE.ImageAutosize.RETURN_TYPES,
|
||||
["COMFY_MATCHTYPE_V3", "INT", "INT", "FLOAT", "FLOAT"],
|
||||
)
|
||||
|
||||
def test_autosizes_image_and_reports_pre_crop_scales(self):
|
||||
image = torch.zeros((1, 5, 3, 3), dtype=torch.float32)
|
||||
|
||||
output = MODULE.ImageAutosize.execute(
|
||||
image=image,
|
||||
max_size=8,
|
||||
min_size=1,
|
||||
constraint_priority="min_size",
|
||||
divisible_by=4,
|
||||
interpolation_mode="nearest-exact",
|
||||
crop_mode="center",
|
||||
)
|
||||
|
||||
resized, width, height, scale_x, scale_y = output.args
|
||||
self.assertEqual(resized.shape, (1, 8, 4, 3))
|
||||
self.assertEqual((width, height), (4, 8))
|
||||
self.assertAlmostEqual(scale_x, 5 / 3)
|
||||
self.assertAlmostEqual(scale_y, 8 / 5)
|
||||
|
||||
def test_no_crop_reports_independent_stretch_scales(self):
|
||||
image = torch.zeros((1, 5, 3, 3), dtype=torch.float32)
|
||||
|
||||
output = MODULE.ImageAutosize.execute(
|
||||
image=image,
|
||||
max_size=8,
|
||||
min_size=1,
|
||||
constraint_priority="min_size",
|
||||
divisible_by=4,
|
||||
interpolation_mode="nearest-exact",
|
||||
crop_mode="none",
|
||||
)
|
||||
|
||||
resized, width, height, scale_x, scale_y = output.args
|
||||
self.assertEqual(resized.shape, (1, 8, 4, 3))
|
||||
self.assertEqual((width, height), (4, 8))
|
||||
self.assertAlmostEqual(scale_x, 4 / 3)
|
||||
self.assertAlmostEqual(scale_y, 8 / 5)
|
||||
|
||||
def test_resizes_mask_without_adding_image_channels(self):
|
||||
mask = torch.zeros((2, 5, 3), dtype=torch.float32)
|
||||
|
||||
output = MODULE.ImageAutosize.execute(
|
||||
image=mask,
|
||||
max_size=8,
|
||||
min_size=1,
|
||||
constraint_priority="min_size",
|
||||
divisible_by=4,
|
||||
interpolation_mode="bilinear",
|
||||
crop_mode="center",
|
||||
)
|
||||
|
||||
resized = output.args[0]
|
||||
self.assertEqual(resized.shape, (2, 8, 4))
|
||||
self.assertEqual(resized.dtype, mask.dtype)
|
||||
self.assertEqual(resized.device, mask.device)
|
||||
|
||||
def test_crops_lanczos_mask_after_grayscale_resize(self):
|
||||
mask = torch.zeros((1, 5, 3), dtype=torch.float32)
|
||||
|
||||
output = MODULE.ImageAutosize.execute(
|
||||
image=mask,
|
||||
max_size=8,
|
||||
min_size=1,
|
||||
constraint_priority="min_size",
|
||||
divisible_by=4,
|
||||
interpolation_mode="lanczos",
|
||||
crop_mode="center",
|
||||
)
|
||||
|
||||
self.assertEqual(output.args[0].shape, (1, 8, 4))
|
||||
|
||||
def test_avoids_resampling_when_dimensions_are_unchanged(self):
|
||||
image = torch.tensor(
|
||||
[
|
||||
[
|
||||
[[0.123456, 0.234567, 0.345678], [0.456789, 0.567891, 0.678912]],
|
||||
[[0.789123, 0.891234, 0.912345], [0.135791, 0.246802, 0.357913]],
|
||||
],
|
||||
],
|
||||
dtype=torch.float32,
|
||||
)
|
||||
|
||||
output = MODULE.ImageAutosize.execute(
|
||||
image=image,
|
||||
max_size=2,
|
||||
min_size=1,
|
||||
constraint_priority="min_size",
|
||||
divisible_by=1,
|
||||
interpolation_mode="lanczos",
|
||||
crop_mode="center",
|
||||
)
|
||||
|
||||
self.assertTrue(torch.equal(output.args[0], image))
|
||||
self.assertEqual(output.args[3:], (1.0, 1.0))
|
||||
|
||||
def test_minimum_shorter_dimension_overrides_longer_target(self):
|
||||
self.assertEqual(
|
||||
MODULE._calculate_target_dimensions(
|
||||
width=10,
|
||||
height=100,
|
||||
max_size=50,
|
||||
min_size=10,
|
||||
constraint_priority="min_size",
|
||||
divisible_by=1,
|
||||
),
|
||||
(10, 100),
|
||||
)
|
||||
|
||||
def test_target_dimensions_cannot_round_to_zero(self):
|
||||
width, height = MODULE._calculate_target_dimensions(
|
||||
width=1,
|
||||
height=100,
|
||||
max_size=1,
|
||||
min_size=1,
|
||||
constraint_priority="min_size",
|
||||
divisible_by=32,
|
||||
)
|
||||
|
||||
self.assertGreaterEqual(width, 32)
|
||||
self.assertGreaterEqual(height, 32)
|
||||
|
||||
def test_maximum_constraint_priority_prefers_smaller_scale(self):
|
||||
self.assertEqual(
|
||||
MODULE._calculate_target_dimensions(
|
||||
width=10,
|
||||
height=100,
|
||||
max_size=50,
|
||||
min_size=10,
|
||||
constraint_priority="max_size",
|
||||
divisible_by=1,
|
||||
),
|
||||
(5, 50),
|
||||
)
|
||||
|
||||
def test_maximum_constraint_priority_controls_resize(self):
|
||||
image = torch.zeros((1, 100, 10, 3), dtype=torch.float32)
|
||||
|
||||
output = MODULE.ImageAutosize.execute(
|
||||
image=image,
|
||||
max_size=50,
|
||||
min_size=10,
|
||||
constraint_priority="max_size",
|
||||
divisible_by=1,
|
||||
interpolation_mode="nearest-exact",
|
||||
crop_mode="none",
|
||||
)
|
||||
|
||||
resized, width, height, scale_x, scale_y = output.args
|
||||
self.assertEqual(resized.shape, (1, 50, 5, 3))
|
||||
self.assertEqual((width, height), (5, 50))
|
||||
self.assertEqual((scale_x, scale_y), (0.5, 0.5))
|
||||
|
||||
def test_crop_origins_follow_selected_anchor(self):
|
||||
self.assertEqual(MODULE._get_crop_origin(7, 9, 4, 6, "top_left"), (0, 0))
|
||||
self.assertEqual(MODULE._get_crop_origin(7, 9, 4, 6, "bottom_right"), (3, 3))
|
||||
self.assertEqual(MODULE._get_crop_origin(7, 9, 4, 6, "top"), (1, 0))
|
||||
self.assertEqual(MODULE._get_crop_origin(7, 9, 4, 6, "right"), (3, 1))
|
||||
self.assertEqual(MODULE._get_crop_origin(7, 9, 4, 6, "center"), (1, 1))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user