{"record":{"id":"d70b42e0746470f7","repo":"roboflow/supervision","slug":"cannot-merge-an-empty-list-of-compactmask-objects","errorCode":null,"errorMessage":"Cannot merge an empty list of CompactMask objects.","messagePattern":"Cannot merge an empty list of CompactMask objects\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/detection/compact_mask.py","lineNumber":1356,"sourceCode":"\n        Examples:\n            ```pycon\n            >>> import numpy as np\n            >>> from supervision.detection.compact_mask import CompactMask\n            >>> masks1 = np.zeros((2, 50, 50), dtype=bool)\n            >>> masks2 = np.zeros((3, 50, 50), dtype=bool)\n            >>> xyxy1 = np.array([[0,0,10,10],[10,10,20,20]], dtype=np.float32)\n            >>> xyxy2 = np.array(\n            ...     [[0,0,5,5],[5,5,10,10],[10,10,15,15]], dtype=np.float32)\n            >>> cm1 = CompactMask.from_dense(masks1, xyxy1, image_shape=(50, 50))\n            >>> cm2 = CompactMask.from_dense(masks2, xyxy2, image_shape=(50, 50))\n            >>> len(CompactMask.merge([cm1, cm2]))\n            5\n\n            ```\n        \"\"\"\n        if not masks_list:\n            raise ValueError(\"Cannot merge an empty list of CompactMask objects.\")\n\n        image_shape = masks_list[0]._image_shape\n        for cm in masks_list[1:]:\n            if cm._image_shape != image_shape:\n                raise ValueError(\n                    f\"Cannot merge CompactMask objects with different image shapes: \"\n                    f\"{image_shape} vs {cm._image_shape}\"\n                )\n\n        # list.extend is a C-level call and avoids the per-element Python\n        # bytecode overhead of a flat list comprehension.  This matters under\n        # GIL contention when multiple threads call merge concurrently.\n        new_rles: list[npt.NDArray[np.int32]] = []\n        for cm in masks_list:\n            new_rles.extend(cm._rles)\n\n        # np.concatenate handles (0, 2) arrays correctly.\n        # No .astype() needed — _crop_shapes and _offsets are already int32.","sourceCodeStart":1338,"sourceCodeEnd":1374,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/detection/compact_mask.py#L1338-L1374","documentation":"Raised by CompactMask.merge when masks_list is empty. Merging zero objects has no meaningful result (there is no image shape to inherit), so the API refuses rather than guessing. Pass at least one CompactMask.","triggerScenarios":"Calling CompactMask.merge([]) — typically because a loop collecting per-tile or per-batch CompactMask objects produced an empty list (no detections anywhere, empty directory, all tiles filtered out).","commonSituations":"Aggregating slicer outputs when the model returned nothing on any tile; merging per-image masks in a loop over an empty folder; a guard upstream filtered everything.","solutions":["Skip the merge when the list is empty: if not masks_list: continue / return an empty CompactMask built from the known image_shape.","If detections were expected, debug why the producing step yielded no CompactMask objects before calling merge.","Construct an empty result explicitly via CompactMask.from_dense(np.zeros((0, h, w), bool), np.empty((0, 4)), image_shape) when an empty object is needed."],"exampleFix":"# before\nmerged = CompactMask.merge(tile_masks)\n\n# after\nif tile_masks:\n    merged = CompactMask.merge(tile_masks)\nelse:\n    merged = CompactMask.from_dense(\n        np.zeros((0, h, w), dtype=bool), np.empty((0, 4), dtype=np.float32),\n        image_shape=(h, w))","handlingStrategy":"validation","validationCode":"if not tile_masks:\n    merged = sv.CompactMask.from_dense(\n        np.zeros((0, h, w), dtype=bool),\n        np.empty((0, 4), dtype=np.float32),\n        image_shape=(h, w),\n    )\nelse:\n    merged = sv.CompactMask.merge(tile_masks)","typeGuard":"def is_mergeable(masks_list) -> bool:\n    return isinstance(masks_list, (list, tuple)) and len(masks_list) > 0","tryCatchPattern":"try:\n    merged = sv.CompactMask.merge(tile_masks)\nexcept ValueError as e:\n    if \"empty list\" in str(e):\n        merged = None  # nothing detected on any tile\n    else:\n        raise","preventionTips":["Guard merges behind `if masks_list:` whenever inputs come from detection loops that can yield zero results.","Return a canonical empty CompactMask for the known image_shape instead of calling merge with [].","Log the tile count before merging in slicer pipelines to notice all-empty batches."],"tags":["compact-mask","merge","empty-input","slicing"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}