From b002a50544bd7a40a87eba95e77f2ab841308aaa Mon Sep 17 00:00:00 2001 From: JesusMan0529 <1638272030@qq.com> Date: Sat, 3 Oct 2026 17:52:52 +0800 Subject: [PATCH 1/3] fix(image): keep longest-edge resize dimensions nonzero --- fastembed/image/transform/functional.py | 3 +- tests/test_image_transform.py | 50 ++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/fastembed/image/transform/functional.py b/fastembed/image/transform/functional.py index e3b977db..bfe353bd 100644 --- a/fastembed/image/transform/functional.py +++ b/fastembed/image/transform/functional.py @@ -186,7 +186,8 @@ def resize_longest_edge( if new_width % 2 != 0: new_width += 1 - return image.resize((new_width, new_height), resample) + # A very narrow image can round down to zero along its shorter edge. + return image.resize((max(1, new_width), max(1, new_height)), resample) def crop_ndarray( diff --git a/tests/test_image_transform.py b/tests/test_image_transform.py index ceed9e35..4f48c9e4 100644 --- a/tests/test_image_transform.py +++ b/tests/test_image_transform.py @@ -2,7 +2,7 @@ import pytest from PIL import Image -from fastembed.image.transform.functional import normalize, resize +from fastembed.image.transform.functional import normalize, resize, resize_longest_edge from fastembed.image.transform.operators import Compose @@ -50,6 +50,54 @@ def test_resize_int_keeps_shortest_edge_behaviour() -> None: assert resize(portrait, size=100).size == (100, 200) +@pytest.mark.parametrize( + ("size", "expected"), + [ + ((4096, 1), (2048, 1)), + ((1, 4096), (1, 2048)), + ((4096, 2), (2048, 2)), + ((2, 4096), (2, 2048)), + ((400, 200), (2048, 1024)), + ((200, 400), (1024, 2048)), + ], +) +def test_resize_longest_edge_keeps_nonzero_dimensions( + size: tuple[int, int], expected: tuple[int, int] +) -> None: + image = Image.new("RGB", size) + + resized = resize_longest_edge(image, max_size=2048) + + assert resized.size == expected + + +@pytest.mark.parametrize("size", [(4096, 1), (1, 4096)]) +def test_idefics3_preprocessor_accepts_thin_images(size: tuple[int, int]) -> None: + # ColModernVBERT's config resizes before splitting into 512-pixel patches. + processor = Compose.from_config( + { + "image_processor_type": "Idefics3ImageProcessor", + "do_resize": True, + "size": {"longest_edge": 2048}, + "resample": Image.Resampling.LANCZOS, + "do_image_splitting": True, + "max_image_size": {"longest_edge": 512}, + "do_rescale": True, + "rescale_factor": 1 / 255, + "do_normalize": True, + "image_mean": [0.5, 0.5, 0.5], + "image_std": [0.5, 0.5, 0.5], + } + ) + + patches = processor([Image.new("RGB", size, color=(128, 128, 128))])[0] + + assert len(patches) == 5 + for patch in patches: + assert patch.shape == (3, 512, 512) + np.testing.assert_allclose(patch, (128 / 255 - 0.5) / 0.5, atol=1e-6) + + @pytest.mark.parametrize( ("mean", "std"), [ From 06e4cce2bf35cd41142320259487bb403030e776 Mon Sep 17 00:00:00 2001 From: JesusMan0529 <1638272030@qq.com> Date: Sat, 3 Oct 2026 18:35:49 +0800 Subject: [PATCH 2/3] docs(image): document resize behavior and regression tests --- fastembed/image/transform/functional.py | 1 + tests/test_image_transform.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/fastembed/image/transform/functional.py b/fastembed/image/transform/functional.py index bfe353bd..bfe82f23 100644 --- a/fastembed/image/transform/functional.py +++ b/fastembed/image/transform/functional.py @@ -168,6 +168,7 @@ def resize_longest_edge( max_size: int, resample: int | Image.Resampling = Image.Resampling.LANCZOS, ) -> Image.Image: + """Resize by the longest edge, preserving even rounding and nonzero dimensions.""" height, width = image.height, image.width aspect_ratio = width / height diff --git a/tests/test_image_transform.py b/tests/test_image_transform.py index 4f48c9e4..9c3a16f8 100644 --- a/tests/test_image_transform.py +++ b/tests/test_image_transform.py @@ -64,6 +64,7 @@ def test_resize_int_keeps_shortest_edge_behaviour() -> None: def test_resize_longest_edge_keeps_nonzero_dimensions( size: tuple[int, int], expected: tuple[int, int] ) -> None: + """Longest-edge resizing must clamp zeros and preserve existing even rounding.""" image = Image.new("RGB", size) resized = resize_longest_edge(image, max_size=2048) @@ -73,6 +74,7 @@ def test_resize_longest_edge_keeps_nonzero_dimensions( @pytest.mark.parametrize("size", [(4096, 1), (1, 4096)]) def test_idefics3_preprocessor_accepts_thin_images(size: tuple[int, int]) -> None: + """Thin images must pass Idefics3 resizing, splitting, rescaling and normalization.""" # ColModernVBERT's config resizes before splitting into 512-pixel patches. processor = Compose.from_config( { From 98bada265ff2b7992616165f23b50bae2b7ab3e7 Mon Sep 17 00:00:00 2001 From: George Panchuk Date: Wed, 7 Oct 2026 01:12:14 +0700 Subject: [PATCH 3/3] tests: drop the extra tests and docstring from the nonzero resize fix --- fastembed/image/transform/functional.py | 1 - tests/test_image_transform.py | 52 +------------------------ 2 files changed, 1 insertion(+), 52 deletions(-) diff --git a/fastembed/image/transform/functional.py b/fastembed/image/transform/functional.py index bfe82f23..bfe353bd 100644 --- a/fastembed/image/transform/functional.py +++ b/fastembed/image/transform/functional.py @@ -168,7 +168,6 @@ def resize_longest_edge( max_size: int, resample: int | Image.Resampling = Image.Resampling.LANCZOS, ) -> Image.Image: - """Resize by the longest edge, preserving even rounding and nonzero dimensions.""" height, width = image.height, image.width aspect_ratio = width / height diff --git a/tests/test_image_transform.py b/tests/test_image_transform.py index 9c3a16f8..ceed9e35 100644 --- a/tests/test_image_transform.py +++ b/tests/test_image_transform.py @@ -2,7 +2,7 @@ import pytest from PIL import Image -from fastembed.image.transform.functional import normalize, resize, resize_longest_edge +from fastembed.image.transform.functional import normalize, resize from fastembed.image.transform.operators import Compose @@ -50,56 +50,6 @@ def test_resize_int_keeps_shortest_edge_behaviour() -> None: assert resize(portrait, size=100).size == (100, 200) -@pytest.mark.parametrize( - ("size", "expected"), - [ - ((4096, 1), (2048, 1)), - ((1, 4096), (1, 2048)), - ((4096, 2), (2048, 2)), - ((2, 4096), (2, 2048)), - ((400, 200), (2048, 1024)), - ((200, 400), (1024, 2048)), - ], -) -def test_resize_longest_edge_keeps_nonzero_dimensions( - size: tuple[int, int], expected: tuple[int, int] -) -> None: - """Longest-edge resizing must clamp zeros and preserve existing even rounding.""" - image = Image.new("RGB", size) - - resized = resize_longest_edge(image, max_size=2048) - - assert resized.size == expected - - -@pytest.mark.parametrize("size", [(4096, 1), (1, 4096)]) -def test_idefics3_preprocessor_accepts_thin_images(size: tuple[int, int]) -> None: - """Thin images must pass Idefics3 resizing, splitting, rescaling and normalization.""" - # ColModernVBERT's config resizes before splitting into 512-pixel patches. - processor = Compose.from_config( - { - "image_processor_type": "Idefics3ImageProcessor", - "do_resize": True, - "size": {"longest_edge": 2048}, - "resample": Image.Resampling.LANCZOS, - "do_image_splitting": True, - "max_image_size": {"longest_edge": 512}, - "do_rescale": True, - "rescale_factor": 1 / 255, - "do_normalize": True, - "image_mean": [0.5, 0.5, 0.5], - "image_std": [0.5, 0.5, 0.5], - } - ) - - patches = processor([Image.new("RGB", size, color=(128, 128, 128))])[0] - - assert len(patches) == 5 - for patch in patches: - assert patch.shape == (3, 512, 512) - np.testing.assert_allclose(patch, (128 / 255 - 0.5) / 0.5, atol=1e-6) - - @pytest.mark.parametrize( ("mean", "std"), [