{"record":{"id":"c7cbd5effa3d2261","repo":"roboflow/supervision","slug":"image-must-be-a-numpy-ndarray-or-pil-image-image-c7cbd5","errorCode":null,"errorMessage":"`image` must be a numpy.ndarray or PIL.Image.Image. Received type: {type(image)}","messagePattern":"`image` must be a numpy\\.ndarray or PIL\\.Image\\.Image\\. Received type: (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/supervision/utils/image.py","lineNumber":650,"sourceCode":"        >>> sv.get_image_resolution_wh(image)\n        (1920, 1080)\n\n        ```\n    \"\"\"\n    if isinstance(image, np.ndarray):\n        if image.ndim < 2:\n            raise ValueError(\n                \"NumPy image must have at least 2 dimensions (H, W, ...). \"\n                f\"Received shape: {image.shape}\"\n            )\n        height, width = image.shape[:2]\n        return int(width), int(height)\n\n    if isinstance(image, Image.Image):\n        width, height = image.size\n        return int(width), int(height)\n\n    raise TypeError(\n        \"`image` must be a numpy.ndarray or PIL.Image.Image. \"\n        f\"Received type: {type(image)}\"\n    )\n\n\nclass ImageSink:\n    \"\"\"\n    Save sequential images into a directory through a context manager.\n\n    `ImageSink` creates the target directory on entry and writes each image\n    using `save_image`, incrementing the image name pattern after every save.\n    \"\"\"\n\n    def __init__(\n        self,\n        target_dir_path: str,\n        overwrite: bool = False,\n        image_name_pattern: str = \"image_{:05d}.png\",","sourceCodeStart":632,"sourceCodeEnd":668,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/utils/image.py#L632-L668","documentation":"Raised by `sv.get_image_resolution_wh` (and helpers with the same guard) when `image` is neither `numpy.ndarray` nor `PIL.Image.Image`. The function reads `(width, height)` from either type; any other object cannot be measured, so a TypeError is raised naming the actual type received.","triggerScenarios":"Calling `sv.get_image_resolution_wh(path_string)`, `sv.get_image_resolution_wh(torch_tensor)`, or passing a dataclass/dict that wraps pixel data instead of the array itself.","commonSituations":"Passing a path where an already-loaded image is expected; handing a framework tensor or a custom `Frame` wrapper object to a supervision utility in a video pipeline; mixing up argument order so another value lands in `image`.","solutions":["Load the image first (`cv2.imread` / `PIL.Image.open`) and pass the array object.","Convert tensors: `tensor.detach().cpu().numpy()`.","Unwrap custom frame containers: `frame.array` or the equivalent attribute holding the ndarray."],"exampleFix":"# before\nw, h = sv.get_image_resolution_wh(frame_metadata)  # custom wrapper object\n# after\nw, h = sv.get_image_resolution_wh(frame_metadata.image)  # the underlying np.ndarray","handlingStrategy":"type-guard","validationCode":"assert isinstance(image, (np.ndarray, Image.Image)), f'unsupported image type {type(image)}'","typeGuard":"from PIL import Image\nimport numpy as np\n\ndef is_image(x: Any) -> bool:\n    return isinstance(x, (np.ndarray, Image.Image))","tryCatchPattern":null,"preventionTips":["Unwrap custom frame/tensor wrappers before calling supervision utilities.","Pass loaded arrays, not paths.","Type-annotate image parameters as np.ndarray in your own code to catch this statically."],"tags":["image","resolution","type-guard","numpy","pillow"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}