{"record":{"id":"17c079c03ce166d7","repo":"roboflow/supervision","slug":"confusion-matrix-must-have-shape-3-got-co","errorCode":null,"errorMessage":"Confusion matrix must have shape (..., 3), got {confusion_matrix.shape}","messagePattern":"Confusion matrix must have shape \\(\\.\\.\\., 3\\), got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/metrics/mean_average_recall.py","lineNumber":653,"sourceCode":"        result_confusion_matrix: npt.NDArray[np.float64] = confusion_matrix\n        return result_confusion_matrix\n\n    @staticmethod\n    def _compute_recall(\n        confusion_matrix: npt.NDArray[np.float64],\n    ) -> npt.NDArray[np.float64]:\n        \"\"\"\n        Broadcastable function, computing the recall from the confusion matrix.\n\n        Args:\n            confusion_matrix: shape (N, ..., 3), where the last dimension\n                contains the true positives, false positives, and false negatives.\n\n        Returns:\n            shape (N, ...), containing the recall for each element.\n        \"\"\"\n        if not confusion_matrix.shape[-1] == 3:\n            raise ValueError(\n                f\"Confusion matrix must have shape (..., 3), got \"\n                f\"{confusion_matrix.shape}\"\n            )\n        true_positives = confusion_matrix[..., 0]\n        false_negatives = confusion_matrix[..., 2]\n\n        denominator = true_positives + false_negatives\n        recall = np.divide(\n            true_positives,\n            denominator,\n            out=np.zeros_like(denominator, dtype=np.float64),\n            where=denominator != 0,\n        )\n\n        result_recall: npt.NDArray[np.float64] = recall\n        return result_recall\n\n    def _detections_content(","sourceCodeStart":635,"sourceCodeEnd":671,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/metrics/mean_average_recall.py#L635-L671","documentation":"This ValueError comes from the internal broadcastable helper _recall_from_confusion_matrix, which computes recall = TP/(TP+FN) from an array whose last axis must hold exactly [TP, FP, FN]. It fires when the supplied confusion-matrix-like array's last dimension is not 3. End users normally never touch this helper; it is exercised inside MeanAverageRecall.compute(), so seeing it usually means the private API was called directly with a wrongly shaped array, or upstream code built an invalid stats structure.","triggerScenarios":"Calling MeanAverageRecall._recall_from_confusion_matrix (private) with an array whose last axis has != 3 elements, e.g. shape (N,4) from a 2x2 confusion matrix or (N,2) TP/FP-only arrays; internal misuse in compute() would indicate a supervision bug or corrupted stats accumulation (e.g. custom fork modified the stats tuples).","commonSituations":"Reusing code written for binary classification 2x2 matrices; passing precision-oriented [TP, FP] pairs; contributing to/forking supervision and changing the stats tuple layout; feeding precomputed arrays from another library (torchmetrics, sklearn) without reshaping.","solutions":["Reshape your data to (..., 3) with columns [true_positives, false_positives, false_negatives] before calling the helper","If integrating external confusion matrices, convert: stack TP, FP, FN along the last axis with np.stack([tp, fp, fn], axis=-1)","Do not call the private helper directly; use the public update()/compute() API which builds correctly shaped arrays","If reached via public compute() on unmodified supervision, report it as a bug with a reproducer"],"exampleFix":"# before\nrecall = mar._recall_from_confusion_matrix(np.stack([tp, fp], axis=-1))  # (N,2)\n\n# after\ncm = np.stack([tp, fp, fn], axis=-1)  # shape (N, 3): [TP, FP, FN]\nrecall = mar._recall_from_confusion_matrix(cm)","handlingStrategy":"validation","validationCode":"import numpy as np\n\ndef valid_confusion_matrix(cm: np.ndarray) -> bool:\n    \"\"\"True when last axis holds [TP, FP, FN].\"\"\"\n    return cm.ndim >= 1 and cm.shape[-1] == 3","typeGuard":"import numpy as np\n\ndef is_tpfpn_array(arr: object) -> bool:\n    \"\"\"Narrow an object to a (..., 3) TP/FP/FN numpy array.\"\"\"\n    return isinstance(arr, np.ndarray) and arr.ndim >= 1 and arr.shape[-1] == 3","tryCatchPattern":"try:\n    recall = helper(cm)\nexcept ValueError as e:\n    raise ValueError(f'reshape {cm.shape} to (..., 3) as [TP, FP, FN]') from e","preventionTips":["Build the array with np.stack([tp, fp, fn], axis=-1) so shape is correct by construction","Do not call private _-prefixed helpers directly; use update()/compute()","When porting from sklearn 2x2 matrices, convert explicitly to TP/FP/FN triplets"],"tags":["metrics","mean-average-recall","internal-api","numpy","shape-validation"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}