Skip to content

平均精确率(Mean Average Precision)

supervision.metrics.mean_average_precision.MeanAveragePrecision

Bases: Metric

Mean Average Precision (mAP) is a metric used to evaluate object detection models. It is the average of the precision-recall curves at different IoU thresholds.

Example
import supervision as sv
from supervision.metrics import MeanAveragePrecision

predictions = sv.Detections(...)
targets = sv.Detections(...)

map_metric = MeanAveragePrecision()
map_result = map_metric.update(predictions, targets).compute()

print(map_result.map50_95)
# 0.4674

print(map_result)
# MeanAveragePrecisionResult:
# Metric target: MetricTarget.BOXES
# Class agnostic: False
# mAP @ 50:95: 0.4674
# mAP @ 50:    0.5048
# mAP @ 75:    0.4796
# mAP scores: [0.50485  0.50377  0.50377  ...]
# IoU thresh: [0.5  0.55  0.6  ...]
# AP per class:
# 0: [0.67699  0.67699  0.67699  ...]
# ...
# Small objects: ...
# Medium objects: ...
# Large objects: ...

map_result.plot()

example_plot

Source code in supervision/metrics/mean_average_precision.py
class MeanAveragePrecision(Metric):
    """
    Mean Average Precision (mAP) is a metric used to evaluate object detection models.
    It is the average of the precision-recall curves at different IoU thresholds.

    Example:
        ```python
        import supervision as sv
        from supervision.metrics import MeanAveragePrecision

        predictions = sv.Detections(...)
        targets = sv.Detections(...)

        map_metric = MeanAveragePrecision()
        map_result = map_metric.update(predictions, targets).compute()

        print(map_result.map50_95)
        # 0.4674

        print(map_result)
        # MeanAveragePrecisionResult:
        # Metric target: MetricTarget.BOXES
        # Class agnostic: False
        # mAP @ 50:95: 0.4674
        # mAP @ 50:    0.5048
        # mAP @ 75:    0.4796
        # mAP scores: [0.50485  0.50377  0.50377  ...]
        # IoU thresh: [0.5  0.55  0.6  ...]
        # AP per class:
        # 0: [0.67699  0.67699  0.67699  ...]
        # ...
        # Small objects: ...
        # Medium objects: ...
        # Large objects: ...

        map_result.plot()
        ```

    ![example_plot](\
        https://media.roboflow.com/supervision-docs/metrics/mAP_plot_example.png\
        ){ align=center width="800" }
    """

    def __init__(
        self,
        metric_target: MetricTarget = MetricTarget.BOXES,
        class_agnostic: bool = False,
        class_mapping: dict[int, int] | None = None,
        image_indices: list[int] | None = None,
    ):
        """
        Initialize the Mean Average Precision metric.

        Args:
            metric_target (MetricTarget): The type of detection data to use.
            class_agnostic (bool): Whether to treat all data as a single class.
            class_mapping (Optional[Dict[int, int]]): A dictionary to map class IDs to
            new IDs.
            image_indices (Optional[List[int]]): The indices of the images to use.
        """
        self._metric_target = metric_target
        self._class_agnostic = class_agnostic

        self._predictions_list: list[Detections] = []
        self._targets_list: list[Detections] = []
        self._class_mapping = class_mapping
        self._image_indices = image_indices

    def reset(self) -> None:
        """
        Reset the metric to its initial state, clearing all stored data.
        """
        self._predictions_list = []
        self._targets_list = []

    def update(
        self,
        predictions: Detections | list[Detections],
        targets: Detections | list[Detections],
    ) -> MeanAveragePrecision:
        """
        Add new predictions and targets to the metric, but do not compute the result.

        Args:
            predictions (Union[Detections, List[Detections]]): The predicted detections.
            targets (Union[Detections, List[Detections]]): The ground-truth detections.

        Returns:
            (MeanAveragePrecision): The updated metric instance.
        """
        if not isinstance(predictions, list):
            predictions = [predictions]
        if not isinstance(targets, list):
            targets = [targets]

        if len(predictions) != len(targets):
            raise ValueError(
                f"The number of predictions ({len(predictions)}) and"
                f" targets ({len(targets)}) during the update must be the same."
            )

        if self._class_agnostic:
            predictions = deepcopy(predictions)
            targets = deepcopy(targets)

            for prediction in predictions:
                prediction.class_id[:] = -1
            for target in targets:
                target.class_id[:] = -1

        self._predictions_list.extend(predictions)
        self._targets_list.extend(targets)

        return self

    def _prepare_targets(self, targets):
        """Transform targets into a dictionary that can be used by the COCO evaluator"""
        images = [{"id": img_id} for img_id in range(len(targets))]
        if self._image_indices is not None:
            images = [
                {"id": self._image_indices[img_id.get("id")]} for img_id in images
            ]
        # Annotations list
        annotations = []
        for image_id, image_targets in enumerate(targets):
            if self._image_indices is not None:
                image_id = self._image_indices[image_id]
            for target in image_targets:
                xyxy = target[0]  # or xyxy = prediction[0]; xyxy[2:4] -= xyxy[0:2]
                xywh = [xyxy[0], xyxy[1], xyxy[2] - xyxy[0], xyxy[3] - xyxy[1]]
                # Get "area" and "iscrowd" (default 0) from data
                data = target[5]

                if self._class_mapping is not None:
                    category_id = self._class_mapping[target[3].item()]
                else:
                    category_id = target[3].item()
                dict_annotation = {
                    "area": data.get("area", 0),
                    "iscrowd": data.get("iscrowd", 0),
                    "image_id": image_id,
                    "bbox": xywh,
                    "category_id": category_id,
                    "id": len(annotations),  # incrementally increase the id
                }
                annotations.append(dict_annotation)
        # Category list
        all_cat_ids = {annotation.get("category_id") for annotation in annotations}
        categories = [{"id": cat_id} for cat_id in all_cat_ids]
        # Create coco dictionary
        return {
            "images": images,
            "annotations": annotations,
            "categories": categories,
        }

    def _prepare_predictions(self, predictions):
        """Transform predictions into a list of predictions that can be used by the COCO
        evaluator."""
        coco_predictions = []
        for image_id, image_predictions in enumerate(predictions):
            if self._image_indices is not None:
                image_id = self._image_indices[image_id]
            for prediction in image_predictions:
                xyxy = prediction[0]  # or xyxy = prediction[0]; xyxy[2:4] -= xyxy[0:2]
                xywh = [xyxy[0], xyxy[1], xyxy[2] - xyxy[0], xyxy[3] - xyxy[1]]
                if self._class_mapping is not None:
                    category_id = self._class_mapping[prediction[3].item()]
                else:
                    category_id = prediction[3].item()
                dict_prediction = {
                    "image_id": image_id,
                    "bbox": xywh,
                    "score": prediction[2].item(),
                    "category_id": category_id,
                }
                coco_predictions.append(dict_prediction)
        return coco_predictions

    def compute(self) -> MeanAveragePrecisionResult:
        """
        Calculate Mean Average Precision based on predicted and ground-truth
        detections at different thresholds using the COCO evaluation metrics.
        Source: https://github.com/rafaelpadilla/review_object_detection_metrics

        Returns:
            (MeanAveragePrecisionResult): The Mean Average Precision result.
        """
        total_images_predictions = len(self._predictions_list)
        total_images_targets = len(self._targets_list)

        if total_images_predictions != total_images_targets:
            raise ValueError(
                f"The number of predictions ({total_images_predictions}) and"
                f" targets ({total_images_targets}) during the evaluation must be"
                " the same."
            )
        dict_targets = self._prepare_targets(self._targets_list)
        lst_predictions = self._prepare_predictions(self._predictions_list)
        # Create a coco object with the targets
        coco_gt = EvaluationDataset(targets=dict_targets)
        # Include the predictions to coco object
        coco_det = coco_gt.load_predictions(lst_predictions)
        # Create a coco evaluator with the predictions
        cocoEval = COCOEvaluator(coco_gt, coco_det)

        # Evaluate on all images
        cocoEval.evaluate()

        # Create MeanAveragePrecisionResult object for small objects
        mAP_small = MeanAveragePrecisionResult(
            metric_target=self._metric_target,
            is_class_agnostic=self._class_agnostic,
            mAP_scores=cocoEval.results["mAP_scores_small"],
            ap_per_class=cocoEval.results["ap_per_class_small"],
            iou_thresholds=cocoEval.params.iou_thrs,
            matched_classes=cocoEval.params.cat_ids,
        )
        # Create MeanAveragePrecisionResult object for medium objects
        mAP_medium = MeanAveragePrecisionResult(
            metric_target=self._metric_target,
            is_class_agnostic=self._class_agnostic,
            mAP_scores=cocoEval.results["mAP_scores_medium"],
            ap_per_class=cocoEval.results["ap_per_class_medium"],
            iou_thresholds=cocoEval.params.iou_thrs,
            matched_classes=cocoEval.params.cat_ids,
        )
        # Create MeanAveragePrecisionResult object for large objects
        mAP_large = MeanAveragePrecisionResult(
            metric_target=self._metric_target,
            is_class_agnostic=self._class_agnostic,
            mAP_scores=cocoEval.results["mAP_scores_large"],
            ap_per_class=cocoEval.results["ap_per_class_large"],
            iou_thresholds=cocoEval.params.iou_thrs,
            matched_classes=cocoEval.params.cat_ids,
        )

        # Create the final MeanAveragePrecisionResult object
        mAP_result = MeanAveragePrecisionResult(
            metric_target=self._metric_target,
            is_class_agnostic=self._class_agnostic,
            mAP_scores=cocoEval.results["mAP_scores_all_sizes"],
            ap_per_class=cocoEval.results["ap_per_class_all_sizes"],
            iou_thresholds=cocoEval.params.iou_thrs,
            matched_classes=cocoEval.params.cat_ids,
            small_objects=mAP_small,
            medium_objects=mAP_medium,
            large_objects=mAP_large,
        )
        return mAP_result

