opendatalab/MinerU · error · ValueError

Input must be a pillow object or a numpy array.

Error message

Input must be a pillow object or a numpy array.

What it means

The wired-table predict entry point accepts only PIL Images or numpy arrays; anything else (file path string, bytes, torch tensor) is rejected up front because the code immediately does np.asarray / cv2.cvtColor on it.

Source

Thrown at mineru/model/table/rec/unet_table/main.py:280

    td_count = html_lower.count('<td')
    th_count = html_lower.count('<th')
    return td_count + th_count


class UnetTableModel:
    def __init__(self, ocr_engine):
        model_path = os.path.join(auto_download_and_get_model_root_path(ModelPath.unet_structure), ModelPath.unet_structure)
        wired_input_args = WiredTableInput(model_path=model_path)
        self.wired_table_model = WiredTableRecognition(wired_input_args, ocr_engine)
        self.ocr_engine = ocr_engine

    def predict(self, input_img, ocr_result, wireless_html_code, return_metadata: bool = False):
        if isinstance(input_img, Image.Image):
            np_img = np.asarray(input_img)
        elif isinstance(input_img, np.ndarray):
            np_img = input_img
        else:
            raise ValueError("Input must be a pillow object or a numpy array.")
        bgr_img = cv2.cvtColor(np_img, cv2.COLOR_RGB2BGR)

        if ocr_result is None:
            ocr_result = self.ocr_engine.ocr(bgr_img)[0]
            ocr_result = [
                [item[0], escape_html(item[1][0]), item[1][1]]
                for item in ocr_result
                if len(item) == 2 and isinstance(item[1], tuple)
            ]

        try:
            wired_table_results = self.wired_table_model(np_img, ocr_result)
            wired_structure_results = (
                self.wired_table_model(np_img, need_ocr=False)
                if return_metadata
                else None
            )

View on GitHub (pinned to 4fe4bde114)

Solutions

  1. Load paths first: Image.open(path) or cv2.imread(path).
  2. Wrap bytes: Image.open(BytesIO(data)).
  3. Convert tensors: img = tensor.cpu().numpy().transpose(1, 2, 0).

Example fix

# before
result = model.predict(img_bytes, ocr_result, html)

# after
from io import BytesIO
from PIL import Image
result = model.predict(np.asarray(Image.open(BytesIO(img_bytes)).convert('RGB')), ocr_result, html)
Defensive patterns

Strategy: type-guard

Validate before calling

def to_ndarray(img):
    if isinstance(img, Image.Image):
        return np.asarray(img.convert('RGB'))
    if isinstance(img, np.ndarray):
        return img
    if isinstance(img, (bytes, bytearray)):
        return np.asarray(Image.open(BytesIO(img)).convert('RGB'))
    if isinstance(img, (str, Path)):
        return np.asarray(Image.open(img).convert('RGB'))
    raise TypeError(type(img))

Type guard

def is_supported_input(img) -> bool:
    return isinstance(img, (Image.Image, np.ndarray))

Try / catch

try:
    res = model.predict(img, ocr_result, html)
except ValueError as e:
    if 'pillow object or a numpy array' in str(e):
        res = model.predict(to_ndarray(img), ocr_result, html)
    else:
        raise

Prevention

When it happens

Trigger: Calling predict(input_img='/path/table.png', ...), passing raw bytes from an upload, or passing a torch.Tensor crop from a GPU pipeline.

Common situations: Wiring a web endpoint that forwards the uploaded bytes directly, or chaining with a DL pipeline whose outputs are tensors.

Related errors


AI-assisted analysis of opendatalab/MinerU@4fe4bde114 (2026-08-14). Data as JSON: /api/errors/d2ba4b2cf2e536b1. Report an issue: GitHub.