| | |
| |
|
| | |
| | |
| | |
| | |
| | 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 |
| |
|