docling-project/docling · error · ValueError

Unsupported numpy array shape: {image.shape}

Error message

Unsupported numpy array shape: {image.shape}

What it means

Same raster contract as the other engines, enforced per image inside the MLX loop: numpy inputs must be (H,W), (H,W,3) or (H,W,4); anything else (CHW tensors, (H,W,1), 4-D) raises ValueError with the shape. Conversion happens under the global MLX lock, so the error surfaces mid-batch.

Source

Thrown at docling/models/vlm_pipeline_models/mlx_model.py:223

        # MLX models are not thread-safe - use global lock to serialize access
        with _MLX_GLOBAL_LOCK:
            _log.debug("MLX model: Acquired global lock for thread safety")
            for image, user_prompt in zip(image_list, user_prompts):
                # Convert numpy array to PIL Image if needed
                if isinstance(image, np.ndarray):
                    if image.ndim == 3 and image.shape[2] in [3, 4]:
                        # RGB or RGBA array
                        from PIL import Image as PILImage

                        image = PILImage.fromarray(image.astype(np.uint8))
                    elif image.ndim == 2:
                        # Grayscale array
                        from PIL import Image as PILImage

                        image = PILImage.fromarray(image.astype(np.uint8), mode="L")
                    else:
                        raise ValueError(
                            f"Unsupported numpy array shape: {image.shape}"
                        )

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

                # Use the MLX chat template approach like in the __call__ method
                formatted_prompt = self.apply_chat_template(
                    self.processor, self.config, user_prompt, num_images=1
                )

                # Stream generate with stop strings and custom stopping criteria support
                start_time = time.time()
                _log.debug("start generating ...")

                tokens: list[VlmPredictionToken] = []
                output = ""

View on GitHub (pinned to 61d76f1ff3)

Solutions

  1. Transpose CHW to HWC before the call
  2. Squeeze (H,W,1) down to (H,W) for the grayscale branch
  3. Pass PIL/docling Image objects to bypass numpy conversion entirely

Example fix

# before
model.process_images([chw_array], "describe")  # (3,H,W) -> ValueError
# after
model.process_images([chw_array.transpose(1, 2, 0)], "describe")
Defensive patterns

Strategy: type-guard

Validate before calling

import numpy as np

def normalize_raster(a: np.ndarray) -> np.ndarray:
    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]
    if a.ndim not in (2, 3) or (a.ndim == 3 and a.shape[2] not in (3, 4)):
        raise ValueError(f'cannot normalize shape {a.shape}')
    return a.astype(np.uint8)

Type guard

import numpy as np

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

Try / catch

try:
    model.process_images(images, prompt)
except ValueError as e:
    if 'Unsupported numpy array shape' in str(e):
        images = [normalize_raster(im) for im in images]
        model.process_images(images, prompt)
    else:
        raise

Prevention

When it happens

Trigger: Passing a channels-first (3,H,W) array, an (H,W,1) grayscale mask, or a 4-D batch ndarray as an element of image_batch to the MLX model.

Common situations: macOS preprocessing that keeps torch's CHW layout; grayscale masks with an explicit 1-channel axis; passing a stacked batch ndarray where one image is expected.

Related errors


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