docling-project/docling · error · ValueError

Unsupported numpy array shape: {img.shape}

Error message

Unsupported numpy array shape: {img.shape}

What it means

Before generation, HuggingFaceTransformersVlmModel normalizes every input to an RGB PIL image. The numpy branch accepts only (H,W), (H,W,3) and (H,W,4); anything else — channels-first (C,H,W), (H,W,1), or higher-rank arrays — raises ValueError with the offending shape.

Source

Thrown at docling/models/vlm_pipeline_models/hf_transformers_model.py:266

        Batched inference for Hugging Face Image-Text-to-Text VLMs (e.g., SmolDocling / SmolVLM).
        - Lets the processor handle all padding & batching for text+images.
        - Trims generated sequences per row using attention_mask (no pad-id fallbacks).
        - Keeps your formulate_prompt() exactly as-is.
        """
        import numpy as np
        import torch
        from PIL import Image as PILImage

        # -- Normalize images to RGB PIL
        pil_images: list[Image] = []
        for img in image_batch:
            if isinstance(img, np.ndarray):
                if img.ndim == 3 and img.shape[2] in (3, 4):
                    pil_img = PILImage.fromarray(img.astype(np.uint8))
                elif img.ndim == 2:
                    pil_img = PILImage.fromarray(img.astype(np.uint8), mode="L")
                else:
                    raise ValueError(f"Unsupported numpy array shape: {img.shape}")
            else:
                pil_img = img
            if pil_img.mode != "RGB":
                pil_img = pil_img.convert("RGB")
            pil_images.append(pil_img)

        if not pil_images:
            return

        # -- Normalize prompts (1 per image)
        if isinstance(prompt, str):
            user_prompts = [prompt] * len(pil_images)
        else:
            if len(prompt) != len(pil_images):
                raise ValueError(
                    f"Number of prompts ({len(prompt)}) must match number of images ({len(pil_images)})"
                )
            user_prompts = prompt

View on GitHub (pinned to 61d76f1ff3)

Solutions

  1. Convert CHW to HWC with img.transpose(1,2,0)
  2. Squeeze trailing singleton channels: img.squeeze() for (H,W,1)
  3. Pass docling Image/PIL objects directly and let Docling handle page rasters

Example fix

# before
batch = [torch_img.numpy() for torch_img in imgs]  # each (3, H, W)
# after
batch = [torch_img.numpy().transpose(1, 2, 0) for torch_img in imgs]  # (H, W, 3)
Defensive patterns

Strategy: type-guard

Validate before calling

import numpy as np

def as_hwc_uint8(a):
    if a.ndim == 3 and a.shape[0] in (3, 4) and a.shape[2] not in (3, 4):
        a = a.transpose(1, 2, 0)
    if a.ndim == 3 and a.shape[2] == 1:
        a = a[..., 0]
    assert a.ndim == 2 or (a.ndim == 3 and a.shape[2] in (3, 4)), a.shape
    return a.astype(np.uint8)

images = [as_hwc_uint8(im) for im in images]

Type guard

import numpy as np

def is_supported_raster(a: np.ndarray) -> bool:
    return a.ndim == 2 or (a.ndim == 3 and a.shape[2] in (3, 4))

Try / catch

try:
    model(pages, prompt)
except ValueError as e:
    if 'Unsupported numpy array shape' in str(e):
        raise  # shapes are batch-wide; fix at source with as_hwc_uint8
    raise

Prevention

When it happens

Trigger: Passing torch-style CHW arrays, np.expand_dims-produced (H,W,1) masks, or a 4-D stacked batch ndarray where single images are expected in the image_batch iterable.

Common situations: Reusing preprocessing pipelines written for torch (CHW); feeding segmentation masks or depth maps with a trailing 1-channel dimension; passing float arrays that were never squeezed/converted.

Related errors


AI-assisted analysis of docling-project/docling@61d76f1ff3 (2026-08-14). Data as JSON: /api/errors/2b6f6f80d057257d. Report an issue: GitHub.