fix: normalize NumPy image outputs
Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
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")
|
||||
|
||||
@@ -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()
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user