Functions

__init__(metric_target=MetricTarget.BOXES, class_agnostic=False, class_mapping=None, image_indices=None)

Initialize the Mean Average Precision metric.

Parameters:

Name Type Description Default
metric_target
MetricTarget

The type of detection data to use.

BOXES
class_agnostic
bool

Whether to treat all data as a single class.

False
class_mapping
Optional[Dict[int, int]]

A dictionary to map class IDs to

None
image_indices
Optional[List[int]]

The indices of the images to use.

None
Source code in supervision/metrics/mean_average_precision.py
def __init__(
    self,
    metric_target: MetricTarget = MetricTarget.BOXES,
    class_agnostic: bool = False,
    class_mapping: dict[int, int] | None = None,
    image_indices: list[int] | None = None,
):
    """
    Initialize the Mean Average Precision metric.

    Args:
        metric_target (MetricTarget): The type of detection data to use.
        class_agnostic (bool): Whether to treat all data as a single class.
        class_mapping (Optional[Dict[int, int]]): A dictionary to map class IDs to
        new IDs.
        image_indices (Optional[List[int]]): The indices of the images to use.
    """
    self._metric_target = metric_target
    self._class_agnostic = class_agnostic

    self._predictions_list: list[Detections] = []
    self._targets_list: list[Detections] = []
    self._class_mapping = class_mapping
    self._image_indices = image_indices

