| from pycocotools.cocoeval import COCOeval
|
| from pycocotools import mask
|
| from tabulate import tabulate
|
| import os
|
| import logging
|
| import io
|
| import numpy as np
|
| import detectron2.utils.comm as comm
|
| from detectron2.config import CfgNode
|
| from detectron2.data import MetadataCatalog, DatasetCatalog
|
| from detectron2.data.datasets.coco import convert_to_coco_json
|
| from detectron2.evaluation.coco_evaluation import COCOEvaluator, _evaluate_predictions_on_coco
|
| from detectron2.evaluation import COCOPanopticEvaluator,SemSegEvaluator
|
| from detectron2.evaluation.fast_eval_api import COCOeval_opt
|
| from detectron2.structures import Boxes, BoxMode, pairwise_iou, PolygonMasks, RotatedBoxes
|
| from detectron2.utils.file_io import PathManager
|
| from typing import Optional
|
| from detectron2.utils.logger import create_small_table
|
| from iopath.common.file_io import file_lock
|
| import shutil
|
| from tqdm import tqdm
|
| from PIL import Image
|
| logger = logging.getLogger(__name__)
|
| import torch
|
| from typing import Optional, Union
|
| import cv2
|
| _CV2_IMPORTED = True
|
| def load_image_into_numpy_array(
|
| filename: str,
|
| copy: bool = False,
|
| dtype: Optional[Union[np.dtype, str]] = None,
|
| ) -> np.ndarray:
|
| with PathManager.open(filename, "rb") as f:
|
| array = np.array(Image.open(f), copy=copy, dtype=dtype)
|
| return array
|
| class my_SemSegEvaluator(SemSegEvaluator):
|
| """
|
| Evaluate semantic segmentation metrics.
|
| """
|
|
|
| def __init__(
|
| self,
|
| dataset_name,
|
| distributed=True,
|
| output_dir=None,
|
| *,
|
| sem_seg_loading_fn=load_image_into_numpy_array,
|
| num_classes=None,
|
| ignore_label=None,
|
| dataset_id_to_cont_id=None,
|
| class_name=None
|
| ):
|
| """
|
| Args:
|
| dataset_name (str): name of the dataset to be evaluated.
|
| distributed (bool): if True, will collect results from all ranks for evaluation.
|
| Otherwise, will evaluate the results in the current process.
|
| output_dir (str): an output directory to dump results.
|
| sem_seg_loading_fn: function to read sem seg file and load into numpy array.
|
| Default provided, but projects can customize.
|
| num_classes, ignore_label: deprecated argument
|
| """
|
| self._logger = logging.getLogger(__name__)
|
| if num_classes is not None:
|
| self._logger.warn(
|
| "SemSegEvaluator(num_classes) is deprecated! It should be obtained from metadata."
|
| )
|
| if ignore_label is not None:
|
| self._logger.warn(
|
| "SemSegEvaluator(ignore_label) is deprecated! It should be obtained from metadata."
|
| )
|
| self._dataset_name = dataset_name
|
| self._distributed = distributed
|
| self._output_dir = output_dir
|
|
|
| self._cpu_device = torch.device("cpu")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| try:
|
| c2d = dataset_id_to_cont_id
|
| self._contiguous_id_to_dataset_id = {v: k for k, v in c2d.items()}
|
| except AttributeError:
|
| self._contiguous_id_to_dataset_id = None
|
| self._class_names = class_name
|
| self.sem_seg_loading_fn = sem_seg_loading_fn
|
| self._num_classes = len(class_name)
|
| if num_classes is not None:
|
| assert self._num_classes == num_classes, f"{self._num_classes} != {num_classes}"
|
| self._ignore_label = ignore_label
|
|
|
|
|
| self._compute_boundary_iou = True
|
| if not _CV2_IMPORTED:
|
| self._compute_boundary_iou = False
|
| self._logger.warn(
|
| """Boundary IoU calculation requires OpenCV. B-IoU metrics are
|
| not going to be computed because OpenCV is not available to import."""
|
| )
|
| if self._num_classes >= np.iinfo(np.uint8).max:
|
| self._compute_boundary_iou = False
|
| self._logger.warn(
|
| f"""SemSegEvaluator(num_classes) is more than supported value for Boundary IoU calculation!
|
| B-IoU metrics are not going to be computed. Max allowed value (exclusive)
|
| for num_classes for calculating Boundary IoU is {np.iinfo(np.uint8).max}.
|
| The number of classes of dataset {self._dataset_name} is {self._num_classes}"""
|
| )
|
| def process(self, inputs, outputs):
|
| """
|
| Args:
|
| inputs: the inputs to a model.
|
| It is a list of dicts. Each dict corresponds to an image and
|
| contains keys like "height", "width", "file_name".
|
| outputs: the outputs of a model. It is either list of semantic segmentation predictions
|
| (Tensor [H, W]) or list of dicts with key "sem_seg" that contains semantic
|
| segmentation prediction in the same format.
|
| """
|
| for input, output in zip(inputs, outputs):
|
| output = output["sem_seg"].argmax(dim=0).to(self._cpu_device)
|
| pred = np.array(output, dtype=int)
|
| gt_filename = input["sem_seg_file_name"]
|
| gt = self.sem_seg_loading_fn(gt_filename, dtype=int)
|
|
|
| gt[gt == self._ignore_label] = self._num_classes
|
|
|
| self._conf_matrix += np.bincount(
|
| (self._num_classes + 1) * pred.reshape(-1) + gt.reshape(-1),
|
| minlength=self._conf_matrix.size,
|
| ).reshape(self._conf_matrix.shape)
|
|
|
| if self._compute_boundary_iou:
|
| b_gt = self._mask_to_boundary(gt.astype(np.uint8))
|
| b_pred = self._mask_to_boundary(pred.astype(np.uint8))
|
|
|
| self._b_conf_matrix += np.bincount(
|
| (self._num_classes + 1) * b_pred.reshape(-1) + b_gt.reshape(-1),
|
| minlength=self._conf_matrix.size,
|
| ).reshape(self._conf_matrix.shape)
|
|
|
| self._predictions.extend(self.encode_json_sem_seg(pred, input["file_name"]))
|
| class my_coco_panoptic_evaluator(COCOPanopticEvaluator):
|
| """
|
| Evaluate Panoptic Quality metrics on COCO using PanopticAPI.
|
| It saves panoptic segmentation prediction in `output_dir`
|
|
|
| It contains a synchronize call and has to be called from all workers.
|
| """
|
|
|
| def __init__(self, dataset_name, output_dir = None, dataset_id_to_cont_id = None, is_thing_list = None):
|
| """
|
| Args:
|
| dataset_name: name of the dataset
|
| output_dir: output directory to save results for evaluation.
|
| """
|
| assert dataset_id_to_cont_id is not None, 'need to give dataset_id_to_cont_id'
|
| assert is_thing_list is not None, 'need to give is_thing_list'
|
| self._metadata = MetadataCatalog.get(dataset_name)
|
| self.is_thing_list = is_thing_list
|
| self._contiguous_id_to_dataset_id = {
|
| v: k for k, v in dataset_id_to_cont_id.items()
|
| }
|
| self._output_dir = output_dir
|
| if self._output_dir is not None:
|
| PathManager.mkdirs(self._output_dir)
|
|
|
|
|
| def _convert_category_id(self, segment_info):
|
| isthing = segment_info.pop("isthing", None)
|
| segment_info["category_id"] = self._contiguous_id_to_dataset_id[
|
| segment_info["category_id"]
|
| ]
|
| return segment_info
|
| def process(self, inputs, outputs):
|
| from panopticapi.utils import id2rgb
|
|
|
| for input, output in zip(inputs, outputs):
|
| panoptic_img, segments_info = output["panoptic_seg"]
|
| panoptic_img = panoptic_img.cpu().numpy()
|
| if segments_info is None:
|
|
|
|
|
|
|
|
|
|
|
| label_divisor = 1000
|
| segments_info = []
|
| for panoptic_label in np.unique(panoptic_img):
|
| if panoptic_label == -1:
|
|
|
| continue
|
| pred_class = panoptic_label // label_divisor
|
| isthing = self.is_thing_list[pred_class]
|
| segments_info.append(
|
| {
|
| "id": int(panoptic_label) + 1,
|
| "category_id": int(pred_class),
|
| "isthing": bool(isthing),
|
| }
|
| )
|
|
|
| panoptic_img += 1
|
|
|
| file_name = os.path.basename(input["file_name"])
|
| file_name_png = os.path.splitext(file_name)[0] + ".png"
|
| with io.BytesIO() as out:
|
| Image.fromarray(id2rgb(panoptic_img)).save(out, format="PNG")
|
| segments_info = [self._convert_category_id(x) for x in segments_info]
|
| self._predictions.append(
|
| {
|
| "image_id": input["image_id"],
|
| "file_name": file_name_png,
|
| "png_string": out.getvalue(),
|
| "segments_info": segments_info,
|
| }
|
| )
|
|
|
|
|