# Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. # # This source code is licensed under the license found in the # LICENSE file in the root directory of this source tree. import argparse import json import os from pathlib import Path import numpy as np import pycocotools.mask as mask_util from mmengine.utils import ProgressBar, mkdir_or_exist from panopticapi.utils import IdGenerator, save_json from PIL import Image from mmdet.datasets.ade20k import ADE20KPanopticDataset ORIGINAL_CATEGORIES = [ 'wall', 'building', 'sky', 'floor', 'tree', 'ceiling', 'road, route', 'bed', 'window', 'grass', 'cabinet', 'sidewalk, pavement', 'person', 'earth, ground', 'door', 'table', 'mountain, mount', 'plant', 'curtain', 'chair', 'car', 'water', 'painting, picture', 'sofa', 'shelf', 'house', 'sea', 'mirror', 'rug', 'field', 'armchair', 'seat', 'fence', 'desk', 'rock, stone', 'wardrobe, closet, press', 'lamp', 'tub', 'rail', 'cushion', 'base, pedestal, stand', 'box', 'column, pillar', 'signboard, sign', 'chest of drawers, chest, bureau, dresser', 'counter', 'sand', 'sink', 'skyscraper', 'fireplace', 'refrigerator, icebox', 'grandstand, covered stand', 'path', 'stairs', 'runway', 'case, display case, showcase, vitrine', 'pool table, billiard table, snooker table', 'pillow', 'screen door, screen', 'stairway, staircase', 'river', 'bridge, span', 'bookcase', 'blind, screen', 'coffee table', 'toilet, can, commode, crapper, pot, potty, stool, throne', 'flower', 'book', 'hill', 'bench', 'countertop', 'stove', 'palm, palm tree', 'kitchen island', 'computer', 'swivel chair', 'boat', 'bar', 'arcade machine', 'hovel, hut, hutch, shack, shanty', 'bus', 'towel', 'light', 'truck', 'tower', 'chandelier', 'awning, sunshade, sunblind', 'street lamp', 'booth', 'tv', 'airplane', 'dirt track', 'clothes', 'pole', 'land, ground, soil', 'bannister, banister, balustrade, balusters, handrail', 'escalator, moving staircase, moving stairway', 'ottoman, pouf, pouffe, puff, hassock', 'bottle', 'buffet, counter, sideboard', 'poster, posting, placard, notice, bill, card', 'stage', 'van', 'ship', 'fountain', 'conveyer belt, conveyor belt, conveyer, conveyor, transporter', 'canopy', 'washer, automatic washer, washing machine', 'plaything, toy', 'pool', 'stool', 'barrel, cask', 'basket, handbasket', 'falls', 'tent', 'bag', 'minibike, motorbike', 'cradle', 'oven', 'ball', 'food, solid food', 'step, stair', 'tank, storage tank', 'trade name', 'microwave', 'pot', 'animal', 'bicycle', 'lake', 'dishwasher', 'screen', 'blanket, cover', 'sculpture', 'hood, exhaust hood', 'sconce', 'vase', 'traffic light', 'tray', 'trash can', 'fan', 'pier', 'crt screen', 'plate', 'monitor', 'bulletin board', 'shower', 'radiator', 'glass, drinking glass', 'clock', 'flag' ] def parse_args(): parser = argparse.ArgumentParser( description='Convert ADE20K annotations to COCO format') parser.add_argument('src', help='ade20k data path') parser.add_argument('--task', help='task name', default='panoptic') args = parser.parse_args() return args def prepare_instance_annotations(dataset_dir: str): dataset_dir = Path(dataset_dir) for name, dirname in [('train', 'training'), ('val', 'validation')]: image_dir = dataset_dir / 'images' / dirname instance_dir = dataset_dir / 'annotations_instance' / dirname ann_id = 0 # json out_file = dataset_dir / f'ade20k_instance_{name}.json' # json config instance_config_file = dataset_dir / 'imgCatIds.json' with open(instance_config_file, 'r') as f: category_dict = json.load(f)['categories'] # catid mapping mapping_file = dataset_dir / 'categoryMapping.txt' with open(mapping_file, 'r') as f: map_id = {} for i, line in enumerate(f.readlines()): if i == 0: continue ins_id, sem_id, _ = line.strip().split() map_id[int(ins_id)] = int(sem_id) - 1 for cat in category_dict: cat['id'] = map_id[cat['id']] filenames = sorted(list(image_dir.iterdir())) ann_dict = {} images = [] annotations = [] progressbar = ProgressBar(len(filenames)) for filename in filenames: image = {} image_id = filename.stem image['id'] = image_id image['file_name'] = filename.name original_format = np.array(Image.open(filename)) image['height'] = original_format.shape[0] image['width'] = original_format.shape[1] images.append(image) instance_file = instance_dir / f'{image_id}.png' ins_seg = np.array(Image.open(instance_file)) assert ins_seg.dtype == np.uint8 instance_cat_ids = ins_seg[..., 0] instance_ins_ids = ins_seg[..., 1] for thing_id in np.unique(instance_ins_ids): if thing_id == 0: continue mask = instance_ins_ids == thing_id instance_cat_id = np.unique(instance_cat_ids[mask]) assert len(instance_cat_id) == 1 anno = {} anno['id'] = ann_id ann_id += 1 anno['image_id'] = image['id'] anno['iscrowd'] = int(0) anno['category_id'] = int(map_id[instance_cat_id[0]]) inds = np.nonzero(mask) ymin, ymax = inds[0].min(), inds[0].max() xmin, xmax = inds[1].min(), inds[1].max() anno['bbox'] = [ int(xmin), int(ymin), int(xmax - xmin + 1), int(ymax - ymin + 1) ] rle = mask_util.encode( np.array(mask[:, :, np.newaxis], order='F', dtype='uint8'))[0] rle['counts'] = rle['counts'].decode('utf-8') anno['segmentation'] = rle anno['area'] = int(mask_util.area(rle)) annotations.append(anno) progressbar.update() ann_dict['images'] = images ann_dict['categories'] = category_dict ann_dict['annotations'] = annotations save_json(ann_dict, out_file) def prepare_panoptic_annotations(dataset_dir: str): dataset_dir = Path(dataset_dir) for name, dirname in [('train', 'training'), ('val', 'validation')]: image_dir = dataset_dir / 'images' / dirname semantic_dir = dataset_dir / 'annotations' / dirname instance_dir = dataset_dir / 'annotations_instance' / dirname # folder to store panoptic PNGs out_folder = dataset_dir / f'ade20k_panoptic_{name}' # json with segmentations information out_file = dataset_dir / f'ade20k_panoptic_{name}.json' mkdir_or_exist(out_folder) # catid mapping neworder_categories = [] all_classes = ORIGINAL_CATEGORIES thing_classes = ADE20KPanopticDataset.METAINFO['thing_classes'] stuff_classes = ADE20KPanopticDataset.METAINFO['stuff_classes'] palette = ADE20KPanopticDataset.METAINFO['palette'] old_2_new_mapping = {} new_2_old_mapping = {} for i, t in enumerate(thing_classes): j = list(all_classes).index(t) old_2_new_mapping[j] = i new_2_old_mapping[i] = j for i, t in enumerate(stuff_classes): j = list(all_classes).index(t) old_2_new_mapping[j] = i + len(thing_classes) new_2_old_mapping[i + len(thing_classes)] = j for old, new in old_2_new_mapping.items(): neworder_categories.append({ 'id': new, 'name': all_classes[old], 'isthing': int(new < len(thing_classes)), 'color': palette[new] }) categories_dict = {cat['id']: cat for cat in neworder_categories} panoptic_json_categories = neworder_categories[:] panoptic_json_images = [] panoptic_json_annotations = [] filenames = sorted(list(image_dir.iterdir())) progressbar = ProgressBar(len(filenames)) for filename in filenames: panoptic_json_image = {} image_id = filename.stem panoptic_json_image['id'] = image_id panoptic_json_image['file_name'] = filename.name original_format = np.array(Image.open(filename)) panoptic_json_image['height'] = original_format.shape[0] panoptic_json_image['width'] = original_format.shape[1] pan_seg = np.zeros( (original_format.shape[0], original_format.shape[1], 3), dtype=np.uint8) id_generator = IdGenerator(categories_dict) filename_semantic = semantic_dir / f'{image_id}.png' filename_instance = instance_dir / f'{image_id}.png' sem_seg = np.array(Image.open(filename_semantic)) ins_seg = np.array(Image.open(filename_instance)) assert sem_seg.dtype == np.uint8 assert ins_seg.dtype == np.uint8 semantic_cat_ids = sem_seg - 1 instance_cat_ids = ins_seg[..., 0] - 1 # instance id starts from 1! # because 0 is reserved as VOID label instance_ins_ids = ins_seg[..., 1] segm_info = [] # process stuffs for semantic_cat_id in np.unique(semantic_cat_ids): if semantic_cat_id == 255: continue if categories_dict[old_2_new_mapping[int( semantic_cat_id)]]['isthing'] == 1: continue mask = semantic_cat_ids == semantic_cat_id # should not have any overlap assert pan_seg[mask].sum() == 0 segment_id, color = id_generator.get_id_and_color( old_2_new_mapping[int(semantic_cat_id)]) pan_seg[mask] = color area = np.sum(mask) # bbox computation for a segment hor = np.sum(mask, axis=0) hor_idx = np.nonzero(hor)[0] x = hor_idx[0] width = hor_idx[-1] - x + 1 vert = np.sum(mask, axis=1) vert_idx = np.nonzero(vert)[0] y = vert_idx[0] height = vert_idx[-1] - y + 1 bbox = [int(x), int(y), int(width), int(height)] segm_info.append({ 'id': int(segment_id), 'category_id': old_2_new_mapping[int(semantic_cat_id)], 'area': int(area), 'bbox': bbox, 'iscrowd': 0 }) # process things for thing_id in np.unique(instance_ins_ids): if thing_id == 0: continue mask = instance_ins_ids == thing_id instance_cat_id = np.unique(instance_cat_ids[mask]) assert len(instance_cat_id) == 1 segment_id, color = id_generator.get_id_and_color( instance_cat_id[0]) pan_seg[mask] = color area = np.sum(mask) # bbox computation for a segment hor = np.sum(mask, axis=0) hor_idx = np.nonzero(hor)[0] x = hor_idx[-1] - x + 1 width = hor_idx[-1] - x + 1 vert = np.sum(mask, axis=1) vert_idx = np.nonzero(vert)[0] y = vert_idx[0] height = vert_idx[-1] - y + 1 bbox = [int(x), int(y), int(width), int(height)] segm_info.append({ 'id': int(segment_id), 'category_id': int(instance_cat_id[0]), 'area': int(area), 'bbox': bbox, 'iscrowd': 0 }) panoptic_json_annotation = { 'image_id': image_id, 'file_name': image_id + '.png', 'segments_info': segm_info } Image.fromarray(pan_seg).save(out_folder / f'{image_id}.png') panoptic_json_images.append(panoptic_json_image) panoptic_json_annotations.append(panoptic_json_annotation) progressbar.update() panoptic_json = { 'images': panoptic_json_images, 'annotations': panoptic_json_annotations, 'categories': panoptic_json_categories } save_json(panoptic_json, out_file) def main(): args = parse_args() assert args.task in ['panoptic', 'instance'] src = args.src if args.task == 'panoptic': annotation_train_path = f'{src}/ade20k_panoptic_train' annotation_val_path = f'{src}/ade20k_panoptic_val' print('Preparing ADE20K panoptic annotations ...') print( f'Creating panoptic annotations to {annotation_train_path} and {annotation_val_path} ...' # noqa ) if os.path.exists(annotation_train_path) or os.path.exists( annotation_val_path): raise RuntimeError('Panoptic annotations already exist.') prepare_panoptic_annotations(src) print('Done.') else: annotation_train_path = f'{src}/ade20k_instance_train' annotation_val_path = f'{src}/ade20k_instance_val' print('Preparing ADE20K instance annotations ...') print( f'Creating instance annotations to {annotation_train_path} and {annotation_val_path} ...' # noqa ) if os.path.exists(annotation_train_path) or os.path.exists( annotation_val_path): raise RuntimeError('Instance annotations already exist.') prepare_instance_annotations(src) print('Done.') if __name__ == '__main__': main()