Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions src/eva/vision/data/transforms/spatial/resize.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class Resize(base.TorchvisionTransformV2):
"""Resize transform for images.

This transform provides different modes of resizing:
1. Spatial resizing: Resize to a specific size dimension or
maximum size for the longer edge.
1. Spatial resizing: Resize to a specific `size` and/or cap the longer
edge at `max_size`. `max_size` acts exclusively as an upper bound.
2. Byte-based resizing: Resize to fit within a maximum byte size.

If both spatial and byte-based constraints are provided, first the spatial
Expand All @@ -39,11 +39,13 @@ def __init__(
size: Desired output size, e.g. (height, width) tuple.
max_bytes: Maximum allowed byte size for the image. If both `size` and
`max_bytes` are provided, spatial resizing is applied first.
max_size: The maximum allowed for the longer edge of the resized image.
max_size: The maximum allowed for the longer edge of the resized
image. When `size` is None, images whose longer edge is
already `<= max_size` are returned unchanged without upscaling.

Raises:
ValueError: If both size and max_bytes are provided, or if max_bytes
is not a positive integer.
ValueError: If `max_bytes` is not a positive integer, or if
`max_size` is provided without `size` on torchvision<0.19.0.
"""
if max_bytes is not None and max_bytes <= 0:
raise ValueError("'max_bytes' must be a positive integer.")
Expand Down Expand Up @@ -79,5 +81,16 @@ def _(self, inpt: Any, params: Dict[str, Any]) -> Any:
if not self.resize_fns:
return inpt
for resize_fn in self.resize_fns:
if self._skip_resize(resize_fn, inpt):
continue
inpt = resize_fn(inpt)
return tv_tensors.wrap(inpt, like=inpt)

def _skip_resize(self, resize_fn: Any, inpt: Any) -> bool:
"""Check if v2.Resize on `inpt` would upscale an image to `max_size` when `size` is None."""
if self.size is not None or self.max_size is None:
return False
if not isinstance(resize_fn, v2.Resize):
return False
longest_edge = max(int(inpt.shape[-2]), int(inpt.shape[-1]))
return longest_edge <= self.max_size
38 changes: 38 additions & 0 deletions tests/eva/vision/data/transforms/spatial/test_resize.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import torch
from torchvision import tv_tensors

from eva.core.utils import requirements
from eva.vision.data import transforms
from eva.vision.utils.image import encode as encode_utils

Expand Down Expand Up @@ -91,6 +92,43 @@ def test_resize_with_max_bytes_only(max_bytes, input_shape):
assert result.shape[1] < input_shape[1] or result.shape[2] < input_shape[2]


@pytest.mark.skipif(
requirements.below("torchvision", "0.19.0"),
reason="`max_size` without `size` requires torchvision>=0.19.0.",
)
@pytest.mark.parametrize(
"max_size, input_shape, expected_shape",
[
# Longer edge already <= max_size: returned unchanged
(256, (3, 100, 100), (3, 100, 100)),
(256, (3, 256, 128), (3, 256, 128)),
(256, (3, 200, 256), (3, 200, 256)),
(512, (3, 300, 400), (3, 300, 400)),
# Longer edge > max_size: downscaled
(256, (3, 512, 512), (3, 256, 256)),
(256, (3, 1024, 512), (3, 256, 128)),
(256, (3, 512, 1024), (3, 128, 256)),
(100, (3, 400, 200), (3, 100, 50)),
],
)
def test_resize_with_max_size_only(max_size, input_shape, expected_shape):
"""Test Resize with only max_size parameter provided.

When `size` is None, images whose longer edge is already <= max_size are
returned unchanged, and larger images are downscaled so the longer edge
equals max_size while preserving aspect ratio.
"""
resize_transform = transforms.Resize(max_size=max_size)
test_image = tv_tensors.Image(torch.rand(*input_shape))

result = resize_transform(test_image)

assert isinstance(result, tv_tensors.Image)
assert result.shape == expected_shape
if input_shape == expected_shape:
assert torch.equal(result, test_image)


@pytest.mark.parametrize(
"input_shape",
[
Expand Down
Loading