| | |
| | import itertools |
| | import json |
| | import numpy as np |
| | import os |
| | import torch |
| | from pycocotools.cocoeval import COCOeval, maskUtils |
| |
|
| | from detectron2.structures import BoxMode, RotatedBoxes, pairwise_iou_rotated |
| | from detectron2.utils.file_io import PathManager |
| |
|
| | from .coco_evaluation import COCOEvaluator |
| |
|
| |
|
| | class RotatedCOCOeval(COCOeval): |
| | @staticmethod |
| | def is_rotated(box_list): |
| | if type(box_list) == np.ndarray: |
| | return box_list.shape[1] == 5 |
| | elif type(box_list) == list: |
| | if box_list == []: |
| | return False |
| | return np.all( |
| | np.array( |
| | [ |
| | (len(obj) == 5) and ((type(obj) == list) or (type(obj) == np.ndarray)) |
| | for obj in box_list |
| | ] |
| | ) |
| | ) |
| | return False |
| |
|
| | @staticmethod |
| | def boxlist_to_tensor(boxlist, output_box_dim): |
| | if type(boxlist) == np.ndarray: |
| | box_tensor = torch.from_numpy(boxlist) |
| | elif type(boxlist) == list: |
| | if boxlist == []: |
| | return torch.zeros((0, output_box_dim), dtype=torch.float32) |
| | else: |
| | box_tensor = torch.FloatTensor(boxlist) |
| | else: |
| | raise Exception("Unrecognized boxlist type") |
| |
|
| | input_box_dim = box_tensor.shape[1] |
| | if input_box_dim != output_box_dim: |
| | if input_box_dim == 4 and output_box_dim == 5: |
| | box_tensor = BoxMode.convert(box_tensor, BoxMode.XYWH_ABS, BoxMode.XYWHA_ABS) |
| | else: |
| | raise Exception( |
| | "Unable to convert from {}-dim box to {}-dim box".format( |
| | input_box_dim, output_box_dim |
| | ) |
| | ) |
| | return box_tensor |
| |
|
| | def compute_iou_dt_gt(self, dt, gt, is_crowd): |
| | if self.is_rotated(dt) or self.is_rotated(gt): |
| | |
| | assert all(c == 0 for c in is_crowd) |
| | dt = RotatedBoxes(self.boxlist_to_tensor(dt, output_box_dim=5)) |
| | gt = RotatedBoxes(self.boxlist_to_tensor(gt, output_box_dim=5)) |
| | return pairwise_iou_rotated(dt, gt) |
| | else: |
| | |
| | return maskUtils.iou(dt, gt, is_crowd) |
| |
|
| | def computeIoU(self, imgId: int, catId: int): |
| | p = self.params |
| | if p.useCats: |
| | gt = self._gts[imgId, catId] |
| | dt = self._dts[imgId, catId] |
| | else: |
| | gt = [_ for cId in p.catIds for _ in self._gts[imgId, cId]] |
| | dt = [_ for cId in p.catIds for _ in self._dts[imgId, cId]] |
| |
|
| | if len(gt) == 0 or len(dt) == 0: |
| | return [] |
| |
|
| | inds = np.argsort([-d["score"] for d in dt], kind="mergesort") |
| | dt = [dt[i] for i in inds] |
| | if len(dt) > p.maxDets[-1]: |
| | dt = dt[0 : p.maxDets[-1]] |
| |
|
| | assert p.iouType == "bbox", "unsupported iouType for iou computation" |
| |
|
| | g = [g["bbox"] for g in gt] |
| | d = [d["bbox"] for d in dt] |
| |
|
| | |
| | iscrowd = [int(o["iscrowd"]) for o in gt] |
| |
|
| | |
| | |
| | ious = self.compute_iou_dt_gt(d, g, iscrowd) |
| | return ious |
| |
|
| |
|
| | class RotatedCOCOEvaluator(COCOEvaluator): |
| | """ |
| | Evaluate object proposal/instance detection outputs using COCO-like metrics and APIs, |
| | with rotated boxes support. |
| | Note: this uses IOU only and does not consider angle differences. |
| | """ |
| |
|
| | 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"] = self.instances_to_json(instances, input["image_id"]) |
| | if "proposals" in output: |
| | prediction["proposals"] = output["proposals"].to(self._cpu_device) |
| | self._predictions.append(prediction) |
| |
|
| | def instances_to_json(self, instances, img_id): |
| | num_instance = len(instances) |
| | if num_instance == 0: |
| | return [] |
| |
|
| | boxes = instances.pred_boxes.tensor.numpy() |
| | if boxes.shape[1] == 4: |
| | boxes = BoxMode.convert(boxes, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS) |
| | boxes = boxes.tolist() |
| | scores = instances.scores.tolist() |
| | classes = instances.pred_classes.tolist() |
| |
|
| | results = [] |
| | for k in range(num_instance): |
| | result = { |
| | "image_id": img_id, |
| | "category_id": classes[k], |
| | "bbox": boxes[k], |
| | "score": scores[k], |
| | } |
| |
|
| | results.append(result) |
| | return results |
| |
|
| | def _eval_predictions(self, predictions, img_ids=None): |
| | """ |
| | Evaluate predictions on the given tasks. |
| | 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])) |
| |
|
| | |
| | if hasattr(self._metadata, "thing_dataset_id_to_contiguous_id"): |
| | reverse_id_mapping = { |
| | v: k for k, v in self._metadata.thing_dataset_id_to_contiguous_id.items() |
| | } |
| | for result in coco_results: |
| | result["category_id"] = reverse_id_mapping[result["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 ...") |
| |
|
| | assert self._tasks is None or set(self._tasks) == { |
| | "bbox" |
| | }, "[RotatedCOCOEvaluator] Only bbox evaluation is supported" |
| | coco_eval = ( |
| | self._evaluate_predictions_on_coco(self._coco_api, coco_results) |
| | if len(coco_results) > 0 |
| | else None |
| | ) |
| |
|
| | task = "bbox" |
| | res = self._derive_coco_results( |
| | coco_eval, task, class_names=self._metadata.get("thing_classes") |
| | ) |
| | self._results[task] = res |
| |
|
| | def _evaluate_predictions_on_coco(self, coco_gt, coco_results): |
| | """ |
| | Evaluate the coco results using COCOEval API. |
| | """ |
| | assert len(coco_results) > 0 |
| |
|
| | coco_dt = coco_gt.loadRes(coco_results) |
| |
|
| | |
| | coco_eval = RotatedCOCOeval(coco_gt, coco_dt, iouType="bbox") |
| |
|
| | coco_eval.evaluate() |
| | coco_eval.accumulate() |
| | coco_eval.summarize() |
| |
|
| | return coco_eval |
| |
|