CoolFace
Apppublic

coreml-community/ControlNet-v1-1-Annotators-cpu

sourceHugging Facemitupdated 2y agoView on Hugging Face
15likes
coco_evaluation.py723 linesDownload Raw Back to evaluation
1# Copyright (c) Facebook, Inc. and its affiliates.2import contextlib3import copy4import io5import itertools6import json7import logging8import numpy as np9import os10import pickle11from collections import OrderedDict12import pycocotools.mask as mask_util13import torch14from pycocotools.coco import COCO15from pycocotools.cocoeval import COCOeval16from tabulate import tabulate17 18import annotator.oneformer.detectron2.utils.comm as comm19from annotator.oneformer.detectron2.config import CfgNode20from annotator.oneformer.detectron2.data import MetadataCatalog21from annotator.oneformer.detectron2.data.datasets.coco import convert_to_coco_json22from annotator.oneformer.detectron2.structures import Boxes, BoxMode, pairwise_iou23from annotator.oneformer.detectron2.utils.file_io import PathManager24from annotator.oneformer.detectron2.utils.logger import create_small_table25 26from .evaluator import DatasetEvaluator27 28try:29    from annotator.oneformer.detectron2.evaluation.fast_eval_api import COCOeval_opt30except ImportError:31    COCOeval_opt = COCOeval32 33 34class COCOEvaluator(DatasetEvaluator):35    """36    Evaluate AR for object proposals, AP for instance detection/segmentation, AP37    for keypoint detection outputs using COCO's metrics.38    See http://cocodataset.org/#detection-eval and39    http://cocodataset.org/#keypoints-eval to understand its metrics.40    The metrics range from 0 to 100 (instead of 0 to 1), where a -1 or NaN means41    the metric cannot be computed (e.g. due to no predictions made).42 43    In addition to COCO, this evaluator is able to support any bounding box detection,44    instance segmentation, or keypoint detection dataset.45    """46 47    def __init__(48        self,49        dataset_name,50        tasks=None,51        distributed=True,52        output_dir=None,53        *,54        max_dets_per_image=None,55        use_fast_impl=True,56        kpt_oks_sigmas=(),57        allow_cached_coco=True,58    ):59        """60        Args:61            dataset_name (str): name of the dataset to be evaluated.62                It must have either the following corresponding metadata:63 64                    "json_file": the path to the COCO format annotation65 66                Or it must be in detectron2's standard dataset format67                so it can be converted to COCO format automatically.68            tasks (tuple[str]): tasks that can be evaluated under the given69                configuration. A task is one of "bbox", "segm", "keypoints".70                By default, will infer this automatically from predictions.71            distributed (True): if True, will collect results from all ranks and run evaluation72                in the main process.73                Otherwise, will only evaluate the results in the current process.74            output_dir (str): optional, an output directory to dump all75                results predicted on the dataset. The dump contains two files:76 77                1. "instances_predictions.pth" a file that can be loaded with `torch.load` and78                   contains all the results in the format they are produced by the model.79                2. "coco_instances_results.json" a json file in COCO's result format.80            max_dets_per_image (int): limit on the maximum number of detections per image.81                By default in COCO, this limit is to 100, but this can be customized82                to be greater, as is needed in evaluation metrics AP fixed and AP pool83                (see https://arxiv.org/pdf/2102.01066.pdf)84                This doesn't affect keypoint evaluation.85            use_fast_impl (bool): use a fast but **unofficial** implementation to compute AP.86                Although the results should be very close to the official implementation in COCO87                API, it is still recommended to compute results with the official API for use in88                papers. The faster implementation also uses more RAM.89            kpt_oks_sigmas (list[float]): The sigmas used to calculate keypoint OKS.90                See http://cocodataset.org/#keypoints-eval91                When empty, it will use the defaults in COCO.92                Otherwise it should be the same length as ROI_KEYPOINT_HEAD.NUM_KEYPOINTS.93            allow_cached_coco (bool): Whether to use cached coco json from previous validation94                runs. You should set this to False if you need to use different validation data.95                Defaults to True.96        """97        self._logger = logging.getLogger(__name__)98        self._distributed = distributed99        self._output_dir = output_dir100 101        if use_fast_impl and (COCOeval_opt is COCOeval):102            self._logger.info("Fast COCO eval is not built. Falling back to official COCO eval.")103            use_fast_impl = False104        self._use_fast_impl = use_fast_impl105 106        # COCOeval requires the limit on the number of detections per image (maxDets) to be a list107        # with at least 3 elements. The default maxDets in COCOeval is [1, 10, 100], in which the108        # 3rd element (100) is used as the limit on the number of detections per image when109        # evaluating AP. COCOEvaluator expects an integer for max_dets_per_image, so for COCOeval,110        # we reformat max_dets_per_image into [1, 10, max_dets_per_image], based on the defaults.111        if max_dets_per_image is None:112            max_dets_per_image = [1, 10, 100]113        else:114            max_dets_per_image = [1, 10, max_dets_per_image]115        self._max_dets_per_image = max_dets_per_image116 117        if tasks is not None and isinstance(tasks, CfgNode):118            kpt_oks_sigmas = (119                tasks.TEST.KEYPOINT_OKS_SIGMAS if not kpt_oks_sigmas else kpt_oks_sigmas120            )121            self._logger.warn(122                "COCO Evaluator instantiated using config, this is deprecated behavior."123                " Please pass in explicit arguments instead."124            )125            self._tasks = None  # Infering it from predictions should be better126        else:127            self._tasks = tasks128 129        self._cpu_device = torch.device("cpu")130 131        self._metadata = MetadataCatalog.get(dataset_name)132        if not hasattr(self._metadata, "json_file"):133            if output_dir is None:134                raise ValueError(135                    "output_dir must be provided to COCOEvaluator "136                    "for datasets not in COCO format."137                )138            self._logger.info(f"Trying to convert '{dataset_name}' to COCO format ...")139 140            cache_path = os.path.join(output_dir, f"{dataset_name}_coco_format.json")141            self._metadata.json_file = cache_path142            convert_to_coco_json(dataset_name, cache_path, allow_cached=allow_cached_coco)143 144        json_file = PathManager.get_local_path(self._metadata.json_file)145        with contextlib.redirect_stdout(io.StringIO()):146            self._coco_api = COCO(json_file)147 148        # Test set json files do not contain annotations (evaluation must be149        # performed using the COCO evaluation server).150        self._do_evaluation = "annotations" in self._coco_api.dataset151        if self._do_evaluation:152            self._kpt_oks_sigmas = kpt_oks_sigmas153 154    def reset(self):155        self._predictions = []156 157    def process(self, inputs, outputs):158        """159        Args:160            inputs: the inputs to a COCO model (e.g., GeneralizedRCNN).161                It is a list of dict. Each dict corresponds to an image and162                contains keys like "height", "width", "file_name", "image_id".163            outputs: the outputs of a COCO model. It is a list of dicts with key164                "instances" that contains :class:`Instances`.165        """166        for input, output in zip(inputs, outputs):167            prediction = {"image_id": input["image_id"]}168 169            if "instances" in output:170                instances = output["instances"].to(self._cpu_device)171                prediction["instances"] = instances_to_coco_json(instances, input["image_id"])172            if "proposals" in output:173                prediction["proposals"] = output["proposals"].to(self._cpu_device)174            if len(prediction) > 1:175                self._predictions.append(prediction)176 177    def evaluate(self, img_ids=None):178        """179        Args:180            img_ids: a list of image IDs to evaluate on. Default to None for the whole dataset181        """182        if self._distributed:183            comm.synchronize()184            predictions = comm.gather(self._predictions, dst=0)185            predictions = list(itertools.chain(*predictions))186 187            if not comm.is_main_process():188                return {}189        else:190            predictions = self._predictions191 192        if len(predictions) == 0:193            self._logger.warning("[COCOEvaluator] Did not receive valid predictions.")194            return {}195 196        if self._output_dir:197            PathManager.mkdirs(self._output_dir)198            file_path = os.path.join(self._output_dir, "instances_predictions.pth")199            with PathManager.open(file_path, "wb") as f:200                torch.save(predictions, f)201 202        self._results = OrderedDict()203        if "proposals" in predictions[0]:204            self._eval_box_proposals(predictions)205        if "instances" in predictions[0]:206            self._eval_predictions(predictions, img_ids=img_ids)207        # Copy so the caller can do whatever with results208        return copy.deepcopy(self._results)209 210    def _tasks_from_predictions(self, predictions):211        """212        Get COCO API "tasks" (i.e. iou_type) from COCO-format predictions.213        """214        tasks = {"bbox"}215        for pred in predictions:216            if "segmentation" in pred:217                tasks.add("segm")218            if "keypoints" in pred:219                tasks.add("keypoints")220        return sorted(tasks)221 222    def _eval_predictions(self, predictions, img_ids=None):223        """224        Evaluate predictions. Fill self._results with the metrics of the tasks.225        """226        self._logger.info("Preparing results for COCO format ...")227        coco_results = list(itertools.chain(*[x["instances"] for x in predictions]))228        tasks = self._tasks or self._tasks_from_predictions(coco_results)229 230        # unmap the category ids for COCO231        if hasattr(self._metadata, "thing_dataset_id_to_contiguous_id"):232            dataset_id_to_contiguous_id = self._metadata.thing_dataset_id_to_contiguous_id233            all_contiguous_ids = list(dataset_id_to_contiguous_id.values())234            num_classes = len(all_contiguous_ids)235            assert min(all_contiguous_ids) == 0 and max(all_contiguous_ids) == num_classes - 1236 237            reverse_id_mapping = {v: k for k, v in dataset_id_to_contiguous_id.items()}238            for result in coco_results:239                category_id = result["category_id"]240                assert category_id < num_classes, (241                    f"A prediction has class={category_id}, "242                    f"but the dataset only has {num_classes} classes and "243                    f"predicted class id should be in [0, {num_classes - 1}]."244                )245                result["category_id"] = reverse_id_mapping[category_id]246 247        if self._output_dir:248            file_path = os.path.join(self._output_dir, "coco_instances_results.json")249            self._logger.info("Saving results to {}".format(file_path))250            with PathManager.open(file_path, "w") as f:251                f.write(json.dumps(coco_results))252                f.flush()253 254        if not self._do_evaluation:255            self._logger.info("Annotations are not available for evaluation.")256            return257 258        self._logger.info(259            "Evaluating predictions with {} COCO API...".format(260                "unofficial" if self._use_fast_impl else "official"261            )262        )263        for task in sorted(tasks):264            assert task in {"bbox", "segm", "keypoints"}, f"Got unknown task: {task}!"265            coco_eval = (266                _evaluate_predictions_on_coco(267                    self._coco_api,268                    coco_results,269                    task,270                    kpt_oks_sigmas=self._kpt_oks_sigmas,271                    cocoeval_fn=COCOeval_opt if self._use_fast_impl else COCOeval,272                    img_ids=img_ids,273                    max_dets_per_image=self._max_dets_per_image,274                )275                if len(coco_results) > 0276                else None  # cocoapi does not handle empty results very well277            )278 279            res = self._derive_coco_results(280                coco_eval, task, class_names=self._metadata.get("thing_classes")281            )282            self._results[task] = res283 284    def _eval_box_proposals(self, predictions):285        """286        Evaluate the box proposals in predictions.287        Fill self._results with the metrics for "box_proposals" task.288        """289        if self._output_dir:290            # Saving generated box proposals to file.291            # Predicted box_proposals are in XYXY_ABS mode.292            bbox_mode = BoxMode.XYXY_ABS.value293            ids, boxes, objectness_logits = [], [], []294            for prediction in predictions:295                ids.append(prediction["image_id"])296                boxes.append(prediction["proposals"].proposal_boxes.tensor.numpy())297                objectness_logits.append(prediction["proposals"].objectness_logits.numpy())298 299            proposal_data = {300                "boxes": boxes,301                "objectness_logits": objectness_logits,302                "ids": ids,303                "bbox_mode": bbox_mode,304            }305            with PathManager.open(os.path.join(self._output_dir, "box_proposals.pkl"), "wb") as f:306                pickle.dump(proposal_data, f)307 308        if not self._do_evaluation:309            self._logger.info("Annotations are not available for evaluation.")310            return311 312        self._logger.info("Evaluating bbox proposals ...")313        res = {}314        areas = {"all": "", "small": "s", "medium": "m", "large": "l"}315        for limit in [100, 1000]:316            for area, suffix in areas.items():317                stats = _evaluate_box_proposals(predictions, self._coco_api, area=area, limit=limit)318                key = "AR{}@{:d}".format(suffix, limit)319                res[key] = float(stats["ar"].item() * 100)320        self._logger.info("Proposal metrics: \n" + create_small_table(res))321        self._results["box_proposals"] = res322 323    def _derive_coco_results(self, coco_eval, iou_type, class_names=None):324        """325        Derive the desired score numbers from summarized COCOeval.326 327        Args:328            coco_eval (None or COCOEval): None represents no predictions from model.329            iou_type (str):330            class_names (None or list[str]): if provided, will use it to predict331                per-category AP.332 333        Returns:334            a dict of {metric name: score}335        """336 337        metrics = {338            "bbox": ["AP", "AP50", "AP75", "APs", "APm", "APl"],339            "segm": ["AP", "AP50", "AP75", "APs", "APm", "APl"],340            "keypoints": ["AP", "AP50", "AP75", "APm", "APl"],341        }[iou_type]342 343        if coco_eval is None:344            self._logger.warn("No predictions from the model!")345            return {metric: float("nan") for metric in metrics}346 347        # the standard metrics348        results = {349            metric: float(coco_eval.stats[idx] * 100 if coco_eval.stats[idx] >= 0 else "nan")350            for idx, metric in enumerate(metrics)351        }352        self._logger.info(353            "Evaluation results for {}: \n".format(iou_type) + create_small_table(results)354        )355        if not np.isfinite(sum(results.values())):356            self._logger.info("Some metrics cannot be computed and is shown as NaN.")357 358        if class_names is None or len(class_names) <= 1:359            return results360        # Compute per-category AP361        # from https://github.com/facebookresearch/Detectron/blob/a6a835f5b8208c45d0dce217ce9bbda915f44df7/detectron/datasets/json_dataset_evaluator.py#L222-L252 # noqa362        precisions = coco_eval.eval["precision"]363        # precision has dims (iou, recall, cls, area range, max dets)364        assert len(class_names) == precisions.shape[2]365 366        results_per_category = []367        for idx, name in enumerate(class_names):368            # area range index 0: all area ranges369            # max dets index -1: typically 100 per image370            precision = precisions[:, :, idx, 0, -1]371            precision = precision[precision > -1]372            ap = np.mean(precision) if precision.size else float("nan")373            results_per_category.append(("{}".format(name), float(ap * 100)))374 375        # tabulate it376        N_COLS = min(6, len(results_per_category) * 2)377        results_flatten = list(itertools.chain(*results_per_category))378        results_2d = itertools.zip_longest(*[results_flatten[i::N_COLS] for i in range(N_COLS)])379        table = tabulate(380            results_2d,381            tablefmt="pipe",382            floatfmt=".3f",383            headers=["category", "AP"] * (N_COLS // 2),384            numalign="left",385        )386        self._logger.info("Per-category {} AP: \n".format(iou_type) + table)387 388        results.update({"AP-" + name: ap for name, ap in results_per_category})389        return results390 391 392def instances_to_coco_json(instances, img_id):393    """394    Dump an "Instances" object to a COCO-format json that's used for evaluation.395 396    Args:397        instances (Instances):398        img_id (int): the image id399 400    Returns:401        list[dict]: list of json annotations in COCO format.402    """403    num_instance = len(instances)404    if num_instance == 0:405        return []406 407    boxes = instances.pred_boxes.tensor.numpy()408    boxes = BoxMode.convert(boxes, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)409    boxes = boxes.tolist()410    scores = instances.scores.tolist()411    classes = instances.pred_classes.tolist()412 413    has_mask = instances.has("pred_masks")414    if has_mask:415        # use RLE to encode the masks, because they are too large and takes memory416        # since this evaluator stores outputs of the entire dataset417        rles = [418            mask_util.encode(np.array(mask[:, :, None], order="F", dtype="uint8"))[0]419            for mask in instances.pred_masks420        ]421        for rle in rles:422            # "counts" is an array encoded by mask_util as a byte-stream. Python3's423            # json writer which always produces strings cannot serialize a bytestream424            # unless you decode it. Thankfully, utf-8 works out (which is also what425            # the pycocotools/_mask.pyx does).426            rle["counts"] = rle["counts"].decode("utf-8")427 428    has_keypoints = instances.has("pred_keypoints")429    if has_keypoints:430        keypoints = instances.pred_keypoints431 432    results = []433    for k in range(num_instance):434        result = {435            "image_id": img_id,436            "category_id": classes[k],437            "bbox": boxes[k],438            "score": scores[k],439        }440        if has_mask:441            result["segmentation"] = rles[k]442        if has_keypoints:443            # In COCO annotations,444            # keypoints coordinates are pixel indices.445            # However our predictions are floating point coordinates.446            # Therefore we subtract 0.5 to be consistent with the annotation format.447            # This is the inverse of data loading logic in `datasets/coco.py`.448            keypoints[k][:, :2] -= 0.5449            result["keypoints"] = keypoints[k].flatten().tolist()450        results.append(result)451    return results452 453 454# inspired from Detectron:455# https://github.com/facebookresearch/Detectron/blob/a6a835f5b8208c45d0dce217ce9bbda915f44df7/detectron/datasets/json_dataset_evaluator.py#L255 # noqa456def _evaluate_box_proposals(dataset_predictions, coco_api, thresholds=None, area="all", limit=None):457    """458    Evaluate detection proposal recall metrics. This function is a much459    faster alternative to the official COCO API recall evaluation code. However,460    it produces slightly different results.461    """462    # Record max overlap value for each gt box463    # Return vector of overlap values464    areas = {465        "all": 0,466        "small": 1,467        "medium": 2,468        "large": 3,469        "96-128": 4,470        "128-256": 5,471        "256-512": 6,472        "512-inf": 7,473    }474    area_ranges = [475        [0**2, 1e5**2],  # all476        [0**2, 32**2],  # small477        [32**2, 96**2],  # medium478        [96**2, 1e5**2],  # large479        [96**2, 128**2],  # 96-128480        [128**2, 256**2],  # 128-256481        [256**2, 512**2],  # 256-512482        [512**2, 1e5**2],483    ]  # 512-inf484    assert area in areas, "Unknown area range: {}".format(area)485    area_range = area_ranges[areas[area]]486    gt_overlaps = []487    num_pos = 0488 489    for prediction_dict in dataset_predictions:490        predictions = prediction_dict["proposals"]491 492        # sort predictions in descending order493        # TODO maybe remove this and make it explicit in the documentation494        inds = predictions.objectness_logits.sort(descending=True)[1]495        predictions = predictions[inds]496 497        ann_ids = coco_api.getAnnIds(imgIds=prediction_dict["image_id"])498        anno = coco_api.loadAnns(ann_ids)499        gt_boxes = [500            BoxMode.convert(obj["bbox"], BoxMode.XYWH_ABS, BoxMode.XYXY_ABS)501            for obj in anno502            if obj["iscrowd"] == 0503        ]504        gt_boxes = torch.as_tensor(gt_boxes).reshape(-1, 4)  # guard against no boxes505        gt_boxes = Boxes(gt_boxes)506        gt_areas = torch.as_tensor([obj["area"] for obj in anno if obj["iscrowd"] == 0])507 508        if len(gt_boxes) == 0 or len(predictions) == 0:509            continue510 511        valid_gt_inds = (gt_areas >= area_range[0]) & (gt_areas <= area_range[1])512        gt_boxes = gt_boxes[valid_gt_inds]513 514        num_pos += len(gt_boxes)515 516        if len(gt_boxes) == 0:517            continue518 519        if limit is not None and len(predictions) > limit:520            predictions = predictions[:limit]521 522        overlaps = pairwise_iou(predictions.proposal_boxes, gt_boxes)523 524        _gt_overlaps = torch.zeros(len(gt_boxes))525        for j in range(min(len(predictions), len(gt_boxes))):526            # find which proposal box maximally covers each gt box527            # and get the iou amount of coverage for each gt box528            max_overlaps, argmax_overlaps = overlaps.max(dim=0)529 530            # find which gt box is 'best' covered (i.e. 'best' = most iou)531            gt_ovr, gt_ind = max_overlaps.max(dim=0)532            assert gt_ovr >= 0533            # find the proposal box that covers the best covered gt box534            box_ind = argmax_overlaps[gt_ind]535            # record the iou coverage of this gt box536            _gt_overlaps[j] = overlaps[box_ind, gt_ind]537            assert _gt_overlaps[j] == gt_ovr538            # mark the proposal box and the gt box as used539            overlaps[box_ind, :] = -1540            overlaps[:, gt_ind] = -1541 542        # append recorded iou coverage level543        gt_overlaps.append(_gt_overlaps)544    gt_overlaps = (545        torch.cat(gt_overlaps, dim=0) if len(gt_overlaps) else torch.zeros(0, dtype=torch.float32)546    )547    gt_overlaps, _ = torch.sort(gt_overlaps)548 549    if thresholds is None:550        step = 0.05551        thresholds = torch.arange(0.5, 0.95 + 1e-5, step, dtype=torch.float32)552    recalls = torch.zeros_like(thresholds)553    # compute recall for each iou threshold554    for i, t in enumerate(thresholds):555        recalls[i] = (gt_overlaps >= t).float().sum() / float(num_pos)556    # ar = 2 * np.trapz(recalls, thresholds)557    ar = recalls.mean()558    return {559        "ar": ar,560        "recalls": recalls,561        "thresholds": thresholds,562        "gt_overlaps": gt_overlaps,563        "num_pos": num_pos,564    }565 566 567def _evaluate_predictions_on_coco(568    coco_gt,569    coco_results,570    iou_type,571    kpt_oks_sigmas=None,572    cocoeval_fn=COCOeval_opt,573    img_ids=None,574    max_dets_per_image=None,575):576    """577    Evaluate the coco results using COCOEval API.578    """579    assert len(coco_results) > 0580 581    if iou_type == "segm":582        coco_results = copy.deepcopy(coco_results)583        # When evaluating mask AP, if the results contain bbox, cocoapi will584        # use the box area as the area of the instance, instead of the mask area.585        # This leads to a different definition of small/medium/large.586        # We remove the bbox field to let mask AP use mask area.587        for c in coco_results:588            c.pop("bbox", None)589 590    coco_dt = coco_gt.loadRes(coco_results)591    coco_eval = cocoeval_fn(coco_gt, coco_dt, iou_type)592    # For COCO, the default max_dets_per_image is [1, 10, 100].593    if max_dets_per_image is None:594        max_dets_per_image = [1, 10, 100]  # Default from COCOEval595    else:596        assert (597            len(max_dets_per_image) >= 3598        ), "COCOeval requires maxDets (and max_dets_per_image) to have length at least 3"599        # In the case that user supplies a custom input for max_dets_per_image,600        # apply COCOevalMaxDets to evaluate AP with the custom input.601        if max_dets_per_image[2] != 100:602            coco_eval = COCOevalMaxDets(coco_gt, coco_dt, iou_type)603    if iou_type != "keypoints":604        coco_eval.params.maxDets = max_dets_per_image605 606    if img_ids is not None:607        coco_eval.params.imgIds = img_ids608 609    if iou_type == "keypoints":610        # Use the COCO default keypoint OKS sigmas unless overrides are specified611        if kpt_oks_sigmas:612            assert hasattr(coco_eval.params, "kpt_oks_sigmas"), "pycocotools is too old!"613            coco_eval.params.kpt_oks_sigmas = np.array(kpt_oks_sigmas)614        # COCOAPI requires every detection and every gt to have keypoints, so615        # we just take the first entry from both616        num_keypoints_dt = len(coco_results[0]["keypoints"]) // 3617        num_keypoints_gt = len(next(iter(coco_gt.anns.values()))["keypoints"]) // 3618        num_keypoints_oks = len(coco_eval.params.kpt_oks_sigmas)619        assert num_keypoints_oks == num_keypoints_dt == num_keypoints_gt, (620            f"[COCOEvaluator] Prediction contain {num_keypoints_dt} keypoints. "621            f"Ground truth contains {num_keypoints_gt} keypoints. "622            f"The length of cfg.TEST.KEYPOINT_OKS_SIGMAS is {num_keypoints_oks}. "623            "They have to agree with each other. For meaning of OKS, please refer to "624            "http://cocodataset.org/#keypoints-eval."625        )626 627    coco_eval.evaluate()628    coco_eval.accumulate()629    coco_eval.summarize()630 631    return coco_eval632 633 634class COCOevalMaxDets(COCOeval):635    """636    Modified version of COCOeval for evaluating AP with a custom637    maxDets (by default for COCO, maxDets is 100)638    """639 640    def summarize(self):641        """642        Compute and display summary metrics for evaluation results given643        a custom value for  max_dets_per_image644        """645 646        def _summarize(ap=1, iouThr=None, areaRng="all", maxDets=100):647            p = self.params648            iStr = " {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.3f}"649            titleStr = "Average Precision" if ap == 1 else "Average Recall"650            typeStr = "(AP)" if ap == 1 else "(AR)"651            iouStr = (652                "{:0.2f}:{:0.2f}".format(p.iouThrs[0], p.iouThrs[-1])653                if iouThr is None654                else "{:0.2f}".format(iouThr)655            )656 657            aind = [i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng]658            mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets]659            if ap == 1:660                # dimension of precision: [TxRxKxAxM]661                s = self.eval["precision"]662                # IoU663                if iouThr is not None:664                    t = np.where(iouThr == p.iouThrs)[0]665                    s = s[t]666                s = s[:, :, :, aind, mind]667            else:668                # dimension of recall: [TxKxAxM]669                s = self.eval["recall"]670                if iouThr is not None:671                    t = np.where(iouThr == p.iouThrs)[0]672                    s = s[t]673                s = s[:, :, aind, mind]674            if len(s[s > -1]) == 0:675                mean_s = -1676            else:677                mean_s = np.mean(s[s > -1])678            print(iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, mean_s))679            return mean_s680 681        def _summarizeDets():682            stats = np.zeros((12,))683            # Evaluate AP using the custom limit on maximum detections per image684            stats[0] = _summarize(1, maxDets=self.params.maxDets[2])685            stats[1] = _summarize(1, iouThr=0.5, maxDets=self.params.maxDets[2])686            stats[2] = _summarize(1, iouThr=0.75, maxDets=self.params.maxDets[2])687            stats[3] = _summarize(1, areaRng="small", maxDets=self.params.maxDets[2])688            stats[4] = _summarize(1, areaRng="medium", maxDets=self.params.maxDets[2])689            stats[5] = _summarize(1, areaRng="large", maxDets=self.params.maxDets[2])690            stats[6] = _summarize(0, maxDets=self.params.maxDets[0])691            stats[7] = _summarize(0, maxDets=self.params.maxDets[1])692            stats[8] = _summarize(0, maxDets=self.params.maxDets[2])693            stats[9] = _summarize(0, areaRng="small", maxDets=self.params.maxDets[2])694            stats[10] = _summarize(0, areaRng="medium", maxDets=self.params.maxDets[2])695            stats[11] = _summarize(0, areaRng="large", maxDets=self.params.maxDets[2])696            return stats697 698        def _summarizeKps():699            stats = np.zeros((10,))700            stats[0] = _summarize(1, maxDets=20)701            stats[1] = _summarize(1, maxDets=20, iouThr=0.5)702            stats[2] = _summarize(1, maxDets=20, iouThr=0.75)703            stats[3] = _summarize(1, maxDets=20, areaRng="medium")704            stats[4] = _summarize(1, maxDets=20, areaRng="large")705            stats[5] = _summarize(0, maxDets=20)706            stats[6] = _summarize(0, maxDets=20, iouThr=0.5)707            stats[7] = _summarize(0, maxDets=20, iouThr=0.75)708            stats[8] = _summarize(0, maxDets=20, areaRng="medium")709            stats[9] = _summarize(0, maxDets=20, areaRng="large")710            return stats711 712        if not self.eval:713            raise Exception("Please run accumulate() first")714        iouType = self.params.iouType715        if iouType == "segm" or iouType == "bbox":716            summarize = _summarizeDets717        elif iouType == "keypoints":718            summarize = _summarizeKps719        self.stats = summarize()720 721    def __str__(self):722        self.summarize()723