compute()

Calculate Mean Average Precision based on predicted and ground-truth detections at different thresholds using the COCO evaluation metrics. Source: https://github.com/rafaelpadilla/review_object_detection_metrics

Returns:

Type Description
MeanAveragePrecisionResult

The Mean Average Precision result.

Source code in supervision/metrics/mean_average_precision.py
def compute(self) -> MeanAveragePrecisionResult:
    """
    Calculate Mean Average Precision based on predicted and ground-truth
    detections at different thresholds using the COCO evaluation metrics.
    Source: https://github.com/rafaelpadilla/review_object_detection_metrics

    Returns:
        (MeanAveragePrecisionResult): The Mean Average Precision result.
    """
    total_images_predictions = len(self._predictions_list)
    total_images_targets = len(self._targets_list)

    if total_images_predictions != total_images_targets:
        raise ValueError(
            f"The number of predictions ({total_images_predictions}) and"
            f" targets ({total_images_targets}) during the evaluation must be"
            " the same."
        )
    dict_targets = self._prepare_targets(self._targets_list)
    lst_predictions = self._prepare_predictions(self._predictions_list)
    # Create a coco object with the targets
    coco_gt = EvaluationDataset(targets=dict_targets)
    # Include the predictions to coco object
    coco_det = coco_gt.load_predictions(lst_predictions)
    # Create a coco evaluator with the predictions
    cocoEval = COCOEvaluator(coco_gt, coco_det)

    # Evaluate on all images
    cocoEval.evaluate()

    # Create MeanAveragePrecisionResult object for small objects
    mAP_small = MeanAveragePrecisionResult(
        metric_target=self._metric_target,
        is_class_agnostic=self._class_agnostic,
        mAP_scores=cocoEval.results["mAP_scores_small"],
        ap_per_class=cocoEval.results["ap_per_class_small"],
        iou_thresholds=cocoEval.params.iou_thrs,
        matched_classes=cocoEval.params.cat_ids,
    )
    # Create MeanAveragePrecisionResult object for medium objects
    mAP_medium = MeanAveragePrecisionResult(
        metric_target=self._metric_target,
        is_class_agnostic=self._class_agnostic,
        mAP_scores=cocoEval.results["mAP_scores_medium"],
        ap_per_class=cocoEval.results["ap_per_class_medium"],
        iou_thresholds=cocoEval.params.iou_thrs,
        matched_classes=cocoEval.params.cat_ids,
    )
    # Create MeanAveragePrecisionResult object for large objects
    mAP_large = MeanAveragePrecisionResult(
        metric_target=self._metric_target,
        is_class_agnostic=self._class_agnostic,
        mAP_scores=cocoEval.results["mAP_scores_large"],
        ap_per_class=cocoEval.results["ap_per_class_large"],
        iou_thresholds=cocoEval.params.iou_thrs,
        matched_classes=cocoEval.params.cat_ids,
    )

    # Create the final MeanAveragePrecisionResult object
    mAP_result = MeanAveragePrecisionResult(
        metric_target=self._metric_target,
        is_class_agnostic=self._class_agnostic,
        mAP_scores=cocoEval.results["mAP_scores_all_sizes"],
        ap_per_class=cocoEval.results["ap_per_class_all_sizes"],
        iou_thresholds=cocoEval.params.iou_thrs,
        matched_classes=cocoEval.params.cat_ids,
        small_objects=mAP_small,
        medium_objects=mAP_medium,
        large_objects=mAP_large,
    )
    return mAP_result

