Spaces:
Runtime error
Runtime error
| # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved | |
| """ | |
| COCO evaluator that works in distributed mode. | |
| Mostly copy-paste from https://github.com/pytorch/vision/blob/edfd5a7/references/detection/coco_eval.py | |
| The difference is that there is less copy-pasting from pycocotools | |
| in the end of the file, as python3 can suppress prints with contextlib | |
| """ | |
| import os | |
| import contextlib | |
| import copy | |
| import numpy as np | |
| import torch | |
| import torchvision | |
| import torch.distributed as dist | |
| from pycocotools.cocoeval import COCOeval | |
| from pycocotools.coco import COCO | |
| import pycocotools.mask as mask_util | |
| import pickle | |
| def is_dist_avail_and_initialized(): | |
| if not dist.is_available(): | |
| return False | |
| if not dist.is_initialized(): | |
| return False | |
| return True | |
| def get_world_size(): | |
| if not is_dist_avail_and_initialized(): | |
| return 1 | |
| return dist.get_world_size() | |
| def all_gather(data): | |
| """ | |
| Run all_gather on arbitrary picklable data (not necessarily tensors) | |
| Args: | |
| data: any picklable object | |
| Returns: | |
| list[data]: list of data gathered from each rank | |
| """ | |
| world_size = get_world_size() | |
| if world_size == 1: | |
| return [data] | |
| # serialized to a Tensor | |
| buffer = pickle.dumps(data) | |
| storage = torch.ByteStorage.from_buffer(buffer) | |
| tensor = torch.ByteTensor(storage).to("cuda") | |
| # obtain Tensor size of each rank | |
| local_size = torch.tensor([tensor.numel()], device="cuda") | |
| size_list = [torch.tensor([0], device="cuda") for _ in range(world_size)] | |
| dist.all_gather(size_list, local_size) | |
| size_list = [int(size.item()) for size in size_list] | |
| max_size = max(size_list) | |
| # receiving Tensor from all ranks | |
| # we pad the tensor because torch all_gather does not support | |
| # gathering tensors of different shapes | |
| tensor_list = [] | |
| for _ in size_list: | |
| tensor_list.append(torch.empty((max_size,), dtype=torch.uint8, device="cuda")) | |
| if local_size != max_size: | |
| padding = torch.empty(size=(max_size - local_size,), dtype=torch.uint8, device="cuda") | |
| tensor = torch.cat((tensor, padding), dim=0) | |
| dist.all_gather(tensor_list, tensor) | |
| data_list = [] | |
| for size, tensor in zip(size_list, tensor_list): | |
| buffer = tensor.cpu().numpy().tobytes()[:size] | |
| data_list.append(pickle.loads(buffer)) | |
| return data_list | |
| def get_coco_api_from_dataset(dataset): | |
| for _ in range(10): | |
| # if isinstance(dataset, torchvision.datasets.CocoDetection): | |
| # break | |
| if isinstance(dataset, torch.utils.data.Subset): | |
| dataset = dataset.dataset | |
| if isinstance(dataset, torchvision.datasets.CocoDetection): | |
| return dataset.coco | |
| class CocoEvaluator(object): | |
| def __init__(self, coco_gt, iou_types): | |
| assert isinstance(iou_types, (list, tuple)) | |
| self.coco_gt = copy.deepcopy(coco_gt) | |
| self.iou_types = iou_types | |
| self.coco_eval = {} | |
| for iou_type in iou_types: | |
| self.coco_eval[iou_type] = COCOeval(self.coco_gt, iouType=iou_type) | |
| self.img_ids = [] | |
| self.eval_imgs = {k: [] for k in iou_types} | |
| def update(self, predictions): | |
| img_ids = list(np.unique(list(predictions.keys()))) | |
| self.img_ids.extend(img_ids) | |
| for iou_type in self.iou_types: | |
| results = self.prepare(predictions, iou_type) | |
| # suppress pycocotools prints | |
| with open(os.devnull, 'w') as devnull: | |
| with contextlib.redirect_stdout(devnull): | |
| coco_dt = COCO.loadRes(self.coco_gt, results) if results else COCO() | |
| coco_eval = self.coco_eval[iou_type] | |
| coco_eval.cocoDt = coco_dt | |
| coco_eval.params.imgIds = list(img_ids) | |
| img_ids, eval_imgs = evaluate(coco_eval) | |
| self.eval_imgs[iou_type].append(eval_imgs) | |
| def synchronize_between_processes(self): | |
| for iou_type in self.iou_types: | |
| self.eval_imgs[iou_type] = np.concatenate(self.eval_imgs[iou_type], 2) | |
| create_common_coco_eval(self.coco_eval[iou_type], self.img_ids, self.eval_imgs[iou_type]) | |
| def accumulate(self): | |
| for coco_eval in self.coco_eval.values(): | |
| coco_eval.accumulate() | |
| def summarize(self): | |
| for iou_type, coco_eval in self.coco_eval.items(): | |
| print("IoU metric: {}".format(iou_type)) | |
| coco_eval.summarize() | |
| def _post_process_stats(self, stats, coco_eval_object, iou_type='bbox'): | |
| # bbox & segm: | |
| # stats[0] = _summarize(1) | |
| # stats[1] = _summarize(1, iouThr=.5, maxDets=self.params.maxDets[2]) | |
| # stats[2] = _summarize(1, iouThr=.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]) | |
| # keypoints: | |
| # stats[0] = _summarize(1, maxDets=20) | |
| # stats[1] = _summarize(1, maxDets=20, iouThr=.5) | |
| # stats[2] = _summarize(1, maxDets=20, iouThr=.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=.5) | |
| # stats[7] = _summarize(0, maxDets=20, iouThr=.75) | |
| # stats[8] = _summarize(0, maxDets=20, areaRng='medium') | |
| # stats[9] = _summarize(0, maxDets=20, areaRng='large') | |
| if iou_type not in ['bbox', 'segm', 'keypoints']: | |
| raise ValueError(f"iou_type '{iou_type}' not supported") | |
| current_max_dets = coco_eval_object.params.maxDets | |
| index_to_title = { | |
| "bbox": { | |
| 0: f"AP-IoU=0.50:0.95-area=all-maxDets={current_max_dets[2]}", | |
| 1: f"AP-IoU=0.50-area=all-maxDets={current_max_dets[2]}", | |
| 2: f"AP-IoU=0.75-area=all-maxDets={current_max_dets[2]}", | |
| 3: f"AP-IoU=0.50:0.95-area=small-maxDets={current_max_dets[2]}", | |
| 4: f"AP-IoU=0.50:0.95-area=medium-maxDets={current_max_dets[2]}", | |
| 5: f"AP-IoU=0.50:0.95-area=large-maxDets={current_max_dets[2]}", | |
| 6: f"AR-IoU=0.50:0.95-area=all-maxDets={current_max_dets[0]}", | |
| 7: f"AR-IoU=0.50:0.95-area=all-maxDets={current_max_dets[1]}", | |
| 8: f"AR-IoU=0.50:0.95-area=all-maxDets={current_max_dets[2]}", | |
| 9: f"AR-IoU=0.50:0.95-area=small-maxDets={current_max_dets[2]}", | |
| 10: f"AR-IoU=0.50:0.95-area=medium-maxDets={current_max_dets[2]}", | |
| 11: f"AR-IoU=0.50:0.95-area=large-maxDets={current_max_dets[2]}", | |
| }, | |
| "keypoints": | |
| { | |
| 0: "AP-IoU=0.50:0.95-area=all-maxDets=20", | |
| 1: "AP-IoU=0.50-area=all-maxDets=20", | |
| 2: "AP-IoU=0.75-area=all-maxDets=20", | |
| 3: "AP-IoU=0.50:0.95-area=medium-maxDets=20", | |
| 4: "AP-IoU=0.50:0.95-area=large-maxDets=20", | |
| 5: "AR-IoU=0.50:0.95-area=all-maxDets=20", | |
| 6: "AR-IoU=0.50-area=all-maxDets=20", | |
| 7: "AR-IoU=0.75-area=all-maxDets=20", | |
| 8: "AR-IoU=0.50:0.95-area=medium-maxDets=20", | |
| 9: "AR-IoU=0.50:0.95-area=large-maxDets=20", | |
| }, | |
| } | |
| output_dict = {} | |
| for index, stat in enumerate(stats): | |
| output_dict[index_to_title[iou_type][index]] = stat | |
| return output_dict | |
| def get_results(self): | |
| output_dict = {} | |
| for iou_type, coco_eval in self.coco_eval.items(): | |
| if iou_type == 'segm': | |
| iou_type = 'bbox' | |
| output_dict[f"iou_{iou_type}"] = self._post_process_stats(coco_eval.stats, coco_eval, iou_type) | |
| return output_dict | |
| def prepare(self, predictions, iou_type): | |
| if iou_type == "bbox": | |
| return self.prepare_for_coco_detection(predictions) | |
| elif iou_type == "segm": | |
| return self.prepare_for_coco_segmentation(predictions) | |
| elif iou_type == "keypoints": | |
| return self.prepare_for_coco_keypoint(predictions) | |
| else: | |
| raise ValueError("Unknown iou type {}".format(iou_type)) | |
| def prepare_for_coco_detection(self, predictions): | |
| coco_results = [] | |
| for original_id, prediction in predictions.items(): | |
| if len(prediction) == 0: | |
| continue | |
| boxes = prediction["boxes"] | |
| if not isinstance(boxes, torch.Tensor): | |
| boxes = torch.as_tensor(boxes) | |
| boxes = convert_to_xywh(boxes).tolist() | |
| scores = prediction["scores"] | |
| if not isinstance(scores, list): | |
| scores = scores.tolist() | |
| labels = prediction["labels"] | |
| if not isinstance(labels, list): | |
| labels = prediction["labels"].tolist() | |
| coco_results.extend( | |
| [ | |
| { | |
| "image_id": original_id, | |
| "category_id": labels[k], | |
| "bbox": box, | |
| "score": scores[k], | |
| } | |
| for k, box in enumerate(boxes) | |
| ] | |
| ) | |
| return coco_results | |
| def prepare_for_coco_segmentation(self, predictions): | |
| coco_results = [] | |
| for original_id, prediction in predictions.items(): | |
| if len(prediction) == 0: | |
| continue | |
| scores = prediction["scores"] | |
| labels = prediction["labels"] | |
| masks = prediction["masks"] | |
| masks = masks > 0.5 | |
| scores = prediction["scores"].tolist() | |
| labels = prediction["labels"].tolist() | |
| rles = [ | |
| mask_util.encode(np.array(mask[0, :, :, np.newaxis], dtype=np.uint8, order="F"))[0] | |
| for mask in masks | |
| ] | |
| for rle in rles: | |
| rle["counts"] = rle["counts"].decode("utf-8") | |
| coco_results.extend( | |
| [ | |
| { | |
| "image_id": original_id, | |
| "category_id": labels[k], | |
| "segmentation": rle, | |
| "score": scores[k], | |
| } | |
| for k, rle in enumerate(rles) | |
| ] | |
| ) | |
| return coco_results | |
| def prepare_for_coco_keypoint(self, predictions): | |
| coco_results = [] | |
| for original_id, prediction in predictions.items(): | |
| if len(prediction) == 0: | |
| continue | |
| boxes = prediction["boxes"] | |
| boxes = convert_to_xywh(boxes).tolist() | |
| scores = prediction["scores"].tolist() | |
| labels = prediction["labels"].tolist() | |
| keypoints = prediction["keypoints"] | |
| keypoints = keypoints.flatten(start_dim=1).tolist() | |
| coco_results.extend( | |
| [ | |
| { | |
| "image_id": original_id, | |
| "category_id": labels[k], | |
| 'keypoints': keypoint, | |
| "score": scores[k], | |
| } | |
| for k, keypoint in enumerate(keypoints) | |
| ] | |
| ) | |
| return coco_results | |
| def convert_to_xywh(boxes): | |
| xmin, ymin, xmax, ymax = boxes.unbind(1) | |
| return torch.stack((xmin, ymin, xmax - xmin, ymax - ymin), dim=1) | |
| def merge(img_ids, eval_imgs): | |
| all_img_ids = all_gather(img_ids) | |
| all_eval_imgs = all_gather(eval_imgs) | |
| merged_img_ids = [] | |
| for p in all_img_ids: | |
| merged_img_ids.extend(p) | |
| merged_eval_imgs = [] | |
| for p in all_eval_imgs: | |
| merged_eval_imgs.append(p) | |
| merged_img_ids = np.array(merged_img_ids) | |
| merged_eval_imgs = np.concatenate(merged_eval_imgs, 2) | |
| # keep only unique (and in sorted order) images | |
| merged_img_ids, idx = np.unique(merged_img_ids, return_index=True) | |
| merged_eval_imgs = merged_eval_imgs[..., idx] | |
| return merged_img_ids, merged_eval_imgs | |
| def create_common_coco_eval(coco_eval, img_ids, eval_imgs): | |
| img_ids, eval_imgs = merge(img_ids, eval_imgs) | |
| img_ids = list(img_ids) | |
| eval_imgs = list(eval_imgs.flatten()) | |
| coco_eval.evalImgs = eval_imgs | |
| coco_eval.params.imgIds = img_ids | |
| coco_eval._paramsEval = copy.deepcopy(coco_eval.params) | |
| ################################################################# | |
| # From pycocotools, just removed the prints and fixed | |
| # a Python3 bug about unicode not defined | |
| ################################################################# | |
| def evaluate(self): | |
| ''' | |
| Run per image evaluation on given images and store results (a list of dict) in self.evalImgs | |
| :return: None | |
| ''' | |
| # tic = time.time() | |
| # print('Running per image evaluation...') | |
| p = self.params | |
| # add backward compatibility if useSegm is specified in params | |
| if p.useSegm is not None: | |
| p.iouType = 'segm' if p.useSegm == 1 else 'bbox' | |
| print('useSegm (deprecated) is not None. Running {} evaluation'.format(p.iouType)) | |
| # print('Evaluate annotation type *{}*'.format(p.iouType)) | |
| p.imgIds = list(np.unique(p.imgIds)) | |
| if p.useCats: | |
| p.catIds = list(np.unique(p.catIds)) | |
| p.maxDets = sorted(p.maxDets) | |
| self.params = p | |
| self._prepare() | |
| # loop through images, area range, max detection number | |
| catIds = p.catIds if p.useCats else [-1] | |
| if p.iouType == 'segm' or p.iouType == 'bbox': | |
| computeIoU = self.computeIoU | |
| elif p.iouType == 'keypoints': | |
| computeIoU = self.computeOks | |
| self.ious = { | |
| (imgId, catId): computeIoU(imgId, catId) | |
| for imgId in p.imgIds | |
| for catId in catIds} | |
| evaluateImg = self.evaluateImg | |
| maxDet = p.maxDets[-1] | |
| evalImgs = [ | |
| evaluateImg(imgId, catId, areaRng, maxDet) | |
| for catId in catIds | |
| for areaRng in p.areaRng | |
| for imgId in p.imgIds | |
| ] | |
| # this is NOT in the pycocotools code, but could be done outside | |
| evalImgs = np.asarray(evalImgs).reshape(len(catIds), len(p.areaRng), len(p.imgIds)) | |
| self._paramsEval = copy.deepcopy(self.params) | |
| # toc = time.time() | |
| # print('DONE (t={:0.2f}s).'.format(toc-tic)) | |
| return p.imgIds, evalImgs |