sgl-project/sglang · error · TypeError

Expected a PIL image, got {type(image)}

Error message

Expected a PIL image, got {type(image)}

What it means

The Dots3 image processor only accepts PIL Image objects; anything else (numpy array, tensor, file path, bytes, base64 string) fails this isinstance check immediately. This mirrors Qwen-VL-style processors that operate on PIL's mode/size API (e.g. RGBA compositing, convert('RGB')).

Source

Thrown at python/sglang/srt/models/dots3_common/dots_omni_towers.py:151

        resized_h = max(factor, self._round_by_factor(height, factor))
        resized_w = max(factor, self._round_by_factor(width, factor))
        if resized_h * resized_w > max_pixels:
            beta = math.sqrt(height * width / max_pixels)
            resized_h = max(factor, self._floor_by_factor(height / beta, factor))
            resized_w = max(factor, self._floor_by_factor(width / beta, factor))
        elif resized_h * resized_w < min_pixels:
            beta = math.sqrt(min_pixels / (height * width))
            resized_h = self._ceil_by_factor(height * beta, factor)
            resized_w = self._ceil_by_factor(width * beta, factor)
            if resized_h * resized_w > max_pixels:
                beta = math.sqrt(resized_h * resized_w / max_pixels)
                resized_h = max(factor, self._floor_by_factor(resized_h / beta, factor))
                resized_w = max(factor, self._floor_by_factor(resized_w / beta, factor))
        return resized_h, resized_w

    def _process_image(self, image, detail="auto"):
        if not isinstance(image, Image.Image):
            raise TypeError(f"Expected a PIL image, got {type(image)}")
        if image.mode == "RGBA":
            background = Image.new("RGB", image.size, (255, 255, 255))
            background.paste(image, mask=image.getchannel("A"))
            image = background
        elif image.mode != "RGB":
            image = image.convert("RGB")

        detail_config = self.image_detail_config.get(detail, {})
        resized_h, resized_w = self._resized_size(
            *image.size,
            min_pixels=detail_config.get("min_pixels", self.min_pixels),
            max_pixels=detail_config.get("max_pixels", self.max_pixels),
            target_height=detail_config.get("target_height"),
            target_width=detail_config.get("target_width"),
        )
        image = image.resize((resized_w, resized_h), Image.Resampling.BICUBIC)
        array = np.asarray(image, dtype=np.float32) / 255.0
        array = (array - self.image_mean) / self.image_std

View on GitHub (pinned to 0132848349)

Solutions

  1. Convert the input to PIL before calling: Image.fromarray(arr) (and cv2.cvtColor BGR->RGB if from OpenCV)
  2. For file paths, open with Image.open(path) and pass the result
  3. For raw bytes, wrap in io.BytesIO and Image.open
  4. Standardize the multimodal request pipeline to always deliver PIL RGB images

Example fix

# before
arr = cv2.imread('x.jpg')
processor.process_images([arr])

# after
from PIL import Image
img = Image.fromarray(cv2.cvtColor(arr, cv2.COLOR_BGR2RGB))
processor.process_images([img])
Defensive patterns

Strategy: type-guard

Validate before calling

from PIL import Image

def to_pil(x):
    if isinstance(x, Image.Image):
        return x
    import numpy as np, io
    if isinstance(x, bytes):
        return Image.open(io.BytesIO(x))
    if isinstance(x, str):
        return Image.open(x)
    if isinstance(x, np.ndarray):
        return Image.fromarray(x)
    raise TypeError(f'cannot convert {type(x)} to PIL')

Type guard

def is_pil_image(x) -> bool:
    from PIL import Image
    return isinstance(x, Image.Image)

Try / catch

try:
    processor.process_images([img])
except TypeError as e:
    if 'PIL image' in str(e):
        img = to_pil(raw)
    else:
        raise

Prevention

When it happens

Trigger: Calling process_images with a numpy ndarray, torch.Tensor, raw bytes, or a file path instead of a PIL.Image.Image instance. Internally the code needs image.mode and Image.new/paste, so non-PIL input is rejected up front.

Common situations: Loading images with cv2.imread (returns BGR ndarray), passing decoded byte buffers from an HTTP handler, or handing over a path string assuming the library will open it.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/6f4f03f480197de9. Report an issue: GitHub.