{"record":{"id":"6f4f03f480197de9","repo":"sgl-project/sglang","slug":"expected-a-pil-image-got-type-image","errorCode":null,"errorMessage":"Expected a PIL image, got {type(image)}","messagePattern":"Expected a PIL image, got (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/models/dots3_common/dots_omni_towers.py","lineNumber":151,"sourceCode":"        resized_h = max(factor, self._round_by_factor(height, factor))\n        resized_w = max(factor, self._round_by_factor(width, factor))\n        if resized_h * resized_w > max_pixels:\n            beta = math.sqrt(height * width / max_pixels)\n            resized_h = max(factor, self._floor_by_factor(height / beta, factor))\n            resized_w = max(factor, self._floor_by_factor(width / beta, factor))\n        elif resized_h * resized_w < min_pixels:\n            beta = math.sqrt(min_pixels / (height * width))\n            resized_h = self._ceil_by_factor(height * beta, factor)\n            resized_w = self._ceil_by_factor(width * beta, factor)\n            if resized_h * resized_w > max_pixels:\n                beta = math.sqrt(resized_h * resized_w / max_pixels)\n                resized_h = max(factor, self._floor_by_factor(resized_h / beta, factor))\n                resized_w = max(factor, self._floor_by_factor(resized_w / beta, factor))\n        return resized_h, resized_w\n\n    def _process_image(self, image, detail=\"auto\"):\n        if not isinstance(image, Image.Image):\n            raise TypeError(f\"Expected a PIL image, got {type(image)}\")\n        if image.mode == \"RGBA\":\n            background = Image.new(\"RGB\", image.size, (255, 255, 255))\n            background.paste(image, mask=image.getchannel(\"A\"))\n            image = background\n        elif image.mode != \"RGB\":\n            image = image.convert(\"RGB\")\n\n        detail_config = self.image_detail_config.get(detail, {})\n        resized_h, resized_w = self._resized_size(\n            *image.size,\n            min_pixels=detail_config.get(\"min_pixels\", self.min_pixels),\n            max_pixels=detail_config.get(\"max_pixels\", self.max_pixels),\n            target_height=detail_config.get(\"target_height\"),\n            target_width=detail_config.get(\"target_width\"),\n        )\n        image = image.resize((resized_w, resized_h), Image.Resampling.BICUBIC)\n        array = np.asarray(image, dtype=np.float32) / 255.0\n        array = (array - self.image_mean) / self.image_std","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/models/dots3_common/dots_omni_towers.py#L133-L169","documentation":"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')).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Convert the input to PIL before calling: Image.fromarray(arr) (and cv2.cvtColor BGR->RGB if from OpenCV)","For file paths, open with Image.open(path) and pass the result","For raw bytes, wrap in io.BytesIO and Image.open","Standardize the multimodal request pipeline to always deliver PIL RGB images"],"exampleFix":"# before\narr = cv2.imread('x.jpg')\nprocessor.process_images([arr])\n\n# after\nfrom PIL import Image\nimg = Image.fromarray(cv2.cvtColor(arr, cv2.COLOR_BGR2RGB))\nprocessor.process_images([img])","handlingStrategy":"type-guard","validationCode":"from PIL import Image\n\ndef to_pil(x):\n    if isinstance(x, Image.Image):\n        return x\n    import numpy as np, io\n    if isinstance(x, bytes):\n        return Image.open(io.BytesIO(x))\n    if isinstance(x, str):\n        return Image.open(x)\n    if isinstance(x, np.ndarray):\n        return Image.fromarray(x)\n    raise TypeError(f'cannot convert {type(x)} to PIL')","typeGuard":"def is_pil_image(x) -> bool:\n    from PIL import Image\n    return isinstance(x, Image.Image)","tryCatchPattern":"try:\n    processor.process_images([img])\nexcept TypeError as e:\n    if 'PIL image' in str(e):\n        img = to_pil(raw)\n    else:\n        raise","preventionTips":["Convert cv2 arrays with BGR->RGB + Image.fromarray","Wrap request handlers to normalize inputs to PIL","Open paths with Image.open before passing"],"tags":["dots3","type-error","pil","multimodal","input-validation"],"backgroundTag":"wrong-input-type","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}