|
|
|
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) is np.ndarray: |
|
return box_list.shape[1] == 5 |
|
elif type(box_list) is list: |
|
if box_list == []: |
|
return False |
|
return np.all( |
|
np.array( |
|
[ |
|
(len(obj) == 5) and ((type(obj) is list) or (type(obj) is np.ndarray)) |
|
for obj in box_list |
|
] |
|
) |
|
) |
|
return False |
|
|
|
@staticmethod |
|
def boxlist_to_tensor(boxlist, output_box_dim): |
|
if type(boxlist) is np.ndarray: |
|
box_tensor = torch.from_numpy(boxlist) |
|
elif type(boxlist) is 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 |
|
|