From 47930b59ca363349d81f112910f6ff8e795f089c Mon Sep 17 00:00:00 2001 From: Akshat katiyar Date: Wed, 10 Jun 2026 11:05:50 +0530 Subject: [PATCH] [Bugfix] Handle HWC images in ImageProcessorItems.get_image_size (#45057) Signed-off-by: YellowFoxH4XOR Co-authored-by: Claude --- tests/multimodal/test_parse.py | 51 ++++++++++++++++++++++++++++++++++ vllm/multimodal/parse.py | 8 +++++- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 tests/multimodal/test_parse.py diff --git a/tests/multimodal/test_parse.py b/tests/multimodal/test_parse.py new file mode 100644 index 00000000000..6504cc6bcf3 --- /dev/null +++ b/tests/multimodal/test_parse.py @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +import numpy as np +import pytest +import torch +from PIL import Image + +from vllm.multimodal.parse import ImageProcessorItems, VideoProcessorItems + +H, W = 480, 640 + + +@pytest.mark.parametrize( + "image", + [ + Image.new("RGB", (W, H)), + # HWC, e.g. from np.array(PIL.Image) + np.zeros((H, W, 3), dtype=np.uint8), + torch.zeros((H, W, 3), dtype=torch.uint8), + # CHW, standard PyTorch / numpy convention + np.zeros((3, H, W), dtype=np.uint8), + torch.zeros((3, H, W), dtype=torch.uint8), + ], +) +def test_image_size_hwc_chw(image): + """Image sizes must be channel-layout agnostic. + + `get_image_size` determines the multimodal placeholder count; reading an + HWC array (the layout `np.array(PIL.Image)` produces) as CHW yields a + bogus size and a placeholder/embedding count mismatch at inference time. + """ + items = ImageProcessorItems([image]) + + assert items.get_image_size(0) == (W, H) + + +@pytest.mark.parametrize( + "frame", + [ + Image.new("RGB", (W, H)), + np.zeros((H, W, 3), dtype=np.uint8), + torch.zeros((H, W, 3), dtype=torch.uint8), + np.zeros((3, H, W), dtype=np.uint8), + torch.zeros((3, H, W), dtype=torch.uint8), + ], +) +def test_frame_size_hwc_chw(frame): + """`get_frame_size` must stay consistent with `get_image_size`.""" + items = VideoProcessorItems([[frame]]) + + assert items.get_frame_size(0) == (W, H) diff --git a/vllm/multimodal/parse.py b/vllm/multimodal/parse.py index cdedd194227..f4c72e060bf 100644 --- a/vllm/multimodal/parse.py +++ b/vllm/multimodal/parse.py @@ -334,7 +334,13 @@ class ImageProcessorItems(ProcessorBatchItems[HfImageItem | None]): if isinstance(image, PILImage.Image): return ImageSize(*image.size) if isinstance(image, (np.ndarray, torch.Tensor)): - _, h, w = image.shape + if image.ndim == 3 and image.shape[-1] in (1, 3, 4): + # HWC format (e.g. from np.array(PIL.Image)). + # PIL images are always channels-last. + h, w = image.shape[0], image.shape[1] + else: + # CHW format (standard PyTorch / numpy convention). + _, h, w = image.shape return ImageSize(w, h) assert_never(image)