| | 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,
|
| | }
|
| | )
|
| |
|
| |
|