reset()

Reset the metric to its initial state, clearing all stored data.

Source code in supervision/metrics/mean_average_precision.py
def reset(self) -> None:
    """
    Reset the metric to its initial state, clearing all stored data.
    """
    self._predictions_list = []
    self._targets_list = []

update(predictions, targets)

Add new predictions and targets to the metric, but do not compute the result.

Parameters:

Name Type Description Default
predictions
Union[Detections, List[Detections]]

The predicted detections.

required
targets
Union[Detections, List[Detections]]

The ground-truth detections.

required

Returns:

Type Description
MeanAveragePrecision

The updated metric instance.

Source code in supervision/metrics/mean_average_precision.py
def update(
    self,
    predictions: Detections | list[Detections],
    targets: Detections | list[Detections],
) -> MeanAveragePrecision:
    """
    Add new predictions and targets to the metric, but do not compute the result.

    Args:
        predictions (Union[Detections, List[Detections]]): The predicted detections.
        targets (Union[Detections, List[Detections]]): The ground-truth detections.

    Returns:
        (MeanAveragePrecision): The updated metric instance.
    """
    if not isinstance(predictions, list):
        predictions = [predictions]
    if not isinstance(targets, list):
        targets = [targets]

    if len(predictions) != len(targets):
        raise ValueError(
            f"The number of predictions ({len(predictions)}) and"
            f" targets ({len(targets)}) during the update must be the same."
        )

    if self._class_agnostic:
        predictions = deepcopy(predictions)
        targets = deepcopy(targets)

        for prediction in predictions:
            prediction.class_id[:] = -1
        for target in targets:
            target.class_id[:] = -1

    self._predictions_list.extend(predictions)
    self._targets_list.extend(targets)

    return self

