{"record":{"id":"1181a7331c8e28ce","repo":"ultralytics/yolov5","slug":"len-of-masks-shape-should-be-2-or-3-but-got-le","errorCode":null,"errorMessage":"\"len of masks shape\" should be 2 or 3, but got {len(masks.shape)}","messagePattern":"\"len of masks shape\" should be 2 or 3, but got (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"utils/segment/general.py","lineNumber":101,"sourceCode":"        im1_shape (tuple): Model input shape as (h, w).\n        masks (np.ndarray): Masks with shape (h, w, num).\n        im0_shape (tuple): Original image shape as (h, w, 3).\n        ratio_pad (tuple, optional): Ratio and padding for scaling. If None, calculated from the shapes.\n\n    Returns:\n        (np.ndarray): Rescaled masks resized to im0_shape.\n    \"\"\"\n    # Rescale coordinates (xyxy) from im1_shape to im0_shape\n    if ratio_pad is None:  # calculate from im0_shape\n        gain = min(im1_shape[0] / im0_shape[0], im1_shape[1] / im0_shape[1])  # gain  = old / new\n        pad = (im1_shape[1] - im0_shape[1] * gain) / 2, (im1_shape[0] - im0_shape[0] * gain) / 2  # wh padding\n    else:\n        pad = ratio_pad[1]\n    top, left = int(pad[1]), int(pad[0])  # y, x\n    bottom, right = int(im1_shape[0] - pad[1]), int(im1_shape[1] - pad[0])\n\n    if len(masks.shape) < 2:\n        raise ValueError(f'\"len of masks shape\" should be 2 or 3, but got {len(masks.shape)}')\n    masks = masks[top:bottom, left:right]\n    if masks.ndim == 3 and masks.shape[2] > 128:  # OpenCV 5 lowered CV_CN_MAX from 512 to 128\n        masks = [\n            cv2.resize(masks[:, :, i : i + 128], (im0_shape[1], im0_shape[0])) for i in range(0, masks.shape[2], 128)\n        ]\n        masks = np.concatenate([x if x.ndim == 3 else x[:, :, None] for x in masks], axis=2)\n    else:\n        masks = cv2.resize(masks, (im0_shape[1], im0_shape[0]))\n\n    if len(masks.shape) == 2:\n        masks = masks[:, :, None]\n    return masks\n","sourceCodeStart":83,"sourceCodeEnd":114,"githubUrl":"https://github.com/ultralytics/yolov5/blob/20d1d78a08277e365d57bfa3a2cce752772d9e59/utils/segment/general.py#L83-L114","documentation":"Raised in scale_image (utils/segment/general.py) during segmentation post-processing when the masks tensor passed in has fewer than 2 dimensions. The rescale logic slices masks[top:bottom, left:right] and resizes with cv2.resize, both of which require at least a 2D (H, W) or 3D (H, W, N) array; a 0D scalar or 1D vector of masks cannot be spatially rescaled.","triggerScenarios":"Calling scale_image() (directly, or via segment/val.py or segment/predict.py post-processing) with masks that is a 0-d array or a 1-d array — e.g. an empty mask stack that was squeezed, a single mask stored as shape (N,) instead of (H, W), or a mis-shaped output from a custom mask head / prototyping code.","commonSituations":"Custom segmentation models whose mask output shape differs from YOLOv5's expected (N, H, W) or (H, W); running prediction on a batch that produced zero detections and downstream code squeezed the empty mask array; converting masks between tensor/numpy formats and losing a dimension; unit tests passing flat arrays.","solutions":["Check the shape of the masks array right before scale_image is called; a segmentation inference should produce (num_masks, H, W) after processing_masks, e.g. print(masks.shape).","If masks can be empty, guard upstream: skip scale_image when masks.size == 0 or masks.ndim < 2 instead of passing a squeezed empty array.","Fix the producer: ensure the mask head / process_mask output keeps 2/3 dims (avoid np.squeeze without axis, avoid indexing that drops the spatial dims).","If writing custom code, reshape to (H, W) or (H, W, 1) explicitly before calling: masks = masks.reshape(h, w, -1)."],"exampleFix":"# before\nmasks = scale_masks(masks, im0_shape)  # masks may be shape (0,) after squeeze\n# after\nif masks.ndim < 2 or masks.size == 0:\n    masks = np.zeros((im0_shape[0], im0_shape[1], 0), dtype=np.float32)\nelse:\n    masks = scale_masks(masks, im0_shape)","handlingStrategy":"type-guard","validationCode":"import numpy as np\n\nassert masks.ndim >= 2, (\n    f\"masks must be (H, W) or (H, W, N); got shape {masks.shape}. \"\n    \"Check process_mask output and avoid squeezing empty mask stacks.\"\n)\nif masks.size == 0:\n    masks = np.zeros((im0_shape[0], im0_shape[1], 0), dtype=np.float32)","typeGuard":"def is_rescalable_mask_array(masks) -> bool:\n    \"\"\"masks must be an ndarray with 2 or 3 dims and nonzero spatial size.\"\"\"\n    return isinstance(masks, np.ndarray) and masks.ndim in (2, 3) and masks.shape[0] > 0 and masks.shape[1] > 0","tryCatchPattern":"try:\n    masks = scale_image(masks, im0_shape, ratio_pad=ratio_pad)\nexcept ValueError as e:\n    if 'should be 2 or 3' in str(e):\n        LOGGER.warning(f\"Skipping mask rescale for degenerate mask shape {masks.shape}\")\n        masks = np.zeros((im0_shape[0], im0_shape[1], 0), dtype=np.float32)\n    else:\n        raise","preventionTips":["Never call np.squeeze on mask stacks without an explicit axis; empty detection batches collapse to <2 dims.","Keep mask tensors in (N, H, W) until the final per-image step.","In custom mask heads, assert the output is 3D before handing it to postprocessing."],"tags":["segmentation","masks","numpy","postprocessing","shape-validation"],"backgroundTag":null,"analyzedSha":"20d1d78a08277e365d57bfa3a2cce752772d9e59","analyzedAt":"2026-08-15T02:56:15.443Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}