{"record":{"id":"a4613eb60cc7fd77","repo":"docling-project/docling","slug":"unsupported-numpy-array-shape-image-shape-a4613e","errorCode":null,"errorMessage":"Unsupported numpy array shape: {image.shape}","messagePattern":"Unsupported numpy array shape: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"docling/models/vlm_pipeline_models/api_vlm_model.py","lineNumber":129,"sourceCode":"                    f\"Prompt list length ({len(prompt)}) must match image count ({len(images)})\"\n                )\n            prompts = prompt\n\n        def _process_single_image(image_prompt_pair):\n            image, prompt_text = image_prompt_pair\n\n            # Convert numpy array to PIL Image if needed\n            if isinstance(image, np.ndarray):\n                if image.ndim == 3 and image.shape[2] in [3, 4]:\n                    from PIL import Image as PILImage\n\n                    image = PILImage.fromarray(image.astype(np.uint8))\n                elif image.ndim == 2:\n                    from PIL import Image as PILImage\n\n                    image = PILImage.fromarray(image.astype(np.uint8), mode=\"L\")\n                else:\n                    raise ValueError(f\"Unsupported numpy array shape: {image.shape}\")\n\n            # Ensure image is in RGB mode\n            if image.mode != \"RGB\":\n                image = image.convert(\"RGB\")\n\n            stop_reason = VlmStopReason.UNSPECIFIED\n\n            if self.vlm_options.custom_stopping_criteria:\n                # Instantiate any GenerationStopper classes before passing to streaming\n                instantiated_stoppers = []\n                for criteria in self.vlm_options.custom_stopping_criteria:\n                    if isinstance(criteria, GenerationStopper):\n                        instantiated_stoppers.append(criteria)\n                    elif isinstance(criteria, type) and issubclass(\n                        criteria, GenerationStopper\n                    ):\n                        instantiated_stoppers.append(criteria())\n                    # Skip non-GenerationStopper criteria (should have been caught in validation)","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/models/vlm_pipeline_models/api_vlm_model.py#L111-L147","documentation":"ApiVlmModel's numpy-to-PIL conversion only accepts 2-D grayscale arrays (H,W) and 3-D arrays whose last dimension is 3 (RGB) or 4 (RGBA). Any other layout — e.g. channels-first (3,H,W), single-column shape (H,W,1), or 4-D batch arrays — raises this ValueError before the image is sent to the API.","triggerScenarios":"Passing a channels-first array from a torch/opencv preprocessing step, an (H,W,1) array from an expanded grayscale mask, or a 4-D stacked batch where a single image is expected.","commonSituations":"Feeding model-preprocessed tensors (CHW convention) directly instead of raw page rasters; using np.expand_dims on grayscale images; accidentally passing a batch ndarray instead of iterating its items.","solutions":["Transpose channels-first arrays to HWC: arr.transpose(1,2,0)","Squeeze (H,W,1) to (H,W), or use the 2-D grayscale path directly","Pass docling Image objects or PIL Images instead of raw arrays to skip conversion entirely"],"exampleFix":"# before\nimg = model_preprocessed_chw  # shape (3, 1024, 768)\nout = model._process_batch([img], prompt)\n# after\nimg = model_preprocessed_chw.transpose(1, 2, 0)  # shape (1024, 768, 3)\nout = model._process_batch([img], prompt)","handlingStrategy":"type-guard","validationCode":"import numpy as np\n\ndef is_supported_raster(a: np.ndarray) -> bool:\n    return a.ndim == 2 or (a.ndim == 3 and a.shape[2] in (3, 4))\n\nfor a in images:\n    if not is_supported_raster(a):\n        raise ValueError(f'bad shape {a.shape}')","typeGuard":"import numpy as np\n\ndef to_hwc(a: np.ndarray) -> np.ndarray:\n    if a.ndim == 3 and a.shape[0] in (3, 4) and a.shape[2] not in (3, 4):\n        a = a.transpose(1, 2, 0)\n    if a.ndim == 3 and a.shape[2] == 1:\n        a = a[:, :, 0]\n    assert a.ndim == 2 or (a.ndim == 3 and a.shape[2] in (3, 4)), a.shape\n    return a","tryCatchPattern":"try:\n    out = model._process_batch(images, prompt)\nexcept ValueError as e:\n    if 'Unsupported numpy array shape' in str(e):\n        images = [to_hwc(im) for im in images]\n        out = model._process_batch(images, prompt)\n    else:\n        raise","preventionTips":["Standardize on HWC uint8 rasters at your pipeline boundary","Convert torch tensors with .permute(1,2,0).cpu().numpy()","Prefer passing docling Image objects so the library renders pages itself"],"tags":["numpy","image","shape","vlm","api"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}