Spaces:
Sleeping
Sleeping
#!/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 <city>_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() | |