From 5c4d9b04bd40f41ebab02cdeaa3cd02386cbd721 Mon Sep 17 00:00:00 2001 From: Ramnath0521 <112818015+Ramnath0521@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:37:15 +0530 Subject: [PATCH] fix: pass (width, height) to Pillow in the Resize transform (#697) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: pass (width, height) to Pillow in the Resize transform `resize()` handed a tuple size straight to `PIL.Image.resize()`. fastembed keeps sizes as (height, width) — `Transform.from_config` builds the tuple as `(size["height"], size["width"])` — while Pillow takes (width, height), so a non-square image processor configuration produced a transposed image: Resize(size=(100, 200))(Image.new("RGB", (300, 300)))[0].size # (100, 200), expected (200, 100) Square sizes are unaffected, which is why this went unnoticed. The int branch of `resize()` already emits Pillow order and is untouched, as are `resize_ndarray()`'s callers, which pass (width, height) explicitly. `Resize.__call__` is the only caller of this function and always supplies fastembed's height-first order, so converting here is safe. Co-Authored-By: Claude Opus 5 * tests: simplify tests --------- Co-authored-by: George Panchuk --- fastembed/image/transform/functional.py | 7 ++++++- tests/test_image_transform.py | 28 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/test_image_transform.py diff --git a/fastembed/image/transform/functional.py b/fastembed/image/transform/functional.py index 9d9e219..af28fb0 100644 --- a/fastembed/image/transform/functional.py +++ b/fastembed/image/transform/functional.py @@ -98,7 +98,12 @@ def resize( resample: int | Image.Resampling = Image.Resampling.BILINEAR, ) -> Image.Image: if isinstance(size, tuple): - return image.resize(size, resample) + # fastembed keeps sizes as (height, width) — `Compose.from_config` builds the + # tuple as (size["height"], size["width"]) — while Pillow's resize takes + # (width, height). The two agree for square sizes, so this only shows up on a + # non-square image processor configuration. + height, width = size + return image.resize((width, height), resample) height, width = image.height, image.width short, long = (width, height) if width <= height else (height, width) diff --git a/tests/test_image_transform.py b/tests/test_image_transform.py new file mode 100644 index 0000000..6a20323 --- /dev/null +++ b/tests/test_image_transform.py @@ -0,0 +1,28 @@ +import pytest +from PIL import Image + +from fastembed.image.transform.functional import resize + + +@pytest.mark.parametrize( + ("size", "expected"), + [ + ((100, 200), (200, 100)), # the bug: a non-square size came back transposed + ((224, 224), (224, 224)), # the square path every shipped model takes + ], +) +def test_resize_tuple_is_height_width(size: tuple[int, int], expected: tuple[int, int]) -> None: + """A ``(height, width)`` size must reach Pillow as ``(width, height)``.""" + resized = resize(Image.new("RGB", (300, 300)), size=size) + + assert resized.size == expected # PIL reports (width, height) + + +def test_resize_int_keeps_shortest_edge_behaviour() -> None: + """The int branch already emitted Pillow order; it must not be disturbed.""" + landscape = Image.new("RGB", (400, 200)) + portrait = Image.new("RGB", (200, 400)) + + # size sets the shortest edge, and the aspect ratio is preserved. + assert resize(landscape, size=100).size == (200, 100) + assert resize(portrait, size=100).size == (100, 200)