supervision.metrics.mean_average_precision.MeanAveragePrecisionResult dataclass

The result of the Mean Average Precision calculation.

Defaults to 0 when no detections or targets are present.

Attributes:

Name Type Description
metric_target MetricTarget

the type of data used for the metric - boxes, masks or oriented bounding boxes.

class_agnostic bool

When computing class-agnostic results, class ID is set to -1.

mAP_map50_95 float

the mAP score at IoU thresholds from 0.5 to 0.95.

mAP_map50 float

the mAP score at IoU threshold of 0.5.

mAP_map75 float

the mAP score at IoU threshold of 0.75.

mAP_scores ndarray

the mAP scores at each IoU threshold. Shape: (num_iou_thresholds,)

ap_per_class ndarray

the average precision scores per class and IoU threshold. Shape: (num_target_classes, num_iou_thresholds)

iou_thresholds ndarray

the IoU thresholds used in the calculations.

matched_classes ndarray

the class IDs of all matched classes. Corresponds to the rows of ap_per_class.

small_objects Optional[MeanAveragePrecisionResult]

the mAP results for small objects (area < 32²).

medium_objects Optional[MeanAveragePrecisionResult]

the mAP results for medium objects (32² ≤ area < 96²).

large_objects Optional[MeanAveragePrecisionResult]

the mAP results for large objects (area ≥ 96²).

