fix: normalize NumPy image outputs

Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
Hongsheng Liu committed 2026-08-11 12:14:23 +08:00
1 parent 53afbf3a0c
commit 5e3aa88e10
3 files changed
+77 -12

No files matched your search

@@ -604,18 +604,7 @@ def main():
raise ValueError("No output generated from omni.generate()")
logger.info(f"Outputs: {outputs}")
images = None
for output in outputs:
images = getattr(output, "images", None)
if images:
break
req_out = getattr(output, "request_output", None)
images = getattr(req_out, "images", None) if req_out is not None else None
if images:
break
if not images:
images = extract_images_from_outputs(outputs)
images = extract_images_from_outputs(outputs)
if not images:
raise ValueError("No images found in request_output")
+37
View File
@@ -0,0 +1,37 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import numpy as np
import pytest
from PIL import Image
from vllm_omni.diffusion.utils.image_output import extract_images_from_outputs
from vllm_omni.outputs import OmniRequestOutput
pytestmark = [pytest.mark.diffusion, pytest.mark.core_model, pytest.mark.cpu]
@pytest.mark.parametrize(
("payload", "expected_pixel"),
[
(np.array([[[-1.0, 0.0, 1.0]]], dtype=np.float32), (0, 127, 255)),
(np.array([[[0, 128, 255]]], dtype=np.uint8), (0, 128, 255)),
],
)
def test_extract_images_normalizes_numpy_payload(
payload: np.ndarray,
expected_pixel: tuple[int, int, int],
tmp_path,
) -> None:
output = OmniRequestOutput(images=[payload])
[image] = extract_images_from_outputs(output)
assert isinstance(image, Image.Image)
assert image.mode == "RGB"
assert image.size == (1, 1)
assert image.getpixel((0, 0)) == expected_pixel
output_path = tmp_path / "image.png"
image.save(output_path)
assert output_path.is_file()
+39
View File
@@ -4,6 +4,7 @@
from collections.abc import Iterable, Mapping, Sequence
from typing import Any
import numpy as np
import torch
from PIL import Image
from vllm.outputs import RequestOutput
@@ -20,6 +21,8 @@ def extract_images_from_outputs(outputs: ImageOutputs) -> list[Image.Image]:
Supported inputs:
- OmniRequestOutput / list[OmniRequestOutput]
- RequestOutput / list[RequestOutput] (compatibility fallback)
PIL images, NumPy arrays, and PyTorch tensors are normalized to PIL images.
"""
for output in _iter_known_outputs(outputs):
if isinstance(output, OmniRequestOutput):
@@ -58,6 +61,9 @@ def _iter_multimodal_image_payloads(output: OmniRequestOutput | RequestOutput) -
def _iter_request_output_payloads(request_output: RequestOutput) -> Iterable[Any]:
images = getattr(request_output, "images", None)
if images is not None:
yield images
mm = getattr(request_output, "multimodal_output", None)
if mm is not None:
yield from _image_values_from_mapping_like(mm)
@@ -82,6 +88,8 @@ def _coerce_images(payload: Any) -> list[Image.Image]:
return []
if isinstance(payload, Image.Image):
return [payload]
if isinstance(payload, np.ndarray):
return _ndarray_to_images(payload)
if isinstance(payload, torch.Tensor):
return _tensor_to_images(payload)
if isinstance(payload, list | tuple):
@@ -92,6 +100,37 @@ def _coerce_images(payload: Any) -> list[Image.Image]:
return []
def _ndarray_to_images(array: np.ndarray) -> list[Image.Image]:
if array.ndim == 4:
images: list[Image.Image] = []
for single in array:
images.extend(_ndarray_to_images(single))
return images
if array.ndim != 3 or array.size == 0:
return []
image = array
if image.shape[-1] not in (1, 3, 4):
if image.shape[0] not in (1, 3, 4):
return []
image = np.moveaxis(image, 0, -1)
if np.issubdtype(image.dtype, np.floating):
image = image.astype(np.float32, copy=False)
if image.min() < 0.0:
image = image / 2 + 0.5
image = np.clip(image, 0.0, 1.0) * 255
elif np.issubdtype(image.dtype, np.integer) or np.issubdtype(image.dtype, np.bool_):
image = np.clip(image, 0, 255)
else:
return []
image = image.astype(np.uint8)
if image.shape[-1] == 1:
image = image[..., 0]
return [Image.fromarray(np.ascontiguousarray(image))]
def _tensor_to_images(tensor: torch.Tensor) -> list[Image.Image]:
img = tensor.detach().to("cpu", dtype=torch.float32)
if img.ndim == 4: