{"record":{"id":"cb36ba972db9e8d8","repo":"docling-project/docling","slug":"unsupported-numpy-array-shape-image-shape-cb36ba","errorCode":null,"errorMessage":"Unsupported numpy array shape: {image.shape}","messagePattern":"Unsupported numpy array shape: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"docling/models/vlm_pipeline_models/mlx_model.py","lineNumber":223,"sourceCode":"\n        # MLX models are not thread-safe - use global lock to serialize access\n        with _MLX_GLOBAL_LOCK:\n            _log.debug(\"MLX model: Acquired global lock for thread safety\")\n            for image, user_prompt in zip(image_list, user_prompts):\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                        # RGB or RGBA array\n                        from PIL import Image as PILImage\n\n                        image = PILImage.fromarray(image.astype(np.uint8))\n                    elif image.ndim == 2:\n                        # Grayscale array\n                        from PIL import Image as PILImage\n\n                        image = PILImage.fromarray(image.astype(np.uint8), mode=\"L\")\n                    else:\n                        raise ValueError(\n                            f\"Unsupported numpy array shape: {image.shape}\"\n                        )\n\n                # Ensure image is in RGB mode (handles RGBA, L, etc.)\n                if image.mode != \"RGB\":\n                    image = image.convert(\"RGB\")\n\n                # Use the MLX chat template approach like in the __call__ method\n                formatted_prompt = self.apply_chat_template(\n                    self.processor, self.config, user_prompt, num_images=1\n                )\n\n                # Stream generate with stop strings and custom stopping criteria support\n                start_time = time.time()\n                _log.debug(\"start generating ...\")\n\n                tokens: list[VlmPredictionToken] = []\n                output = \"\"","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/models/vlm_pipeline_models/mlx_model.py#L205-L241","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Transpose CHW to HWC before the call","Squeeze (H,W,1) down to (H,W) for the grayscale branch","Pass PIL/docling Image objects to bypass numpy conversion entirely"],"exampleFix":"# before\nmodel.process_images([chw_array], \"describe\")  # (3,H,W) -> ValueError\n# after\nmodel.process_images([chw_array.transpose(1, 2, 0)], \"describe\")","handlingStrategy":"type-guard","validationCode":"import numpy as np\n\ndef normalize_raster(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    if a.ndim not in (2, 3) or (a.ndim == 3 and a.shape[2] not in (3, 4)):\n        raise ValueError(f'cannot normalize shape {a.shape}')\n    return a.astype(np.uint8)","typeGuard":"import numpy as np\n\ndef is_supported_mlx_raster(a: np.ndarray) -> bool:\n    return a.ndim == 2 or (a.ndim == 3 and a.shape[2] in (3, 4))","tryCatchPattern":"try:\n    model.process_images(images, prompt)\nexcept ValueError as e:\n    if 'Unsupported numpy array shape' in str(e):\n        images = [normalize_raster(im) for im in images]\n        model.process_images(images, prompt)\n    else:\n        raise","preventionTips":["Convert CHW tensors to HWC before crossing into Docling","Squeeze singleton channel axes on masks and grayscale sources","Prefer PIL Image inputs on macOS pipelines"],"tags":["mlx","numpy","image","shape"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}