Source code in supervision/metrics/mean_average_precision.py
@dataclass
class MeanAveragePrecisionResult:
    """
    The result of the Mean Average Precision calculation.

    Defaults to `0` when no detections or targets are present.

    Attributes:
        metric_target (MetricTarget): the type of data used for the metric -
            boxes, masks or oriented bounding boxes.
        class_agnostic (bool): When computing class-agnostic results, class ID
            is set to `-1`.
        mAP_map50_95 (float): the mAP score at IoU thresholds from `0.5` to `0.95`.
        mAP_map50 (float): the mAP score at IoU threshold of `0.5`.
        mAP_map75 (float): the mAP score at IoU threshold of `0.75`.
        mAP_scores (np.ndarray): the mAP scores at each IoU threshold.
            Shape: `(num_iou_thresholds,)`
        ap_per_class (np.ndarray): the average precision scores per
            class and IoU threshold. Shape: `(num_target_classes, num_iou_thresholds)`
        iou_thresholds (np.ndarray): the IoU thresholds used in the calculations.
        matched_classes (np.ndarray): the class IDs of all matched classes.
            Corresponds to the rows of `ap_per_class`.
        small_objects (Optional[MeanAveragePrecisionResult]): the mAP results
            for small objects (area < 32²).
        medium_objects (Optional[MeanAveragePrecisionResult]): the mAP results
            for medium objects (32² ≤ area < 96²).
        large_objects (Optional[MeanAveragePrecisionResult]): the mAP results
            for large objects (area ≥ 96²).
    """

    metric_target: MetricTarget
    is_class_agnostic: bool

    @property
    def map50_95(self) -> float:
        return self.mAP_scores.mean()

    @property
    def map50(self) -> float:
        return self.mAP_scores[0]

    @property
    def map75(self) -> float:
        return self.mAP_scores[5]

    mAP_scores: np.ndarray
    ap_per_class: np.ndarray
    iou_thresholds: np.ndarray
    matched_classes: np.ndarray
    small_objects: MeanAveragePrecisionResult | None = None
    medium_objects: MeanAveragePrecisionResult | None = None
    large_objects: MeanAveragePrecisionResult | None = None

    def __str__(self) -> str:
        """
        Formats the evaluation output metrics to match the structure used by pycocotools

        Example:
           ```python
           print(map_result)
           # MeanAveragePrecisionResult:
           Average Precision (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.464
           Average Precision (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.637
           Average Precision (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.203
           Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.284
           Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.497
           Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.629
            ```
        """
        return (
            f"Average Precision (AP) @[ IoU=0.50:0.95 | area=   all | "
            f"maxDets=100 ] = {self.map50_95:.3f}\n"
            f"Average Precision (AP) @[ IoU=0.50      | area=   all | "
            f"maxDets=100 ] = {self.map50:.3f}\n"
            f"Average Precision (AP) @[ IoU=0.75      | area=   all | "
            f"maxDets=100 ] = {self.map75:.3f}\n"
            f"Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] "
            f"= {self.small_objects.map50_95:.3f}\n"
            f"Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] \
                = {self.medium_objects.map50_95:.3f}\n"
            f"Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] \
                = {self.large_objects.map50_95:.3f}"
        )

    def to_pandas(self) -> pd.DataFrame:
        """
        Convert the result to a pandas DataFrame.

        Returns:
            (pd.DataFrame): The result as a DataFrame.
        """
        ensure_pandas_installed()
        import pandas as pd

        pandas_data = {
            "mAP@50:95": self.map50_95,
            "mAP@50": self.map50,
            "mAP@75": self.map75,
        }

        if self.small_objects is not None:
            small_objects_df = self.small_objects.to_pandas()
            for key, value in small_objects_df.items():
                pandas_data[f"small_objects_{key}"] = value
        if self.medium_objects is not None:
            medium_objects_df = self.medium_objects.to_pandas()
            for key, value in medium_objects_df.items():
                pandas_data[f"medium_objects_{key}"] = value
        if self.large_objects is not None:
            large_objects_df = self.large_objects.to_pandas()
            for key, value in large_objects_df.items():
                pandas_data[f"large_objects_{key}"] = value

        # Average precisions are currently not included in the DataFrame.
        return pd.DataFrame(
            pandas_data,
            index=[0],
        )

    def plot(self):
        """
        Plot the mAP results.

        ![example_plot](\
            https://media.roboflow.com/supervision-docs/metrics/mAP_plot_example.png\
            ){ align=center width="800" }
        """

        labels = ["mAP@50:95", "mAP@50", "mAP@75"]
        values = [self.map50_95, self.map50, self.map75]
        colors = [LEGACY_COLOR_PALETTE[0]] * 3

        if self.small_objects is not None:
            labels += ["Small: mAP@50:95", "Small: mAP@50", "Small: mAP@75"]
            values += [
                self.small_objects.map50_95,
                self.small_objects.map50,
                self.small_objects.map75,
            ]
            colors += [LEGACY_COLOR_PALETTE[3]] * 3

        if self.medium_objects is not None:
            labels += ["Medium: mAP@50:95", "Medium: mAP@50", "Medium: mAP@75"]
            values += [
                self.medium_objects.map50_95,
                self.medium_objects.map50,
                self.medium_objects.map75,
            ]
            colors += [LEGACY_COLOR_PALETTE[2]] * 3

        if self.large_objects is not None:
            labels += ["Large: mAP@50:95", "Large: mAP@50", "Large: mAP@75"]
            values += [
                self.large_objects.map50_95,
                self.large_objects.map50,
                self.large_objects.map75,
            ]
            colors += [LEGACY_COLOR_PALETTE[4]] * 3

        plt.rcParams["font.family"] = "monospace"

        _, ax = plt.subplots(figsize=(10, 6))
        ax.set_ylim(0, 1)
        ax.set_ylabel("Value", fontweight="bold")
        ax.set_title("Mean Average Precision", fontweight="bold")

        x_positions = range(len(labels))
        bars = ax.bar(x_positions, values, color=colors, align="center")

        ax.set_xticks(x_positions)
        ax.set_xticklabels(labels, rotation=45, ha="right")

        for bar in bars:
            y_value = bar.get_height()
            ax.text(
                bar.get_x() + bar.get_width() / 2,
                y_value + 0.02,
                f"{y_value:.2f}",
                ha="center",
                va="bottom",
            )

        plt.rcParams["font.family"] = "sans-serif"

        plt.tight_layout()
        plt.show()

