#!/usr/bin/python # # Converts the *instanceIds.png annotations of the Cityscapes dataset # to COCO-style panoptic segmentation format (http://cocodataset.org/#format-data). # The convertion is working for 'fine' set of the annotations. # # By default with this tool uses IDs specified in labels.py. You can use flag # --use-train-id to get train ids for categories. 'ignoreInEval' categories are # removed during the conversion. # # In panoptic segmentation format image_id is used to match predictions and ground truth. # For cityscapes image_id has form _123456_123456 and corresponds to the prefix # of cityscapes image files. # # python imports from __future__ import print_function, absolute_import, division, unicode_literals import os import glob import sys import argparse import json import numpy as np # Image processing from PIL import Image # cityscapes imports from ext.cityscapes_scripts.helpers.csHelpers import printError from ext.cityscapes_scripts.helpers.labels import id2label, labels import mmengine # The main method def convert2panoptic(cityscapesPath=None, outputFolder=None, useTrainId=False, setNames=["val", "train", "test"]): # Where to look for Cityscapes if cityscapesPath is None: if 'CITYSCAPES_DATASET' in os.environ: cityscapesPath = os.environ['CITYSCAPES_DATASET'] else: cityscapesPath = 'data/cityscapes' cityscapesPath = os.path.join(cityscapesPath, "gtFine") if outputFolder is None: outputFolder = cityscapesPath.replace('gtFine', "annotations") mmengine.mkdir_or_exist(outputFolder) categories = [] for label in labels: if label.ignoreInEval: continue categories.append({'id': int(label.trainId) if useTrainId else int(label.id), 'name': label.name, 'color': label.color, 'supercategory': label.category, 'isthing': 1 if label.hasInstances else 0}) categories = sorted(categories, key=lambda x:x['id']) for setName in setNames: # how to search for all ground truth searchFine = os.path.join(cityscapesPath, setName, "*", "*_instanceIds.png") # search files filesFine = glob.glob(searchFine) filesFine.sort() files = filesFine # quit if we did not find anything if not files: printError( "Did not find any files for {} set using matching pattern {}. Please consult the README.".format(setName, searchFine) ) # a bit verbose print("Converting {} annotation files for {} set.".format(len(files), setName)) trainIfSuffix = "_trainId" if useTrainId else "" outputBaseFile = "cityscapes_panoptic_{}{}".format(setName, trainIfSuffix) outFile = os.path.join(outputFolder, "{}.json".format(outputBaseFile)) print("Json file with the annotations in panoptic format will be saved in {}".format(outFile)) panopticFolder = os.path.join(outputFolder, outputBaseFile) if not os.path.isdir(panopticFolder): print("Creating folder {} for panoptic segmentation PNGs".format(panopticFolder)) os.mkdir(panopticFolder) print("Corresponding segmentations in .png format will be saved in {}".format(panopticFolder)) images = [] annotations = [] for progress, f in enumerate(files): originalFormat = np.array(Image.open(f)) fileName = os.path.basename(f) location = fileName.split('_')[0] imageId = fileName.replace("_gtFine_instanceIds.png", "") fileName = os.path.join(location, fileName) inputFileName = fileName.replace("_gtFine_instanceIds.png", "_leftImg8bit.png") outputFileName = fileName.replace("_gtFine_instanceIds.png", "_panoptic.png") # image entry, id for image is its filename without extension images.append({"id": imageId, "width": int(originalFormat.shape[1]), "height": int(originalFormat.shape[0]), "file_name": inputFileName}) pan_format = np.zeros( (originalFormat.shape[0], originalFormat.shape[1], 3), dtype=np.uint8 ) segmentIds = np.unique(originalFormat) segmInfo = [] for segmentId in segmentIds: if segmentId < 1000: semanticId = segmentId isCrowd = 1 else: semanticId = segmentId // 1000 isCrowd = 0 labelInfo = id2label[semanticId] categoryId = labelInfo.trainId if useTrainId else labelInfo.id if labelInfo.ignoreInEval: continue if not labelInfo.hasInstances: isCrowd = 0 mask = originalFormat == segmentId color = [segmentId % 256, segmentId // 256, segmentId // 256 // 256] pan_format[mask] = color area = np.sum(mask) # segment area computation # 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)] segmInfo.append({"id": int(segmentId), "category_id": int(categoryId), "area": int(area), "bbox": bbox, "iscrowd": isCrowd}) annotations.append({'image_id': imageId, 'file_name': outputFileName, "segments_info": segmInfo}) mmengine.mkdir_or_exist(os.path.dirname(os.path.join(panopticFolder, outputFileName))) Image.fromarray(pan_format).save(os.path.join(panopticFolder, outputFileName)) print("\rProgress: {:>3.2f} %".format((progress + 1) * 100 / len(files)), end=' ') sys.stdout.flush() print("\nSaving the json file {}".format(outFile)) d = {'images': images, 'annotations': annotations, 'categories': categories} with open(outFile, 'w') as f: json.dump(d, f, sort_keys=True, indent=4) def main(): parser = argparse.ArgumentParser() parser.add_argument("--dataset-folder", dest="cityscapesPath", help="path to the Cityscapes dataset 'gtFine' folder", default=None, type=str) parser.add_argument("--output-folder", dest="outputFolder", help="path to the output folder.", default=None, type=str) parser.add_argument("--use-train-id", default=True,action="store_true", dest="useTrainId") parser.add_argument("--set-names", dest="setNames", help="set names to which apply the function to", nargs='+', default=["val", "train"], type=str) args = parser.parse_args() convert2panoptic(args.cityscapesPath, args.outputFolder, args.useTrainId, args.setNames) # call the main if __name__ == "__main__": main()