docling-project/docling · error · ValueError

Unsupported numpy array shape: {image.shape}

Error message

Unsupported numpy array shape: {image.shape}

What it means

In the VLM image preprocessing utility, a numpy array is accepted only with ndim==2 (grayscale) or ndim==3 with last dimension 3 or 4 (RGB/RGBA). Any other shape (e.g. 3 dims with shape[2]==1, or 4+ dims) raises ValueError('Unsupported numpy array shape').

Source

Thrown at docling/models/inference_engines/vlm/_utils.py:41

    Args:
        image: Input image as PIL Image or numpy array

    Returns:
        RGB PIL Image

    Raises:
        ValueError: If numpy array has unsupported shape
    """
    # Handle numpy arrays
    if isinstance(image, np.ndarray):
        if image.ndim == 3 and image.shape[2] in [3, 4]:
            # RGB or RGBA array
            image = Image.fromarray(image.astype(np.uint8))
        elif image.ndim == 2:
            # Grayscale array
            image = Image.fromarray(image.astype(np.uint8), mode="L")
        else:
            raise ValueError(f"Unsupported numpy array shape: {image.shape}")

    # Ensure RGB mode (handles RGBA, L, P, etc.)
    if image.mode != "RGB":
        image = image.convert("RGB")

    return image


def preprocess_image_batch(
    images: List[Union[Image.Image, np.ndarray]],
) -> List[Image.Image]:
    """Preprocess a batch of images to RGB PIL Images.

    Args:
        images: List of images as PIL Images or numpy arrays

    Returns:
        List of RGB PIL Images

View on GitHub (pinned to 61d76f1ff3)

Solutions

  1. Squeeze singleton channel dims before passing: arr = arr.squeeze() or arr = arr[:, :, 0] for (H, W, 1).
  2. Convert to a PIL Image yourself (Image.fromarray(arr).convert('RGB')) and pass PIL images instead.
  3. For BGR cv2 arrays, cv2.cvtColor(arr, cv2.COLOR_BGR2RGB) first — and ensure ndim/shape fit the accepted forms.

Example fix

# before
image = cv2.imread(path, cv2.IMREAD_UNCHANGED)  # may be (H, W, 1) or BGRA
outputs = vlm_engine.predict_batch([{"image": image, "prompt": p}])

# after
image = cv2.imread(path)
if image.ndim == 2:
    pass
elif image.ndim == 3 and image.shape[2] == 1:
    image = image[:, :, 0]
else:
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
outputs = vlm_engine.predict_batch([{"image": image, "prompt": p}])
Defensive patterns

Strategy: type-guard

Validate before calling

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

if isinstance(img, np.ndarray) and not is_acceptable_array(img):
    img = np.squeeze(img)  # or convert explicitly before passing

Type guard

def is_vlm_ready_image(x: object) -> TypeGuard[Union[Image.Image, "np.ndarray"]]:
    if isinstance(x, Image.Image):
        return True
    return isinstance(x, np.ndarray) and (x.ndim == 2 or (x.ndim == 3 and x.shape[2] in (3, 4)))

Try / catch

try:
    images = preprocess_image_batch(raw_images)
except ValueError as e:
    raise ValueError(f"Normalize arrays to HxW, HxWx3 or HxWx4 before VLM inference: {e}") from e

Prevention

When it happens

Trigger: Passing numpy images to a VLM engine predict call where arrays come from cv2 with BGR + extra channel, single-channel arrays kept as (H, W, 1), float arrays with an unexpected axis, or batched arrays of shape (N, H, W, 3).

Common situations: cv2.imread with IMREAD_UNCHANGED on grayscale PNGs producing (H, W, 1); medical/scientific imaging pipelines with (H, W, 1) or (H, W, k>4) multi-channel stacks; passing batched tensors instead of per-image arrays.

Related errors


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