Functions

__str__()

Formats the evaluation output metrics to match the structure used by pycocotools

Example

```python print(map_result)

MeanAveragePrecisionResult:

Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.464 Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.637 Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.203 Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.284 Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.497 Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.629 ```

Source code in supervision/metrics/mean_average_precision.py
def __str__(self) -> str:
    """
    Formats the evaluation output metrics to match the structure used by pycocotools

    Example:
       ```python
       print(map_result)
       # MeanAveragePrecisionResult:
       Average Precision (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.464
       Average Precision (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.637
       Average Precision (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.203
       Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.284
       Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.497
       Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.629
        ```
    """
    return (
        f"Average Precision (AP) @[ IoU=0.50:0.95 | area=   all | "
        f"maxDets=100 ] = {self.map50_95:.3f}\n"
        f"Average Precision (AP) @[ IoU=0.50      | area=   all | "
        f"maxDets=100 ] = {self.map50:.3f}\n"
        f"Average Precision (AP) @[ IoU=0.75      | area=   all | "
        f"maxDets=100 ] = {self.map75:.3f}\n"
        f"Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] "
        f"= {self.small_objects.map50_95:.3f}\n"
        f"Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] \
            = {self.medium_objects.map50_95:.3f}\n"
        f"Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] \
            = {self.large_objects.map50_95:.3f}"
    )

plot()

Plot the mAP results.

example_plot

Source code in supervision/metrics/mean_average_precision.py
def plot(self):
    """
    Plot the mAP results.

    ![example_plot](\
        https://media.roboflow.com/supervision-docs/metrics/mAP_plot_example.png\
        ){ align=center width="800" }
    """

    labels = ["mAP@50:95", "mAP@50", "mAP@75"]
    values = [self.map50_95, self.map50, self.map75]
    colors = [LEGACY_COLOR_PALETTE[0]] * 3

    if self.small_objects is not None:
        labels += ["Small: mAP@50:95", "Small: mAP@50", "Small: mAP@75"]
        values += [
            self.small_objects.map50_95,
            self.small_objects.map50,
            self.small_objects.map75,
        ]
        colors += [LEGACY_COLOR_PALETTE[3]] * 3

    if self.medium_objects is not None:
        labels += ["Medium: mAP@50:95", "Medium: mAP@50", "Medium: mAP@75"]
        values += [
            self.medium_objects.map50_95,
            self.medium_objects.map50,
            self.medium_objects.map75,
        ]
        colors += [LEGACY_COLOR_PALETTE[2]] * 3

    if self.large_objects is not None:
        labels += ["Large: mAP@50:95", "Large: mAP@50", "Large: mAP@75"]
        values += [
            self.large_objects.map50_95,
            self.large_objects.map50,
            self.large_objects.map75,
        ]
        colors += [LEGACY_COLOR_PALETTE[4]] * 3

    plt.rcParams["font.family"] = "monospace"

    _, ax = plt.subplots(figsize=(10, 6))
    ax.set_ylim(0, 1)
    ax.set_ylabel("Value", fontweight="bold")
    ax.set_title("Mean Average Precision", fontweight="bold")

    x_positions = range(len(labels))
    bars = ax.bar(x_positions, values, color=colors, align="center")

    ax.set_xticks(x_positions)
    ax.set_xticklabels(labels, rotation=45, ha="right")

    for bar in bars:
        y_value = bar.get_height()
        ax.text(
            bar.get_x() + bar.get_width() / 2,
            y_value + 0.02,
            f"{y_value:.2f}",
            ha="center",
            va="bottom",
        )

    plt.rcParams["font.family"] = "sans-serif"

    plt.tight_layout()
    plt.show()

to_pandas()

Convert the result to a pandas DataFrame.

Returns:

Type Description
DataFrame

The result as a DataFrame.

