|
|
| import contextlib
|
| import copy
|
| import io
|
| import itertools
|
| import json
|
| import logging
|
| import numpy as np
|
| import os
|
| import pickle
|
| from collections import OrderedDict
|
| import pycocotools.mask as mask_util
|
| import torch
|
| from pycocotools.coco import COCO
|
| from pycocotools.cocoeval import COCOeval
|
| from tabulate import tabulate
|
|
|
| import detectron2.utils.comm as comm
|
| from detectron2.config import CfgNode
|
| from detectron2.data import MetadataCatalog
|
| from detectron2.data.datasets.coco import convert_to_coco_json
|
| from detectron2.structures import Boxes, BoxMode, pairwise_iou
|
| from detectron2.utils.file_io import PathManager
|
| from detectron2.utils.logger import create_small_table
|
|
|
| from .evaluator import DatasetEvaluator
|
|
|
| try:
|
| from detectron2.evaluation.fast_eval_api import COCOeval_opt
|
| except ImportError:
|
| COCOeval_opt = COCOeval
|
|
|
|
|
| class COCOEvaluator(DatasetEvaluator):
|
| """
|
| Evaluate AR for object proposals, AP for instance detection/segmentation, AP
|
| for keypoint detection outputs using COCO's metrics.
|
| See http://cocodataset.org/#detection-eval and
|
| http://cocodataset.org/#keypoints-eval to understand its metrics.
|
| The metrics range from 0 to 100 (instead of 0 to 1), where a -1 or NaN means
|
| the metric cannot be computed (e.g. due to no predictions made).
|
|
|
| In addition to COCO, this evaluator is able to support any bounding box detection,
|
| instance segmentation, or keypoint detection dataset.
|
| """
|
|
|
| def __init__(
|
| self,
|
| dataset_name,
|
| tasks=None,
|
| distributed=True,
|
| output_dir=None,
|
| *,
|
| max_dets_per_image=None,
|
| use_fast_impl=True,
|
| kpt_oks_sigmas=(),
|
| allow_cached_coco=True,
|
| ):
|
| """
|
| Args:
|
| dataset_name (str): name of the dataset to be evaluated.
|
| It must have either the following corresponding metadata:
|
|
|
| "json_file": the path to the COCO format annotation
|
|
|
| Or it must be in detectron2's standard dataset format
|
| so it can be converted to COCO format automatically.
|
| tasks (tuple[str]): tasks that can be evaluated under the given
|
| configuration. A task is one of "bbox", "segm", "keypoints".
|
| By default, will infer this automatically from predictions.
|
| distributed (True): if True, will collect results from all ranks and run evaluation
|
| in the main process.
|
| Otherwise, will only evaluate the results in the current process.
|
| output_dir (str): optional, an output directory to dump all
|
| results predicted on the dataset. The dump contains two files:
|
|
|
| 1. "instances_predictions.pth" a file that can be loaded with `torch.load` and
|
| contains all the results in the format they are produced by the model.
|
| 2. "coco_instances_results.json" a json file in COCO's result format.
|
| max_dets_per_image (int): limit on the maximum number of detections per image.
|
| By default in COCO, this limit is to 100, but this can be customized
|
| to be greater, as is needed in evaluation metrics AP fixed and AP pool
|
| (see https://arxiv.org/pdf/2102.01066.pdf)
|
| This doesn't affect keypoint evaluation.
|
| use_fast_impl (bool): use a fast but **unofficial** implementation to compute AP.
|
| Although the results should be very close to the official implementation in COCO
|
| API, it is still recommended to compute results with the official API for use in
|
| papers. The faster implementation also uses more RAM.
|
| kpt_oks_sigmas (list[float]): The sigmas used to calculate keypoint OKS.
|
| See http://cocodataset.org/#keypoints-eval
|
| When empty, it will use the defaults in COCO.
|
| Otherwise it should be the same length as ROI_KEYPOINT_HEAD.NUM_KEYPOINTS.
|
| allow_cached_coco (bool): Whether to use cached coco json from previous validation
|
| runs. You should set this to False if you need to use different validation data.
|
| Defaults to True.
|
| """
|
| self._logger = logging.getLogger(__name__)
|
| self._distributed = distributed
|
| self._output_dir = output_dir
|
|
|
| if use_fast_impl and (COCOeval_opt is COCOeval):
|
| self._logger.info("Fast COCO eval is not built. Falling back to official COCO eval.")
|
| use_fast_impl = False
|
| self._use_fast_impl = use_fast_impl
|
|
|
|
|
|
|
|
|
|
|
|
|
| if max_dets_per_image is None:
|
| max_dets_per_image = [1, 10, 100]
|
| else:
|
| max_dets_per_image = [1, 10, max_dets_per_image]
|
| self._max_dets_per_image = max_dets_per_image
|
|
|
| if tasks is not None and isinstance(tasks, CfgNode):
|
| kpt_oks_sigmas = (
|
| tasks.TEST.KEYPOINT_OKS_SIGMAS if not kpt_oks_sigmas else kpt_oks_sigmas
|
| )
|
| self._logger.warn(
|
| "COCO Evaluator instantiated using config, this is deprecated behavior."
|
| " Please pass in explicit arguments instead."
|
| )
|
| self._tasks = None
|
| else:
|
| self._tasks = tasks
|
|
|
| self._cpu_device = torch.device("cpu")
|
|
|
| self._metadata = MetadataCatalog.get(dataset_name)
|
| if not hasattr(self._metadata, "json_file"):
|
| if output_dir is None:
|
| raise ValueError(
|
| "output_dir must be provided to COCOEvaluator "
|
| "for datasets not in COCO format."
|
| )
|
| self._logger.info(f"Trying to convert '{dataset_name}' to COCO format ...")
|
|
|
| cache_path = os.path.join(output_dir, f"{dataset_name}_coco_format.json")
|
| self._metadata.json_file = cache_path
|
| convert_to_coco_json(dataset_name, cache_path, allow_cached=allow_cached_coco)
|
|
|
| json_file = PathManager.get_local_path(self._metadata.json_file)
|
| with contextlib.redirect_stdout(io.StringIO()):
|
| self._coco_api = COCO(json_file)
|
|
|
|
|
|
|
| self._do_evaluation = "annotations" in self._coco_api.dataset
|
| if self._do_evaluation:
|
| self._kpt_oks_sigmas = kpt_oks_sigmas
|
|
|
| def reset(self):
|
| self._predictions = []
|
|
|
| def process(self, inputs, outputs):
|
| """
|
| Args:
|
| inputs: the inputs to a COCO model (e.g., GeneralizedRCNN).
|
| It is a list of dict. Each dict corresponds to an image and
|
| contains keys like "height", "width", "file_name", "image_id".
|
| outputs: the outputs of a COCO model. It is a list of dicts with key
|
| "instances" that contains :class:`Instances`.
|
| """
|
| for input, output in zip(inputs, outputs):
|
| prediction = {"image_id": input["image_id"]}
|
|
|
| if "instances" in output:
|
| instances = output["instances"].to(self._cpu_device)
|
| prediction["instances"] = instances_to_coco_json(instances, input["image_id"])
|
| if "proposals" in output:
|
| prediction["proposals"] = output["proposals"].to(self._cpu_device)
|
| if len(prediction) > 1:
|
| self._predictions.append(prediction)
|
|
|
| def evaluate(self, img_ids=None):
|
| """
|
| Args:
|
| img_ids: a list of image IDs to evaluate on. Default to None for the whole dataset
|
| """
|
| if self._distributed:
|
| comm.synchronize()
|
| predictions = comm.gather(self._predictions, dst=0)
|
| predictions = list(itertools.chain(*predictions))
|
|
|
| if not comm.is_main_process():
|
| return {}
|
| else:
|
| predictions = self._predictions
|
|
|
| if len(predictions) == 0:
|
| self._logger.warning("[COCOEvaluator] Did not receive valid predictions.")
|
| return {}
|
|
|
| if self._output_dir:
|
| PathManager.mkdirs(self._output_dir)
|
| file_path = os.path.join(self._output_dir, "instances_predictions.pth")
|
| with PathManager.open(file_path, "wb") as f:
|
| torch.save(predictions, f)
|
|
|
| self._results = OrderedDict()
|
| if "proposals" in predictions[0]:
|
| self._eval_box_proposals(predictions)
|
| if "instances" in predictions[0]:
|
| self._eval_predictions(predictions, img_ids=img_ids)
|
|
|
| return copy.deepcopy(self._results)
|
|
|
| def _tasks_from_predictions(self, predictions):
|
| """
|
| Get COCO API "tasks" (i.e. iou_type) from COCO-format predictions.
|
| """
|
| tasks = {"bbox"}
|
| for pred in predictions:
|
| if "segmentation" in pred:
|
| tasks.add("segm")
|
| if "keypoints" in pred:
|
| tasks.add("keypoints")
|
| return sorted(tasks)
|
|
|
| def _eval_predictions(self, predictions, img_ids=None):
|
| """
|
| Evaluate predictions. Fill self._results with the metrics of the tasks.
|
| """
|
| self._logger.info("Preparing results for COCO format ...")
|
| coco_results = list(itertools.chain(*[x["instances"] for x in predictions]))
|
| tasks = self._tasks or self._tasks_from_predictions(coco_results)
|
|
|
|
|
| if hasattr(self._metadata, "thing_dataset_id_to_contiguous_id"):
|
| dataset_id_to_contiguous_id = self._metadata.thing_dataset_id_to_contiguous_id
|
| all_contiguous_ids = list(dataset_id_to_contiguous_id.values())
|
| num_classes = len(all_contiguous_ids)
|
| assert min(all_contiguous_ids) == 0 and max(all_contiguous_ids) == num_classes - 1
|
|
|
| reverse_id_mapping = {v: k for k, v in dataset_id_to_contiguous_id.items()}
|
| for result in coco_results:
|
| category_id = result["category_id"]
|
| assert category_id < num_classes, (
|
| f"A prediction has class={category_id}, "
|
| f"but the dataset only has {num_classes} classes and "
|
| f"predicted class id should be in [0, {num_classes - 1}]."
|
| )
|
| result["category_id"] = reverse_id_mapping[category_id]
|
|
|
| if self._output_dir:
|
| file_path = os.path.join(self._output_dir, "coco_instances_results.json")
|
| self._logger.info("Saving results to {}".format(file_path))
|
| with PathManager.open(file_path, "w") as f:
|
| f.write(json.dumps(coco_results))
|
| f.flush()
|
|
|
| if not self._do_evaluation:
|
| self._logger.info("Annotations are not available for evaluation.")
|
| return
|
|
|
| self._logger.info(
|
| "Evaluating predictions with {} COCO API...".format(
|
| "unofficial" if self._use_fast_impl else "official"
|
| )
|
| )
|
| for task in sorted(tasks):
|
| assert task in {"bbox", "segm", "keypoints"}, f"Got unknown task: {task}!"
|
| coco_eval = (
|
| _evaluate_predictions_on_coco(
|
| self._coco_api,
|
| coco_results,
|
| task,
|
| kpt_oks_sigmas=self._kpt_oks_sigmas,
|
| cocoeval_fn=COCOeval_opt if self._use_fast_impl else COCOeval,
|
| img_ids=img_ids,
|
| max_dets_per_image=self._max_dets_per_image,
|
| )
|
| if len(coco_results) > 0
|
| else None
|
| )
|
|
|
| res = self._derive_coco_results(
|
| coco_eval, task, class_names=self._metadata.get("thing_classes")
|
| )
|
| self._results[task] = res
|
|
|
| def _eval_box_proposals(self, predictions):
|
| """
|
| Evaluate the box proposals in predictions.
|
| Fill self._results with the metrics for "box_proposals" task.
|
| """
|
| if self._output_dir:
|
|
|
|
|
| bbox_mode = BoxMode.XYXY_ABS.value
|
| ids, boxes, objectness_logits = [], [], []
|
| for prediction in predictions:
|
| ids.append(prediction["image_id"])
|
| boxes.append(prediction["proposals"].proposal_boxes.tensor.numpy())
|
| objectness_logits.append(prediction["proposals"].objectness_logits.numpy())
|
|
|
| proposal_data = {
|
| "boxes": boxes,
|
| "objectness_logits": objectness_logits,
|
| "ids": ids,
|
| "bbox_mode": bbox_mode,
|
| }
|
| with PathManager.open(os.path.join(self._output_dir, "box_proposals.pkl"), "wb") as f:
|
| pickle.dump(proposal_data, f)
|
|
|
| if not self._do_evaluation:
|
| self._logger.info("Annotations are not available for evaluation.")
|
| return
|
|
|
| self._logger.info("Evaluating bbox proposals ...")
|
| res = {}
|
| areas = {"all": "", "small": "s", "medium": "m", "large": "l"}
|
| for limit in [100, 1000]:
|
| for area, suffix in areas.items():
|
| stats = _evaluate_box_proposals(predictions, self._coco_api, area=area, limit=limit)
|
| key = "AR{}@{:d}".format(suffix, limit)
|
| res[key] = float(stats["ar"].item() * 100)
|
| self._logger.info("Proposal metrics: \n" + create_small_table(res))
|
| self._results["box_proposals"] = res
|
|
|
| def _derive_coco_results(self, coco_eval, iou_type, class_names=None):
|
| """
|
| Derive the desired score numbers from summarized COCOeval.
|
|
|
| Args:
|
| coco_eval (None or COCOEval): None represents no predictions from model.
|
| iou_type (str):
|
| class_names (None or list[str]): if provided, will use it to predict
|
| per-category AP.
|
|
|
| Returns:
|
| a dict of {metric name: score}
|
| """
|
|
|
| metrics = {
|
| "bbox": ["AP", "AP50", "AP75", "APs", "APm", "APl"],
|
| "segm": ["AP", "AP50", "AP75", "APs", "APm", "APl"],
|
| "keypoints": ["AP", "AP50", "AP75", "APm", "APl"],
|
| }[iou_type]
|
|
|
| if coco_eval is None:
|
| self._logger.warn("No predictions from the model!")
|
| return {metric: float("nan") for metric in metrics}
|
|
|
|
|
| results = {
|
| metric: float(coco_eval.stats[idx] * 100 if coco_eval.stats[idx] >= 0 else "nan")
|
| for idx, metric in enumerate(metrics)
|
| }
|
| self._logger.info(
|
| "Evaluation results for {}: \n".format(iou_type) + create_small_table(results)
|
| )
|
| if not np.isfinite(sum(results.values())):
|
| self._logger.info("Some metrics cannot be computed and is shown as NaN.")
|
|
|
| if class_names is None or len(class_names) <= 1:
|
| return results
|
|
|
|
|
| precisions = coco_eval.eval["precision"]
|
|
|
| assert len(class_names) == precisions.shape[2]
|
|
|
| results_per_category = []
|
| for idx, name in enumerate(class_names):
|
|
|
|
|
| precision = precisions[:, :, idx, 0, -1]
|
| precision = precision[precision > -1]
|
| ap = np.mean(precision) if precision.size else float("nan")
|
| results_per_category.append(("{}".format(name), float(ap * 100)))
|
|
|
|
|
| N_COLS = min(6, len(results_per_category) * 2)
|
| results_flatten = list(itertools.chain(*results_per_category))
|
| results_2d = itertools.zip_longest(*[results_flatten[i::N_COLS] for i in range(N_COLS)])
|
| table = tabulate(
|
| results_2d,
|
| tablefmt="pipe",
|
| floatfmt=".3f",
|
| headers=["category", "AP"] * (N_COLS // 2),
|
| numalign="left",
|
| )
|
| self._logger.info("Per-category {} AP: \n".format(iou_type) + table)
|
|
|
| results.update({"AP-" + name: ap for name, ap in results_per_category})
|
| return results
|
|
|
|
|
| def instances_to_coco_json(instances, img_id):
|
| """
|
| Dump an "Instances" object to a COCO-format json that's used for evaluation.
|
|
|
| Args:
|
| instances (Instances):
|
| img_id (int): the image id
|
|
|
| Returns:
|
| list[dict]: list of json annotations in COCO format.
|
| """
|
| num_instance = len(instances)
|
| if num_instance == 0:
|
| return []
|
|
|
| boxes = instances.pred_boxes.tensor.numpy()
|
| boxes = BoxMode.convert(boxes, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
|
| boxes = boxes.tolist()
|
| scores = instances.scores.tolist()
|
| classes = instances.pred_classes.tolist()
|
|
|
| has_mask = instances.has("pred_masks")
|
| if has_mask:
|
|
|
|
|
| rles = [
|
| mask_util.encode(np.array(mask[:, :, None], order="F", dtype="uint8"))[0]
|
| for mask in instances.pred_masks
|
| ]
|
| for rle in rles:
|
|
|
|
|
|
|
|
|
| rle["counts"] = rle["counts"].decode("utf-8")
|
|
|
| has_keypoints = instances.has("pred_keypoints")
|
| if has_keypoints:
|
| keypoints = instances.pred_keypoints
|
|
|
| results = []
|
| for k in range(num_instance):
|
| result = {
|
| "image_id": img_id,
|
| "category_id": classes[k],
|
| "bbox": boxes[k],
|
| "score": scores[k],
|
| }
|
| if has_mask:
|
| result["segmentation"] = rles[k]
|
| if has_keypoints:
|
|
|
|
|
|
|
|
|
|
|
| keypoints[k][:, :2] -= 0.5
|
| result["keypoints"] = keypoints[k].flatten().tolist()
|
| results.append(result)
|
| return results
|
|
|
|
|
|
|
|
|
| def _evaluate_box_proposals(dataset_predictions, coco_api, thresholds=None, area="all", limit=None):
|
| """
|
| Evaluate detection proposal recall metrics. This function is a much
|
| faster alternative to the official COCO API recall evaluation code. However,
|
| it produces slightly different results.
|
| """
|
|
|
|
|
| areas = {
|
| "all": 0,
|
| "small": 1,
|
| "medium": 2,
|
| "large": 3,
|
| "96-128": 4,
|
| "128-256": 5,
|
| "256-512": 6,
|
| "512-inf": 7,
|
| }
|
| area_ranges = [
|
| [0**2, 1e5**2],
|
| [0**2, 32**2],
|
| [32**2, 96**2],
|
| [96**2, 1e5**2],
|
| [96**2, 128**2],
|
| [128**2, 256**2],
|
| [256**2, 512**2],
|
| [512**2, 1e5**2],
|
| ]
|
| assert area in areas, "Unknown area range: {}".format(area)
|
| area_range = area_ranges[areas[area]]
|
| gt_overlaps = []
|
| num_pos = 0
|
|
|
| for prediction_dict in dataset_predictions:
|
| predictions = prediction_dict["proposals"]
|
|
|
|
|
|
|
| inds = predictions.objectness_logits.sort(descending=True)[1]
|
| predictions = predictions[inds]
|
|
|
| ann_ids = coco_api.getAnnIds(imgIds=prediction_dict["image_id"])
|
| anno = coco_api.loadAnns(ann_ids)
|
| gt_boxes = [
|
| BoxMode.convert(obj["bbox"], BoxMode.XYWH_ABS, BoxMode.XYXY_ABS)
|
| for obj in anno
|
| if obj["iscrowd"] == 0
|
| ]
|
| gt_boxes = torch.as_tensor(gt_boxes).reshape(-1, 4)
|
| gt_boxes = Boxes(gt_boxes)
|
| gt_areas = torch.as_tensor([obj["area"] for obj in anno if obj["iscrowd"] == 0])
|
|
|
| if len(gt_boxes) == 0 or len(predictions) == 0:
|
| continue
|
|
|
| valid_gt_inds = (gt_areas >= area_range[0]) & (gt_areas <= area_range[1])
|
| gt_boxes = gt_boxes[valid_gt_inds]
|
|
|
| num_pos += len(gt_boxes)
|
|
|
| if len(gt_boxes) == 0:
|
| continue
|
|
|
| if limit is not None and len(predictions) > limit:
|
| predictions = predictions[:limit]
|
|
|
| overlaps = pairwise_iou(predictions.proposal_boxes, gt_boxes)
|
|
|
| _gt_overlaps = torch.zeros(len(gt_boxes))
|
| for j in range(min(len(predictions), len(gt_boxes))):
|
|
|
|
|
| max_overlaps, argmax_overlaps = overlaps.max(dim=0)
|
|
|
|
|
| gt_ovr, gt_ind = max_overlaps.max(dim=0)
|
| assert gt_ovr >= 0
|
|
|
| box_ind = argmax_overlaps[gt_ind]
|
|
|
| _gt_overlaps[j] = overlaps[box_ind, gt_ind]
|
| assert _gt_overlaps[j] == gt_ovr
|
|
|
| overlaps[box_ind, :] = -1
|
| overlaps[:, gt_ind] = -1
|
|
|
|
|
| gt_overlaps.append(_gt_overlaps)
|
| gt_overlaps = (
|
| torch.cat(gt_overlaps, dim=0) if len(gt_overlaps) else torch.zeros(0, dtype=torch.float32)
|
| )
|
| gt_overlaps, _ = torch.sort(gt_overlaps)
|
|
|
| if thresholds is None:
|
| step = 0.05
|
| thresholds = torch.arange(0.5, 0.95 + 1e-5, step, dtype=torch.float32)
|
| recalls = torch.zeros_like(thresholds)
|
|
|
| for i, t in enumerate(thresholds):
|
| recalls[i] = (gt_overlaps >= t).float().sum() / float(num_pos)
|
|
|
| ar = recalls.mean()
|
| return {
|
| "ar": ar,
|
| "recalls": recalls,
|
| "thresholds": thresholds,
|
| "gt_overlaps": gt_overlaps,
|
| "num_pos": num_pos,
|
| }
|
|
|
|
|
| def _evaluate_predictions_on_coco(
|
| coco_gt,
|
| coco_results,
|
| iou_type,
|
| kpt_oks_sigmas=None,
|
| cocoeval_fn=COCOeval_opt,
|
| img_ids=None,
|
| max_dets_per_image=None,
|
| ):
|
| """
|
| Evaluate the coco results using COCOEval API.
|
| """
|
| assert len(coco_results) > 0
|
|
|
| if iou_type == "segm":
|
| coco_results = copy.deepcopy(coco_results)
|
|
|
|
|
|
|
|
|
| for c in coco_results:
|
| c.pop("bbox", None)
|
|
|
| coco_dt = coco_gt.loadRes(coco_results)
|
| coco_eval = cocoeval_fn(coco_gt, coco_dt, iou_type)
|
|
|
| if max_dets_per_image is None:
|
| max_dets_per_image = [1, 10, 100]
|
| else:
|
| assert (
|
| len(max_dets_per_image) >= 3
|
| ), "COCOeval requires maxDets (and max_dets_per_image) to have length at least 3"
|
|
|
|
|
| if max_dets_per_image[2] != 100:
|
| coco_eval = COCOevalMaxDets(coco_gt, coco_dt, iou_type)
|
| if iou_type != "keypoints":
|
| coco_eval.params.maxDets = max_dets_per_image
|
|
|
| if img_ids is not None:
|
| coco_eval.params.imgIds = img_ids
|
|
|
| if iou_type == "keypoints":
|
|
|
| if kpt_oks_sigmas:
|
| assert hasattr(coco_eval.params, "kpt_oks_sigmas"), "pycocotools is too old!"
|
| coco_eval.params.kpt_oks_sigmas = np.array(kpt_oks_sigmas)
|
|
|
|
|
| num_keypoints_dt = len(coco_results[0]["keypoints"]) // 3
|
| num_keypoints_gt = len(next(iter(coco_gt.anns.values()))["keypoints"]) // 3
|
| num_keypoints_oks = len(coco_eval.params.kpt_oks_sigmas)
|
| assert num_keypoints_oks == num_keypoints_dt == num_keypoints_gt, (
|
| f"[COCOEvaluator] Prediction contain {num_keypoints_dt} keypoints. "
|
| f"Ground truth contains {num_keypoints_gt} keypoints. "
|
| f"The length of cfg.TEST.KEYPOINT_OKS_SIGMAS is {num_keypoints_oks}. "
|
| "They have to agree with each other. For meaning of OKS, please refer to "
|
| "http://cocodataset.org/#keypoints-eval."
|
| )
|
|
|
| coco_eval.evaluate()
|
| coco_eval.accumulate()
|
| coco_eval.summarize()
|
|
|
| return coco_eval
|
|
|
|
|
| class COCOevalMaxDets(COCOeval):
|
| """
|
| Modified version of COCOeval for evaluating AP with a custom
|
| maxDets (by default for COCO, maxDets is 100)
|
| """
|
|
|
| def summarize(self):
|
| """
|
| Compute and display summary metrics for evaluation results given
|
| a custom value for max_dets_per_image
|
| """
|
|
|
| def _summarize(ap=1, iouThr=None, areaRng="all", maxDets=100):
|
| p = self.params
|
| iStr = " {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.3f}"
|
| titleStr = "Average Precision" if ap == 1 else "Average Recall"
|
| typeStr = "(AP)" if ap == 1 else "(AR)"
|
| iouStr = (
|
| "{:0.2f}:{:0.2f}".format(p.iouThrs[0], p.iouThrs[-1])
|
| if iouThr is None
|
| else "{:0.2f}".format(iouThr)
|
| )
|
|
|
| aind = [i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng]
|
| mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets]
|
| if ap == 1:
|
|
|
| s = self.eval["precision"]
|
|
|
| if iouThr is not None:
|
| t = np.where(iouThr == p.iouThrs)[0]
|
| s = s[t]
|
| s = s[:, :, :, aind, mind]
|
| else:
|
|
|
| s = self.eval["recall"]
|
| if iouThr is not None:
|
| t = np.where(iouThr == p.iouThrs)[0]
|
| s = s[t]
|
| s = s[:, :, aind, mind]
|
| if len(s[s > -1]) == 0:
|
| mean_s = -1
|
| else:
|
| mean_s = np.mean(s[s > -1])
|
| print(iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, mean_s))
|
| return mean_s
|
|
|
| def _summarizeDets():
|
| stats = np.zeros((12,))
|
|
|
| stats[0] = _summarize(1, maxDets=self.params.maxDets[2])
|
| stats[1] = _summarize(1, iouThr=0.5, maxDets=self.params.maxDets[2])
|
| stats[2] = _summarize(1, iouThr=0.75, maxDets=self.params.maxDets[2])
|
| stats[3] = _summarize(1, areaRng="small", maxDets=self.params.maxDets[2])
|
| stats[4] = _summarize(1, areaRng="medium", maxDets=self.params.maxDets[2])
|
| stats[5] = _summarize(1, areaRng="large", maxDets=self.params.maxDets[2])
|
| stats[6] = _summarize(0, maxDets=self.params.maxDets[0])
|
| stats[7] = _summarize(0, maxDets=self.params.maxDets[1])
|
| stats[8] = _summarize(0, maxDets=self.params.maxDets[2])
|
| stats[9] = _summarize(0, areaRng="small", maxDets=self.params.maxDets[2])
|
| stats[10] = _summarize(0, areaRng="medium", maxDets=self.params.maxDets[2])
|
| stats[11] = _summarize(0, areaRng="large", maxDets=self.params.maxDets[2])
|
| return stats
|
|
|
| def _summarizeKps():
|
| stats = np.zeros((10,))
|
| stats[0] = _summarize(1, maxDets=20)
|
| stats[1] = _summarize(1, maxDets=20, iouThr=0.5)
|
| stats[2] = _summarize(1, maxDets=20, iouThr=0.75)
|
| stats[3] = _summarize(1, maxDets=20, areaRng="medium")
|
| stats[4] = _summarize(1, maxDets=20, areaRng="large")
|
| stats[5] = _summarize(0, maxDets=20)
|
| stats[6] = _summarize(0, maxDets=20, iouThr=0.5)
|
| stats[7] = _summarize(0, maxDets=20, iouThr=0.75)
|
| stats[8] = _summarize(0, maxDets=20, areaRng="medium")
|
| stats[9] = _summarize(0, maxDets=20, areaRng="large")
|
| return stats
|
|
|
| if not self.eval:
|
| raise Exception("Please run accumulate() first")
|
| iouType = self.params.iouType
|
| if iouType == "segm" or iouType == "bbox":
|
| summarize = _summarizeDets
|
| elif iouType == "keypoints":
|
| summarize = _summarizeKps
|
| self.stats = summarize()
|
|
|
| def __str__(self):
|
| self.summarize()
|
|
|