{"record":{"id":"110030eaa00ff9dd","repo":"roboflow/supervision","slug":"cannot-filter-keypoints-with-a-2d-boolean-mask-whe","errorCode":null,"errorMessage":"Cannot filter keypoints with a 2D boolean mask where rows have different numbers of True values. All objects must select the same number of keypoints. Got counts per object: {counts.tolist()}","messagePattern":"Cannot filter keypoints with a 2D boolean mask where rows have different numbers of True values\\. All objects must select the same number of keypoints\\. Got counts per object: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/key_points/core.py","lineNumber":876,"sourceCode":"        Raises:\n            ValueError: If `mask.shape[0]` does not match the number of objects, if\n                `mask.shape[1]` does not match the number of keypoints, or if\n                different rows of the mask select different numbers of `True` values.\n        \"\"\"\n        n = len(self.xy)\n        if mask.shape[0] != n:\n            raise ValueError(\n                f\"2D boolean mask row count {mask.shape[0]} does not match \"\n                f\"object count {n}.\"\n            )\n        if mask.shape[1] != self.xy.shape[1]:\n            raise ValueError(\n                f\"2D boolean mask column count {mask.shape[1]} does not match \"\n                f\"keypoint count {self.xy.shape[1]}.\"\n            )\n        counts = np.sum(mask, axis=1)\n        if n > 0 and not np.all(counts == counts[0]):\n            raise ValueError(\n                \"Cannot filter keypoints with a 2D boolean mask where rows have \"\n                \"different numbers of True values. \"\n                \"All objects must select the same number of keypoints. \"\n                f\"Got counts per object: {counts.tolist()}\"\n            )\n        k = int(counts[0]) if n > 0 else 0\n        xy_selected = np.zeros((n, k, self.xy.shape[2]), dtype=self.xy.dtype)\n        keypoint_confidence_selected: npt.NDArray[np.float32] | None = None\n        if self.keypoint_confidence is not None:\n            keypoint_confidence_selected = cast(\n                npt.NDArray[np.float32],\n                np.zeros((n, k), dtype=self.keypoint_confidence.dtype),\n            )\n        visible_selected: npt.NDArray[np.bool_] | None = None\n        if self.visible is not None:\n            visible_selected = np.zeros((n, k), dtype=bool)\n        for row in range(n):\n            row_indices = np.flatnonzero(mask[row])","sourceCodeStart":858,"sourceCodeEnd":894,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/key_points/core.py#L858-L894","documentation":"sv.F1Score.update() accepts a single (predictions, targets) pair or equal-length lists (one per image) and raises when the counts differ. Entries are matched index-wise, so unequal lists mean images on one side have no counterpart on the other and the metric would silently misattribute everything after the first gap.","triggerScenarios":"Calling f1.update(predictions=[...], targets=[...]) with mismatched list lengths — typically from loops that conditionally append to one list (e.g. skipping frames where the model found nothing) but not the other.","commonSituations":"Batch evaluation scripts appending predictions only for non-empty frames; dataset sweeps where some frames error out on one side; refactoring that moved one append inside an if-block.","solutions":["Append to both lists unconditionally; use sv.Detections.empty() for frames with no detections","Iterate with zip(image, ground_truth) so both sides stay paired","assert len(predictions_list) == len(targets_list) before update()","Call update() once per image with single Detections objects instead of accumulating lists"],"exampleFix":"# before\nfor img, gt in zip(images, gts):\n    det = detector(img)\n    if det is not None:\n        preds.append(det)\n    targets.append(gt)          # lists desync\nf1.update(predictions=preds, targets=targets)  # -> ValueError\n\n# after\nfor img, gt in zip(images, gts):\n    det = detector(img) or sv.Detections.empty()\n    preds.append(det)\n    targets.append(gt)\nf1.update(predictions=preds, targets=targets)","handlingStrategy":"validation","validationCode":"assert len(predictions) == len(targets), (\n    f'predictions ({len(predictions)}) and targets ({len(targets)}) must pair 1:1'\n)\nf1.update(predictions=predictions, targets=targets)","typeGuard":"def is_paired_batch(predictions: list, targets: list) -> bool:\n    \"\"\"True when both lists are equal-length lists of Detections.\"\"\"\n    return len(predictions) == len(targets) and all(\n        isinstance(p, sv.Detections) and isinstance(t, sv.Detections)\n        for p, t in zip(predictions, targets)\n    )","tryCatchPattern":null,"preventionTips":["Append sv.Detections.empty() for empty frames so lists never desync","Drive both appends from the same loop iteration over the dataset"],"tags":["metrics","f1-score","input-validation","batch-evaluation"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}