{"record":{"id":"a6d95989725d7815","repo":"roboflow/supervision","slug":"callback-returned-len-detections-in-slices-dete","errorCode":null,"errorMessage":"Callback returned {len(detections_in_slices)} Detections for {len(offsets)} slices. Lengths must match.","messagePattern":"Callback returned (.+?) Detections for (.+?) slices\\. Lengths must match\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/detection/tools/inference_slicer.py","lineNumber":591,"sourceCode":"                with self._raster_read_lock:\n                    bands = image.read(window=window)\n                slices.append(np.ascontiguousarray(np.transpose(bands, (1, 2, 0))))\n            resolution_wh = (image.width, image.height)\n        else:\n            slices = [crop_image(image=image, xyxy=offset) for offset in offsets]\n            resolution_wh = get_image_resolution_wh(image)\n\n        batch_callback = cast(\n            Callable[[list[npt.NDArray[Any]]], list[Detections]], self.callback\n        )\n        detections_in_slices = batch_callback(slices)\n        if not isinstance(detections_in_slices, list):\n            raise ValueError(\n                \"Callback must return `list[Detections]` when `batch_size > 1`. \"\n                f\"Got: {type(detections_in_slices)}\"\n            )\n        if len(detections_in_slices) != len(offsets):\n            raise ValueError(\n                f\"Callback returned {len(detections_in_slices)} Detections \"\n                f\"for {len(offsets)} slices. Lengths must match.\"\n            )\n\n        if self.compact_masks:\n            for det, image_slice in zip(detections_in_slices, slices):\n                if det.mask is not None and isinstance(det.mask, np.ndarray):\n                    slice_w, slice_h = get_image_resolution_wh(image_slice)\n                    full_slice_xyxy = np.tile(\n                        np.array([[0, 0, slice_w - 1, slice_h - 1]], dtype=np.float64),\n                        (len(det), 1),\n                    )\n                    det.mask = CompactMask.from_dense(\n                        det.mask,\n                        full_slice_xyxy,\n                        image_shape=(slice_h, slice_w),\n                    )\n","sourceCodeStart":573,"sourceCodeEnd":609,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/detection/tools/inference_slicer.py#L573-L609","documentation":"Raised by InferenceSlicer's batch path when the callback's returned list length differs from the number of image slices passed in. The slicer must zip each returned Detections with its slice offset to map detections back into full-image coordinates; a mismatched length breaks that 1:1 alignment, so it fails instead of silently dropping or misplacing detections.","triggerScenarios":"A batch callback that returns model predictions for a filtered subset (e.g. only images with detections), or a predict call that returns fewer Results than inputs (some backends skip failed images), or returning e.g. results[:-1] by an off-by-one bug.","commonSituations":"Callbacks that filter empty results; batched inference wrappers that deduplicate or drop failed items; misunderstanding that one Detections per input slice is required even when a slice has zero detections.","solutions":["Always return exactly one Detections (possibly empty, sv.Detections.empty()) per input image, in input order.","If the model API can skip images, index-pad the results back to the input length before returning.","Do not filter slices inside the callback — filtering happens later via the slicer's overlap/NMS stages."],"exampleFix":"# before\ndef callback(images):\n    return [sv.Detections.from_ultralytics(r) for r in model.predict(images) if len(r.boxes) > 0]\n\n# after\ndef callback(images):\n    return [sv.Detections.from_ultralytics(r) for r in model.predict(images)]  # empty slices yield empty Detections","handlingStrategy":"validation","validationCode":"def batch_callback(images):\n    results = model.predict(images, verbose=False)\n    if len(results) != len(images):\n        raise RuntimeError(f'model returned {len(results)} results for {len(images)} images')\n    return [sv.Detections.from_ultralytics(r) for r in results]","typeGuard":"def matches_slice_count(result, images) -> bool:\n    return isinstance(result, list) and len(result) == len(images)","tryCatchPattern":"try:\n    detections = slicer(image)\nexcept ValueError as err:\n    if 'Lengths must match' in str(err):\n        raise RuntimeError('batch callback must return one Detections per input slice') from err\n    raise","preventionTips":["Never filter empty results inside the callback — return sv.Detections.empty() for empty slices.","Assert len(results) == len(images) right after the model call for a clearer failure point."],"tags":["inference-slicer","batching","callback-contract","valueerror"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}