|
|
|
|
|
|
|
|
|
|
|
|
|
import multiprocessing |
|
import os |
|
|
|
import mmcv |
|
import numpy as np |
|
from mmengine.fileio import get |
|
|
|
|
|
|
|
|
|
|
|
INSTANCE_OFFSET = 1000 |
|
|
|
try: |
|
from panopticapi.evaluation import OFFSET, VOID, PQStat |
|
from panopticapi.utils import rgb2id |
|
except ImportError: |
|
PQStat = None |
|
rgb2id = None |
|
VOID = 0 |
|
OFFSET = 256 * 256 * 256 |
|
|
|
|
|
def pq_compute_single_core(proc_id, |
|
annotation_set, |
|
gt_folder, |
|
pred_folder, |
|
categories, |
|
backend_args=None, |
|
print_log=False): |
|
"""The single core function to evaluate the metric of Panoptic |
|
Segmentation. |
|
|
|
Same as the function with the same name in `panopticapi`. Only the function |
|
to load the images is changed to use the file client. |
|
|
|
Args: |
|
proc_id (int): The id of the mini process. |
|
gt_folder (str): The path of the ground truth images. |
|
pred_folder (str): The path of the prediction images. |
|
categories (str): The categories of the dataset. |
|
backend_args (object): The Backend of the dataset. If None, |
|
the backend will be set to `local`. |
|
print_log (bool): Whether to print the log. Defaults to False. |
|
""" |
|
if PQStat is None: |
|
raise RuntimeError( |
|
'panopticapi is not installed, please install it by: ' |
|
'pip install git+https://github.com/cocodataset/' |
|
'panopticapi.git.') |
|
|
|
pq_stat = PQStat() |
|
|
|
idx = 0 |
|
for gt_ann, pred_ann in annotation_set: |
|
if print_log and idx % 100 == 0: |
|
print('Core: {}, {} from {} images processed'.format( |
|
proc_id, idx, len(annotation_set))) |
|
idx += 1 |
|
|
|
|
|
img_bytes = get( |
|
os.path.join(gt_folder, gt_ann['file_name']), |
|
backend_args=backend_args) |
|
pan_gt = mmcv.imfrombytes(img_bytes, flag='color', channel_order='rgb') |
|
pan_gt = rgb2id(pan_gt) |
|
|
|
|
|
pan_pred = mmcv.imread( |
|
os.path.join(pred_folder, pred_ann['file_name']), |
|
flag='color', |
|
channel_order='rgb') |
|
pan_pred = rgb2id(pan_pred) |
|
|
|
gt_segms = {el['id']: el for el in gt_ann['segments_info']} |
|
pred_segms = {el['id']: el for el in pred_ann['segments_info']} |
|
|
|
|
|
pred_labels_set = set(el['id'] for el in pred_ann['segments_info']) |
|
labels, labels_cnt = np.unique(pan_pred, return_counts=True) |
|
for label, label_cnt in zip(labels, labels_cnt): |
|
if label not in pred_segms: |
|
if label == VOID: |
|
continue |
|
raise KeyError( |
|
'In the image with ID {} segment with ID {} is ' |
|
'presented in PNG and not presented in JSON.'.format( |
|
gt_ann['image_id'], label)) |
|
pred_segms[label]['area'] = label_cnt |
|
pred_labels_set.remove(label) |
|
if pred_segms[label]['category_id'] not in categories: |
|
raise KeyError( |
|
'In the image with ID {} segment with ID {} has ' |
|
'unknown category_id {}.'.format( |
|
gt_ann['image_id'], label, |
|
pred_segms[label]['category_id'])) |
|
if len(pred_labels_set) != 0: |
|
raise KeyError( |
|
'In the image with ID {} the following segment IDs {} ' |
|
'are presented in JSON and not presented in PNG.'.format( |
|
gt_ann['image_id'], list(pred_labels_set))) |
|
|
|
|
|
pan_gt_pred = pan_gt.astype(np.uint64) * OFFSET + pan_pred.astype( |
|
np.uint64) |
|
gt_pred_map = {} |
|
labels, labels_cnt = np.unique(pan_gt_pred, return_counts=True) |
|
for label, intersection in zip(labels, labels_cnt): |
|
gt_id = label // OFFSET |
|
pred_id = label % OFFSET |
|
gt_pred_map[(gt_id, pred_id)] = intersection |
|
|
|
|
|
gt_matched = set() |
|
pred_matched = set() |
|
for label_tuple, intersection in gt_pred_map.items(): |
|
gt_label, pred_label = label_tuple |
|
if gt_label not in gt_segms: |
|
continue |
|
if pred_label not in pred_segms: |
|
continue |
|
if gt_segms[gt_label]['iscrowd'] == 1: |
|
continue |
|
if gt_segms[gt_label]['category_id'] != pred_segms[pred_label][ |
|
'category_id']: |
|
continue |
|
|
|
union = pred_segms[pred_label]['area'] + gt_segms[gt_label][ |
|
'area'] - intersection - gt_pred_map.get((VOID, pred_label), 0) |
|
iou = intersection / union |
|
if iou > 0.5: |
|
pq_stat[gt_segms[gt_label]['category_id']].tp += 1 |
|
pq_stat[gt_segms[gt_label]['category_id']].iou += iou |
|
gt_matched.add(gt_label) |
|
pred_matched.add(pred_label) |
|
|
|
|
|
crowd_labels_dict = {} |
|
for gt_label, gt_info in gt_segms.items(): |
|
if gt_label in gt_matched: |
|
continue |
|
|
|
if gt_info['iscrowd'] == 1: |
|
crowd_labels_dict[gt_info['category_id']] = gt_label |
|
continue |
|
pq_stat[gt_info['category_id']].fn += 1 |
|
|
|
|
|
for pred_label, pred_info in pred_segms.items(): |
|
if pred_label in pred_matched: |
|
continue |
|
|
|
intersection = gt_pred_map.get((VOID, pred_label), 0) |
|
|
|
if pred_info['category_id'] in crowd_labels_dict: |
|
intersection += gt_pred_map.get( |
|
(crowd_labels_dict[pred_info['category_id']], pred_label), |
|
0) |
|
|
|
|
|
if intersection / pred_info['area'] > 0.5: |
|
continue |
|
pq_stat[pred_info['category_id']].fp += 1 |
|
|
|
if print_log: |
|
print('Core: {}, all {} images processed'.format( |
|
proc_id, len(annotation_set))) |
|
return pq_stat |
|
|
|
|
|
def pq_compute_multi_core(matched_annotations_list, |
|
gt_folder, |
|
pred_folder, |
|
categories, |
|
backend_args=None, |
|
nproc=32): |
|
"""Evaluate the metrics of Panoptic Segmentation with multithreading. |
|
|
|
Same as the function with the same name in `panopticapi`. |
|
|
|
Args: |
|
matched_annotations_list (list): The matched annotation list. Each |
|
element is a tuple of annotations of the same image with the |
|
format (gt_anns, pred_anns). |
|
gt_folder (str): The path of the ground truth images. |
|
pred_folder (str): The path of the prediction images. |
|
categories (str): The categories of the dataset. |
|
backend_args (object): The file client of the dataset. If None, |
|
the backend will be set to `local`. |
|
nproc (int): Number of processes for panoptic quality computing. |
|
Defaults to 32. When `nproc` exceeds the number of cpu cores, |
|
the number of cpu cores is used. |
|
""" |
|
if PQStat is None: |
|
raise RuntimeError( |
|
'panopticapi is not installed, please install it by: ' |
|
'pip install git+https://github.com/cocodataset/' |
|
'panopticapi.git.') |
|
|
|
cpu_num = min(nproc, multiprocessing.cpu_count()) |
|
|
|
annotations_split = np.array_split(matched_annotations_list, cpu_num) |
|
print('Number of cores: {}, images per core: {}'.format( |
|
cpu_num, len(annotations_split[0]))) |
|
workers = multiprocessing.Pool(processes=cpu_num) |
|
processes = [] |
|
for proc_id, annotation_set in enumerate(annotations_split): |
|
p = workers.apply_async(pq_compute_single_core, |
|
(proc_id, annotation_set, gt_folder, |
|
pred_folder, categories, backend_args)) |
|
processes.append(p) |
|
|
|
|
|
|
|
workers.close() |
|
workers.join() |
|
|
|
pq_stat = PQStat() |
|
for p in processes: |
|
pq_stat += p.get() |
|
|
|
return pq_stat |
|
|