Source code in supervision/metrics/mean_average_precision.py
def to_pandas(self) -> pd.DataFrame:
    """
    Convert the result to a pandas DataFrame.

    Returns:
        (pd.DataFrame): The result as a DataFrame.
    """
    ensure_pandas_installed()
    import pandas as pd

    pandas_data = {
        "mAP@50:95": self.map50_95,
        "mAP@50": self.map50,
        "mAP@75": self.map75,
    }

    if self.small_objects is not None:
        small_objects_df = self.small_objects.to_pandas()
        for key, value in small_objects_df.items():
            pandas_data[f"small_objects_{key}"] = value
    if self.medium_objects is not None:
        medium_objects_df = self.medium_objects.to_pandas()
        for key, value in medium_objects_df.items():
            pandas_data[f"medium_objects_{key}"] = value
    if self.large_objects is not None:
        large_objects_df = self.large_objects.to_pandas()
        for key, value in large_objects_df.items():
            pandas_data[f"large_objects_{key}"] = value

    # Average precisions are currently not included in the DataFrame.
    return pd.DataFrame(
        pandas_data,
        index=[0],
    )

supervision.dataset.formats.coco.get_coco_class_index_mapping(annotations_path)

Generates a mapping from sequential class indices to original COCO class ids.

This function is essential when working with models that expect class ids to be zero-indexed and sequential (0 to 79), as opposed to the original COCO dataset where category ids are non-contiguous ranging from 1 to 90 but skipping some ids.

Use Cases
  • Evaluating models trained with COCO-style annotations where class ids are sequential ranging from 0 to 79.
  • Ensuring consistent class indexing across training, inference and evaluation, when using different tools or datasets with COCO format.
  • Reproducing results from models that assume sequential class ids (0 to 79).
How it Works
  • Reads the COCO annotation file in its original format (annotations_path).
  • Extracts and sorts all class names by their original COCO id (1 to 90).
  • Builds a mapping from COCO class ids (not sequential with skipped ids) to new class ids (sequential ranging from 0 to 79).
  • Returns a dictionary mapping: {new_class_id: original_COCO_class_id}.

Parameters:

Name Type Description Default

annotations_path

str

Path to COCO JSON annotations file

required

Returns:

Type Description
dict[int, int]

Dict[int, int]: A mapping from new class id (sequential ranging from 0 to 79)

dict[int, int]

to original COCO class id (1 to 90 with skipped ids).

Source code in supervision/dataset/formats/coco.py
def get_coco_class_index_mapping(annotations_path: str) -> dict[int, int]:
    """
    Generates a mapping from sequential class indices to original COCO class ids.

    This function is essential when working with models that expect class ids to be
    zero-indexed and sequential (0 to 79), as opposed to the original COCO
    dataset where category ids are non-contiguous ranging from 1 to 90 but skipping some
    ids.

    Use Cases:
        - Evaluating models trained with COCO-style annotations where class ids
          are sequential ranging from 0 to 79.
        - Ensuring consistent class indexing across training, inference and evaluation,
          when using different tools or datasets with COCO format.
        - Reproducing results from models that assume sequential class ids (0 to 79).

    How it Works:
        - Reads the COCO annotation file in its original format (`annotations_path`).
        - Extracts and sorts all class names by their original COCO id (1 to 90).
        - Builds a mapping from COCO class ids (not sequential with skipped ids) to
          new class ids (sequential ranging from 0 to 79).
        - Returns a dictionary mapping: `{new_class_id: original_COCO_class_id}`.

    Args:
        annotations_path (str): Path to COCO JSON annotations file
        (e.g., `instances_val2017.json`).

    Returns:
        Dict[int, int]: A mapping from new class id (sequential ranging from 0 to 79)
        to original COCO class id (1 to 90 with skipped ids).
    """
    coco_data = read_json_file(annotations_path)
    classes = coco_categories_to_classes(coco_categories=coco_data["categories"])
    class_mapping = build_coco_class_index_mapping(
        coco_categories=coco_data["categories"], target_classes=classes
    )
    return {v: k for k, v in class_mapping.items()}

Comments