From 6d3607e16cce9824ccdec03cb0b715902539b4a3 Mon Sep 17 00:00:00 2001 From: "Maslova, Zoya" Date: Fri, 21 May 2021 16:17:41 +0300 Subject: [PATCH 1/6] add cityscapes format --- CHANGELOG.md | 3 +- README.md | 1 + datumaro/components/extractor.py | 11 +- datumaro/plugins/cityscapes_format.py | 330 +++++++++++++++ datumaro/util/image.py | 7 +- docs/design.md | 2 +- docs/formats/cityscapes_user_manual.md | 176 ++++++++ docs/user_manual.md | 4 + requirements.txt | 1 + setup.py | 1 + ...tcity_000001_000031_gtFine_instanceIds.png | Bin 0 -> 76 bytes ...tcity_000001_000032_gtFine_instanceIds.png | Bin 0 -> 76 bytes ...tcity_000002_000045_gtFine_instanceIds.png | Bin 0 -> 76 bytes ...tcity_000001_000019_gtFine_instanceIds.png | Bin 0 -> 76 bytes .../defaultcity_000001_000031_leftImg8bit.png | Bin 0 -> 70 bytes .../defaultcity_000001_000032_leftImg8bit.png | Bin 0 -> 70 bytes .../defaultcity_000002_000045_leftImg8bit.png | Bin 0 -> 70 bytes .../defaultcity_000001_000019_leftImg8bit.png | Bin 0 -> 70 bytes tests/test_cityscapes_format.py | 379 ++++++++++++++++++ 19 files changed, 906 insertions(+), 9 deletions(-) create mode 100644 datumaro/plugins/cityscapes_format.py create mode 100644 docs/formats/cityscapes_user_manual.md create mode 100644 tests/assets/cityscapes_dataset/gtFine/test/defaultcity/defaultcity_000001_000031_gtFine_instanceIds.png create mode 100644 tests/assets/cityscapes_dataset/gtFine/test/defaultcity/defaultcity_000001_000032_gtFine_instanceIds.png create mode 100644 tests/assets/cityscapes_dataset/gtFine/train/defaultcity/defaultcity_000002_000045_gtFine_instanceIds.png create mode 100644 tests/assets/cityscapes_dataset/gtFine/val/defaultcity/defaultcity_000001_000019_gtFine_instanceIds.png create mode 100644 tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/test/defaultcity/defaultcity_000001_000031_leftImg8bit.png create mode 100644 tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/test/defaultcity/defaultcity_000001_000032_leftImg8bit.png create mode 100644 tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/train/defaultcity/defaultcity_000002_000045_leftImg8bit.png create mode 100644 tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/val/defaultcity/defaultcity_000001_000019_leftImg8bit.png create mode 100644 tests/test_cityscapes_format.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a256c4877c..e85315d9d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for escaping in attribiute values in LabelMe format () - Support for Segmentation Splitting () - Support for CIFAR-10/100 dataset format (, ) -- Support COCO panoptic and stuff format () +- Support for COCO panoptic and stuff format () - Documentation file and integration tests for Pascal VOC format () - Support for MNIST and MNIST in CSV dataset formats () - Documentation file for COCO format () - Documentation file and integration tests for YOLO format () +- Support for Cityscapes dataset format () ### Changed - LabelMe format saves dataset items with their relative paths by subsets without changing names () diff --git a/README.md b/README.md index 7bd9d6252b..2c5957f522 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ CVAT annotations ---> Publication, statistics etc. - [MNIST](http://yann.lecun.com/exdb/mnist/) (`classification`) - [MNIST in CSV](https://pjreddie.com/projects/mnist-in-csv/) (`classification`) - [CamVid](http://mi.eng.cam.ac.uk/research/projects/VideoRec/CamVid/) + - [Cityscapes](https://www.cityscapes-dataset.com/) - [CVAT](https://github.com/opencv/cvat/blob/develop/cvat/apps/documentation/xml_format.md) - [LabelMe](http://labelme.csail.mit.edu/Release3.0) - [ICDAR13/15](https://rrc.cvc.uab.es/?ch=2) (`word_recognition`, `text_localization`, `text_segmentation`) diff --git a/datumaro/components/extractor.py b/datumaro/components/extractor.py index b913dece13..785861d363 100644 --- a/datumaro/components/extractor.py +++ b/datumaro/components/extractor.py @@ -236,7 +236,7 @@ def __eq__(self, other): class CompiledMask: @staticmethod def from_instance_masks(instance_masks, - instance_ids=None, instance_labels=None): + instance_ids=None, instance_labels=None, dtype=None): from datumaro.util.mask_tools import make_index_mask if instance_ids is not None: @@ -266,7 +266,7 @@ def from_instance_masks(instance_masks, m, idx, instance_id, class_id = next(it) if not class_id: idx = 0 - index_mask = make_index_mask(m, idx) + index_mask = make_index_mask(m, idx, dtype=dtype) instance_map.append(instance_id) class_map.append(class_id) @@ -282,8 +282,8 @@ def from_instance_masks(instance_masks, else: merged_instance_mask = np.array(instance_map, dtype=np.min_scalar_type(instance_map))[index_mask] - merged_class_mask = np.array(class_map, - dtype=np.min_scalar_type(class_map))[index_mask] + dtype_mask = dtype if dtype else np.min_scalar_type(class_map) + merged_class_mask = np.array(class_map, dtype=dtype_mask)[index_mask] return __class__(class_mask=merged_class_mask, instance_mask=merged_instance_mask) @@ -673,7 +673,8 @@ def __call__(self, path, **extra_params): @classmethod def _find_sources_recursive(cls, path, ext, extractor_name, filename='*', dirname='', file_filter=None, max_depth=3): - if path.endswith(ext) and osp.isfile(path): + if (path.endswith(ext) and osp.isfile(path)) or \ + (ext == '' and osp.isdir(path) and not dirname == '' and dirname in path): sources = [{'url': path, 'format': extractor_name}] else: sources = [] diff --git a/datumaro/plugins/cityscapes_format.py b/datumaro/plugins/cityscapes_format.py new file mode 100644 index 0000000000..b3ab0d1a14 --- /dev/null +++ b/datumaro/plugins/cityscapes_format.py @@ -0,0 +1,330 @@ + +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import logging as log +import os +import os.path as osp +from collections import OrderedDict +from enum import Enum + +import numpy as np + +from cityscapesscripts.helpers.labels import labels as CityscapesLabels +from glob import iglob + +from datumaro.components.converter import Converter +from datumaro.components.extractor import (AnnotationType, CompiledMask, + DatasetItem, Importer, LabelCategories, Mask, + MaskCategories, SourceExtractor) +from datumaro.util import str_to_bool +from datumaro.util.annotation_util import make_label_id_mapping +from datumaro.util.image import save_image, load_image +from datumaro.util.mask_tools import generate_colormap, paint_mask + + +class CityscapesPath: + GT_FINE_DIR = 'gtFine' + IMGS_FINE_DIR = 'imgsFine' + ORIGINAL_IMAGE_DIR = 'leftImg8bit' + ORIGINAL_IMAGE = '_'+ORIGINAL_IMAGE_DIR+'.png' + INSTANCES_IMAGE = '_instanceIds.png' + COLOR_IMAGE = '_color.png' + LABELIDS_IMAGE = '_labelIds.png' + + LABELMAP_FILE = 'label_colors.txt' + +def make_cityscapes_label_map(): + label_map = OrderedDict() + for label in CityscapesLabels: + label_map[label.name.replace(' ', '_')] = label.color + return label_map + +def make_cityscapes_categories(label_map=None): + if label_map is None: + label_map = make_cityscapes_label_map() + + categories = {} + label_categories = LabelCategories() + for label, desc in label_map.items(): + label_categories.add(label) + categories[AnnotationType.label] = label_categories + + has_colors = any(v is not None for v in label_map.values()) + if not has_colors: # generate new colors + colormap = generate_colormap(len(label_map)) + else: # only copy defined colors + label_id = lambda label: label_categories.find(label)[0] + colormap = { label_id(name): (desc[0], desc[1], desc[2]) + for name, desc in label_map.items() } + mask_categories = MaskCategories(colormap) + mask_categories.inverse_colormap # pylint: disable=pointless-statement + categories[AnnotationType.mask] = mask_categories + return categories + +def parse_label_map(path): + if not path: + return None + + label_map = OrderedDict() + with open(path, 'r') as f: + for line in f: + # skip empty and commented lines + line = line.strip() + if not line or line and line[0] == '#': + continue + + # color, name + label_desc = line.strip().split() + + if 2 < len(label_desc): + name = label_desc[3] + color = tuple([int(c) for c in label_desc[:-1]]) + else: + name = label_desc[0] + color = None + + if name in label_map: + raise ValueError("Label '%s' is already defined" % name) + + label_map[name] = color + return label_map + +def write_label_map(path, label_map): + with open(path, 'w') as f: + for label_name, label_desc in label_map.items(): + if label_desc: + color_rgb = ' '.join(str(c) for c in label_desc) + else: + color_rgb = '' + f.write('%s %s\n' % (color_rgb, label_name)) + +class CityscapesExtractor(SourceExtractor): + def __init__(self, path, subset=None): + assert osp.isdir(path), path + self._path = path + + if not subset: + subset = osp.splitext(osp.basename(path))[0] + self._subset = subset + super().__init__(subset=subset) + + self._categories = self._load_categories(osp.join(self._path, '../../../')) + self._items = list(self._load_items().values()) + + def _load_categories(self, path): + label_map = None + label_map_path = osp.join(path, CityscapesPath.LABELMAP_FILE) + if osp.isfile(label_map_path): + label_map = parse_label_map(label_map_path) + else: + label_map = make_cityscapes_label_map() + self._labels = [label for label in label_map] + return make_cityscapes_categories(label_map) + + def _load_items(self): + items = {} + + for image_path in iglob(osp.join(self._path, '*', '*'+CityscapesPath.ORIGINAL_IMAGE), recursive=True): + city_name, sample_id = self._get_city_and_sample(image_path) + instances_path = osp.join(self._path, '../../../', + CityscapesPath.GT_FINE_DIR, self._subset, city_name, + sample_id+'_'+CityscapesPath.GT_FINE_DIR+CityscapesPath.INSTANCES_IMAGE) + anns = [] + if osp.isfile(instances_path): + instances_mask = load_image(instances_path, dtype=np.int32) + segm_ids = np.unique(instances_mask) + for segm_id in segm_ids: + if segm_id < 1000: + semanticId = segm_id + isCrowd = True + id = segm_id + else: + semanticId = segm_id // 1000 + isCrowd = False + id = segm_id % 1000 + anns.append(Mask(image=self._lazy_extract_mask(instances_mask, segm_id), + label=semanticId, id=id, + attributes = { 'is_crowd': isCrowd })) + items[sample_id] = DatasetItem(id=sample_id, subset=self._subset, + image=image_path, annotations=anns) + return items + + @staticmethod + def _lazy_extract_mask(mask, c): + return lambda: mask == c + + def _get_city_and_sample(self, full_path): + related_path = osp.relpath(full_path, self._path) + city_name = related_path.split('/')[0] + + sample_id = osp.basename(full_path) + sample_id = sample_id.split(CityscapesPath.ORIGINAL_IMAGE)[0] + + return city_name, sample_id + + +class CityscapesImporter(Importer): + @classmethod + def find_sources(cls, path): + return cls._find_sources_recursive(path, '', 'cityscapes', + dirname=osp.join(CityscapesPath.IMGS_FINE_DIR, + CityscapesPath.ORIGINAL_IMAGE_DIR), max_depth=1) + + +LabelmapType = Enum('LabelmapType', ['cityscapes', 'source']) + +class CityscapesConverter(Converter): + DEFAULT_IMAGE_EXT = '.png' + + @staticmethod + def _get_labelmap(s): + if osp.isfile(s): + return s + try: + return LabelmapType[s].name + except KeyError: + import argparse + raise argparse.ArgumentTypeError() + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + + parser.add_argument('--apply-colormap', type=str_to_bool, default=True, + help="Use colormap for class masks (default: %(default)s)") + parser.add_argument('--label-map', type=cls._get_labelmap, default=None, + help="Labelmap file path or one of %s" % \ + ', '.join(t.name for t in LabelmapType)) + return parser + + def __init__(self, extractor, save_dir, + apply_colormap=True, label_map=None, **kwargs): + super().__init__(extractor, save_dir, **kwargs) + + self._apply_colormap = apply_colormap + + if label_map is None: + label_map = LabelmapType.source.name + self._load_categories(label_map) + + def apply(self): + os.makedirs(self._save_dir, exist_ok=True) + + for subset_name, subset in self._extractor.subsets().items(): + for item in subset: + item.id = item.id.replace('/', '_') + city_name = item.id.split('_')[0] + image_path = osp.join(CityscapesPath.IMGS_FINE_DIR, + CityscapesPath.ORIGINAL_IMAGE_DIR, subset_name, + city_name, item.id+CityscapesPath.ORIGINAL_IMAGE) + if self._save_images: + self._save_image(item, osp.join(self._save_dir, image_path)) + + common_folder_path = osp.join(CityscapesPath.GT_FINE_DIR, + subset_name, city_name) + + masks = [a for a in item.annotations + if a.type == AnnotationType.mask] + if masks: + common_image_name = item.id+'_'+CityscapesPath.GT_FINE_DIR + + compiled_class_mask = CompiledMask.from_instance_masks(masks, + instance_labels=[self._label_id_mapping(m.label) + for m in masks]) + color_mask_path = osp.join(common_folder_path, + common_image_name+CityscapesPath.COLOR_IMAGE) + self.save_segm(osp.join(self._save_dir, color_mask_path), + compiled_class_mask.class_mask) + + labelids_mask_path = osp.join(common_folder_path, + common_image_name+CityscapesPath.LABELIDS_IMAGE) + self.save_segm(osp.join(self._save_dir, labelids_mask_path), + compiled_class_mask.class_mask, apply_colormap=False, + dtype=np.int32) + + compiled_instance_mask = CompiledMask.from_instance_masks(masks, + instance_labels=[m.id if m.attributes.get('is_crowd', True) + else m.label*1000+m.id for m in masks]) + inst_path = osp.join(common_folder_path, + common_image_name+CityscapesPath.INSTANCES_IMAGE) + self.save_segm(osp.join(self._save_dir, inst_path), + compiled_instance_mask.class_mask, apply_colormap=False, + dtype=np.int32) + self.save_label_map() + + def save_label_map(self): + path = osp.join(self._save_dir, CityscapesPath.LABELMAP_FILE) + write_label_map(path, self._label_map) + + def _load_categories(self, label_map_source): + if label_map_source == LabelmapType.cityscapes.name: + # use the default Cityscapes colormap + label_map = make_cityscapes_label_map() + + elif label_map_source == LabelmapType.source.name and \ + AnnotationType.mask not in self._extractor.categories(): + # generate colormap for input labels + labels = self._extractor.categories() \ + .get(AnnotationType.label, LabelCategories()) + label_map = OrderedDict((item.name, None) + for item in labels.items) + + elif label_map_source == LabelmapType.source.name and \ + AnnotationType.mask in self._extractor.categories(): + # use source colormap + labels = self._extractor.categories()[AnnotationType.label] + colors = self._extractor.categories()[AnnotationType.mask] + label_map = OrderedDict() + for idx, item in enumerate(labels.items): + color = colors.colormap.get(idx) + if color is not None: + label_map[item.name] = color + + elif isinstance(label_map_source, dict): + label_map = OrderedDict( + sorted(label_map_source.items(), key=lambda e: e[0])) + + elif isinstance(label_map_source, str) and osp.isfile(label_map_source): + label_map = parse_label_map(label_map_source) + + else: + raise Exception("Wrong labelmap specified, " + "expected one of %s or a file path" % \ + ', '.join(t.name for t in LabelmapType)) + + self._categories = make_cityscapes_categories(label_map) + self._label_map = label_map + self._label_id_mapping = self._make_label_id_map() + + def _make_label_id_map(self): + map_id, id_mapping, src_labels, dst_labels = make_label_id_mapping( + self._extractor.categories().get(AnnotationType.label), + self._categories[AnnotationType.label]) + + void_labels = [src_label for src_id, src_label in src_labels.items() + if src_label not in dst_labels] + if void_labels: + log.warning("The following labels are remapped to background: %s" % + ', '.join(void_labels)) + log.debug("Saving segmentations with the following label mapping: \n%s" % + '\n'.join(["#%s '%s' -> #%s '%s'" % + ( + src_id, src_label, id_mapping[src_id], + self._categories[AnnotationType.label] \ + .items[id_mapping[src_id]].name + ) + for src_id, src_label in src_labels.items() + ]) + ) + + return map_id + + def save_segm(self, path, mask, colormap=None, apply_colormap=True, + dtype=np.uint8): + if self._apply_colormap and apply_colormap: + if colormap is None: + colormap = self._categories[AnnotationType.mask].colormap + mask = paint_mask(mask, colormap) + save_image(path, mask, create_dir=True, dtype=dtype) diff --git a/datumaro/util/image.py b/datumaro/util/image.py index 17e2a0d0ba..c23049f78d 100644 --- a/datumaro/util/image.py +++ b/datumaro/util/image.py @@ -65,7 +65,10 @@ def save_image(path, image, create_dir=False, dtype=np.uint8, **kwargs): if not kwargs: kwargs = {} - if _IMAGE_BACKEND == _IMAGE_BACKENDS.cv2: + backend = _IMAGE_BACKEND + if dtype == np.int32: + backend = _IMAGE_BACKENDS.PIL + if backend == _IMAGE_BACKENDS.cv2: import cv2 params = [] @@ -78,7 +81,7 @@ def save_image(path, image, create_dir=False, dtype=np.uint8, **kwargs): image = image.astype(dtype) cv2.imwrite(path, image, params=params) - elif _IMAGE_BACKEND == _IMAGE_BACKENDS.PIL: + elif backend == _IMAGE_BACKENDS.PIL: from PIL import Image params = {} diff --git a/docs/design.md b/docs/design.md index 1e520400c0..b24a57a595 100644 --- a/docs/design.md +++ b/docs/design.md @@ -108,7 +108,7 @@ It should be capable of downloading and processing data from CVAT. - [x] PASCAL VOC - [x] YOLO - [x] TF Detection API - - [ ] Cityscapes + - [x] Cityscapes - [x] ImageNet - Dataset visualization (`show`) diff --git a/docs/formats/cityscapes_user_manual.md b/docs/formats/cityscapes_user_manual.md new file mode 100644 index 0000000000..49597fa981 --- /dev/null +++ b/docs/formats/cityscapes_user_manual.md @@ -0,0 +1,176 @@ +# Cityscapes user manual + +## Contents + +- [Format specification](#format-specification) +- [Load Cityscapes dataset](#load-Cityscapes-dataset) +- [Export to other formats](#export-to-other-formats) +- [Export to Cityscapes](#export-to-Cityscapes) +- [Particular use cases](#particular-use-cases) + +## Format specification + +Cityscapes format overview available [here](https://www.cityscapes-dataset.com/dataset-overview/). +Cityscapes format specification available [here](https://github.com/mcordts/cityscapesScripts#the-cityscapes-dataset). + +Cityscapes dataset format supports `Masks` (segmentations tasks) annotations. + +## Load Cityscapes dataset + +The Cityscapes dataset is available for free [download](https://www.cityscapes-dataset.com/downloads/). + +There are two ways to create Datumaro project and add Cityscapes dataset to it: + +``` bash +datum import --format cityscapes --input-path +# or +datum create +datum add path -f cityscapes +``` + +It is possible to specify project name and project directory run +`datum create --help` for more information. + +Cityscapes dataset directory should have the following structure: + + +``` +└─ Dataset/ + ├── imgsFine/ + │ ├── leftImg8bit + │ │ ├── + │ │ | ├── {city1} + │ │ │ | ├── {city1}_{seq:0>6}_{frame:0>6}_leftImg8bit.png + │ │ │ │ └── ... + │ │ | ├── {city2} + │ │ │ └── ... + │ │ └── ... + ├── gtFine/ + │ ├── + │ │ ├── {city1} + │ │ | ├── {city1}_{seq:0>6}_{frame:0>6}_gtFine_color.png + │ │ | ├── {city1}_{seq:0>6}_{frame:0>6}_gtFine_instanceIds.png + │ │ | ├── {city1}_{seq:0>6}_{frame:0>6}_gtFine_labelIds.png + │ │ │ └── ... + │ │ ├── {city2} + │ │ └── ... + │ └── ... +``` + +Annorated files description: +1. *leftImg8bit.png - left images in 8-bit LDR format +1. *color.png - class labels are encoded by its color +1. *instanceIds.png - class and instance labels are encoded by an instance ID. + The pixel values encode class and the individual instance: the integer part + of a division by 1000 of each ID provides class ID, the remainder + is the instance ID. If a certain annotation describes multiple instances, + then the pixels have the regular ID of that class +1. *labelIds.png - class labels are encoded by its ID + +To make sure that the selected dataset has been added to the project, you can run +`datum info`, which will display the project and dataset information. + +## Export to other formats + +Datumaro can convert Cityscapes dataset into any other format [Datumaro supports](../user_manual.md#supported-formats). +To get the expected result, the dataset needs to be converted to formats +that support the segmentation task (e.g. PascalVOC, CamVID, etc.) +There are few ways to convert Cityscapes dataset to other dataset format: + +``` bash +datum project import -f cityscapes -i +datum export -f voc -o +# or +datum convert -if cityscapes -i -f voc -o +``` + +Some formats provide extra options for conversion. +These options are passed after double dash (`--`) in the command line. +To get information about them, run + +`datum export -f -- -h` + +## Export to Cityscapes + +There are few ways to convert dataset to Cityscapes format: + +``` bash +# export dataset into Cityscapes format from existing project +datum export -p -f cityscapes -o \ + -- --save-images +# converting to Cityscapes format from other format +datum convert -if voc -i \ + -f cityscapes -o -- --save-images +``` + +Extra options for export to cityscapes format: +- `--save-images` allow to export dataset with saving images +(by default `False`); +- `--image-ext IMAGE_EXT` allow to specify image extension +for exporting dataset (by default - keep original or use `.png`, if none). +- `--apply-colormap APPLY_COLORMAP` allow to use colormap for class masks +(`*color.png` files, by default `True`); +- `--label_map` allow to define a custom colormap. Example + +``` bash +# mycolormap.txt : +# 0 0 255 sky +# 255 0 0 person +#... +datum export -f cityscapes -- --label-map mycolormap.txt + +# or you can use original cityscapes colomap: +datum export -f cityscapes -- --label-map cityscapes +``` + +## Particular use cases + +Datumaro supports filtering, transformation, merging etc. for all formats +and for the Cityscapes format in particular. Follow +[user manual](../user_manual.md) +to get more information about these operations. + +There are few examples of using Datumaro operations to solve +particular problems with Cityscapes dataset: + +### Example 1. How to load an original Cityscapes dataset ans convert to Pascal VOC + +```bash +datum create -o project +datum add path -p project -f cityscapes ./Cityscapes/ +datum stats -p project +datum export -p final_project -o dataset -f voc --overwrite -- --save-images +``` + +### Example 2. How to create custom Cityscapes-like dataset + +```python +import numpy as np +from datumaro.components.dataset import Dataset +from datumaro.components.extractor import Mask, DatasetItem + +import datumaro.plugins.cityscapes_format as Cityscapes + +label_map = OrderedDict() +label_map['background'] = (0, 0, 0) +label_map['label_1'] = (1, 2, 3) +label_map['label_2'] = (3, 2, 1) +categories = Cityscapes.make_cityscapes_categories(label_map) + +dataset = Dataset.from_iterable([ + DatasetItem(id=1, + image=np.ones((1, 5, 3)), + annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 1]]), label=1, id=1, + attributes={'is_crowd': False}), + Mask(image=np.array([[0, 1, 1, 0, 0]]), label=2, id=2, + attributes={'is_crowd': False}), + ] + ), + ], categories=categories) + +dataset.export('./dataset', format='cityscapes') +``` + +More examples of working with Cityscapes dataset from code can be found in +[tests](../../tests/test_cityscapes_format.py) diff --git a/docs/user_manual.md b/docs/user_manual.md index 5e5a5e22c0..b757c4b1ad 100644 --- a/docs/user_manual.md +++ b/docs/user_manual.md @@ -131,6 +131,10 @@ List of supported formats: - CamVid (`segmentation`) - [Format specification](http://mi.eng.cam.ac.uk/research/projects/VideoRec/CamVid/) - [Dataset example](../tests/assets/camvid_dataset) +- Cityscapes (`segmentation`) + - [Format specification](https://www.cityscapes-dataset.com/dataset-overview/) + - [Dataset example](../tests/assets/cityscapes_dataset) + - [Format documentation](./formats/cityscapes_user_manual.md) - CVAT - [Format specification](https://github.com/opencv/cvat/blob/develop/cvat/apps/documentation/xml_format.md) - [Dataset example](../tests/assets/cvat_dataset) diff --git a/requirements.txt b/requirements.txt index 5cfc7dd4f2..c7318c75b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ PyYAML>=5.3.1 scikit-image>=0.15.0 tensorboardX>=1.8 pandas>=1.1.5 +cityscapesscripts>=2.2.0 diff --git a/setup.py b/setup.py index d1e5ff0152..df3f169c7f 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ def get_requirements(): 'PyYAML', 'scikit-image', 'tensorboardX', + 'cityscapesscripts', ] if strtobool(os.getenv('DATUMARO_HEADLESS', '0').lower()): requirements.append('opencv-python-headless') diff --git a/tests/assets/cityscapes_dataset/gtFine/test/defaultcity/defaultcity_000001_000031_gtFine_instanceIds.png b/tests/assets/cityscapes_dataset/gtFine/test/defaultcity/defaultcity_000001_000031_gtFine_instanceIds.png new file mode 100644 index 0000000000000000000000000000000000000000..9a2cb23ffa86206a2ab160a7130f677f754e7ec7 GIT binary patch literal 76 zcmeAS@N?(olHy`uVBq!ia0vp^tU$~t03;ZaS)bFS$tekG2`6-t6H*cw Y7+8KVJQc0h?*u9HboFyt=akR{04LoMdjJ3c literal 0 HcmV?d00001 diff --git a/tests/assets/cityscapes_dataset/gtFine/test/defaultcity/defaultcity_000001_000032_gtFine_instanceIds.png b/tests/assets/cityscapes_dataset/gtFine/test/defaultcity/defaultcity_000001_000032_gtFine_instanceIds.png new file mode 100644 index 0000000000000000000000000000000000000000..56c008eac13067d18945da44505d2462602153bc GIT binary patch literal 76 zcmeAS@N?(olHy`uVBq!ia0vp^tU$~t03;ZaS)bFS$!n&hBxL+Q^I>iy YBZELaGi#|D%K?xwPgg&ebxsLQ08Wk)i2wiq literal 0 HcmV?d00001 diff --git a/tests/assets/cityscapes_dataset/gtFine/train/defaultcity/defaultcity_000002_000045_gtFine_instanceIds.png b/tests/assets/cityscapes_dataset/gtFine/train/defaultcity/defaultcity_000002_000045_gtFine_instanceIds.png new file mode 100644 index 0000000000000000000000000000000000000000..e658ec33cda661d28014cdf6891a175b988e7cff GIT binary patch literal 76 zcmeAS@N?(olHy`uVBq!ia0vp^tU$~t03;ZaS)bFS$q5N*34OLF4xBl_ Y!@y?6sC^-1(E^Y%Pgg&ebxsLQ06=^akN^Mx literal 0 HcmV?d00001 diff --git a/tests/assets/cityscapes_dataset/gtFine/val/defaultcity/defaultcity_000001_000019_gtFine_instanceIds.png b/tests/assets/cityscapes_dataset/gtFine/val/defaultcity/defaultcity_000001_000019_gtFine_instanceIds.png new file mode 100644 index 0000000000000000000000000000000000000000..d2ccdd1f1a7ced01bd18144787ccba00b5db98a2 GIT binary patch literal 76 zcmeAS@N?(olHy`uVBq!ia0vp^tU$~t03;ZaS)bFS$q5N*i3y27GJ%bO WA&WsMXzu$7AZ4DeelF{r5}E)R;}Hu0 literal 0 HcmV?d00001 diff --git a/tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/test/defaultcity/defaultcity_000001_000031_leftImg8bit.png b/tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/test/defaultcity/defaultcity_000001_000031_leftImg8bit.png new file mode 100644 index 0000000000000000000000000000000000000000..528f10546704be6b339cfe1f577ca4b10ef4f472 GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1j!2~2{&iT9qEaBo9!XcZ?!o;QmFVdQ&MBb@0GX=|x&QzG literal 0 HcmV?d00001 diff --git a/tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/test/defaultcity/defaultcity_000001_000032_leftImg8bit.png b/tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/test/defaultcity/defaultcity_000001_000032_leftImg8bit.png new file mode 100644 index 0000000000000000000000000000000000000000..528f10546704be6b339cfe1f577ca4b10ef4f472 GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1j!2~2{&iT9qEaBo9!XcZ?!o;QmFVdQ&MBb@0GX=|x&QzG literal 0 HcmV?d00001 diff --git a/tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/train/defaultcity/defaultcity_000002_000045_leftImg8bit.png b/tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/train/defaultcity/defaultcity_000002_000045_leftImg8bit.png new file mode 100644 index 0000000000000000000000000000000000000000..528f10546704be6b339cfe1f577ca4b10ef4f472 GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1j!2~2{&iT9qEaBo9!XcZ?!o;QmFVdQ&MBb@0GX=|x&QzG literal 0 HcmV?d00001 diff --git a/tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/val/defaultcity/defaultcity_000001_000019_leftImg8bit.png b/tests/assets/cityscapes_dataset/imgsFine/leftImg8bit/val/defaultcity/defaultcity_000001_000019_leftImg8bit.png new file mode 100644 index 0000000000000000000000000000000000000000..528f10546704be6b339cfe1f577ca4b10ef4f472 GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1j!2~2{&iT9qEaBo9!XcZ?!o;QmFVdQ&MBb@0GX=|x&QzG literal 0 HcmV?d00001 diff --git a/tests/test_cityscapes_format.py b/tests/test_cityscapes_format.py new file mode 100644 index 0000000000..ea9b700f1e --- /dev/null +++ b/tests/test_cityscapes_format.py @@ -0,0 +1,379 @@ +import os.path as osp +from collections import OrderedDict +from functools import partial +from unittest import TestCase + +import datumaro.plugins.cityscapes_format as Cityscapes +import numpy as np +from datumaro.components.extractor import (AnnotationType, DatasetItem, + Extractor, LabelCategories, Mask) +from datumaro.components.dataset import Dataset +from datumaro.plugins.cityscapes_format import CityscapesImporter, CityscapesConverter +from datumaro.util.image import Image +from datumaro.util.test_utils import (TestDir, compare_datasets, + test_save_and_load) + +DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'cityscapes_dataset') + +class CityscapesFormatTest(TestCase): + def test_can_write_and_parse_labelmap(self): + src_label_map = Cityscapes.make_cityscapes_label_map() + + with TestDir() as test_dir: + file_path = osp.join(test_dir, 'label_colors.txt') + + Cityscapes.write_label_map(file_path, src_label_map) + dst_label_map = Cityscapes.parse_label_map(file_path) + + self.assertEqual(src_label_map, dst_label_map) + +class CityscapesImportTest(TestCase): + def test_can_import(self): + source_dataset = Dataset.from_iterable([ + DatasetItem(id='defaultcity_000001_000031', subset='test', + image=np.ones((1, 5, 3)), + annotations=[ + Mask(image=np.array([[1, 1, 0, 0, 0]]), id=3, label=3, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 0, 1, 0, 0]]), id=1, label=27, + attributes={'is_crowd': False}), + Mask(image=np.array([[0, 0, 0, 1, 1]]), id=2, label=27, + attributes={'is_crowd': False}), + ] + ), + DatasetItem(id='defaultcity_000001_000032', subset='test', + image=np.ones((1, 5, 3)), + annotations=[ + Mask(image=np.array([[1, 1, 0, 0, 0]]), id=1, label=31, + attributes={'is_crowd': False}), + Mask(image=np.array([[0, 0, 1, 0, 0]]), id=12, label=12, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 0, 0, 1, 1]]), id=3, label=3, + attributes={'is_crowd': True}), + ] + ), + DatasetItem(id='defaultcity_000002_000045', subset='train', + image=np.ones((1, 5, 3)), + annotations=[ + Mask(image=np.array([[1, 1, 0, 1, 1]]), id=3, label=3, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 0, 1, 0, 0]]), id=1, label=24, + attributes={'is_crowd': False}), + ] + ), + DatasetItem(id='defaultcity_000001_000019', subset = 'val', + image=np.ones((1, 5, 3)), + annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 1]]), id=3, label=3, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 1, 1, 0, 0]]), id=24, label=1, + attributes={'is_crowd': False}), + ] + ), + ], categories=Cityscapes.make_cityscapes_categories()) + + parsed_dataset = Dataset.import_from(DUMMY_DATASET_DIR, 'cityscapes') + + compare_datasets(self, source_dataset, parsed_dataset) + + def test_can_detect_cityscapes(self): + self.assertTrue(CityscapesImporter.detect(DUMMY_DATASET_DIR)) + + +class TestExtractorBase(Extractor): + def _label(self, cityscapes_label): + return self.categories()[AnnotationType.label].find(cityscapes_label)[0] + + def categories(self): + return Cityscapes.make_cityscapes_categories() + +class CityscapesConverterTest(TestCase): + def _test_save_and_load(self, source_dataset, converter, test_dir, + target_dataset=None, importer_args=None, **kwargs): + return test_save_and_load(self, source_dataset, converter, test_dir, + importer='cityscapes', + target_dataset=target_dataset, importer_args=importer_args, **kwargs) + + def test_can_save_cityscapes_segm(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='defaultcity_1_2', subset='test', + image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[0, 0, 0, 1, 0]]), label=3, id=3, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 1, 1, 0, 0]]), label=24, id=1, + attributes={'is_crowd': False}), + Mask(image=np.array([[1, 0, 0, 0, 1]]), label=15, id=15, + attributes={'is_crowd': True}), + ]), + DatasetItem(id='defaultcity_3', subset='val', + image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[1, 1, 0, 1, 1]]), label=3, id=3, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 0, 1, 0, 0]]), label=5, id=5, + attributes={'is_crowd': True}), + ]), + ]) + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + partial(CityscapesConverter.convert, label_map='cityscapes', + save_images=True), test_dir) + + def test_can_save_cityscapes_segm_unpainted(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='defaultcity_1_2', subset='test', + image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[0, 0, 0, 1, 0]]), label=3, id=3, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 1, 1, 0, 0]]), label=24, id=1, + attributes={'is_crowd': False}), + Mask(image=np.array([[1, 0, 0, 0, 1]]), label=15, id=15, + attributes={'is_crowd': True}), + ]), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + partial(CityscapesConverter.convert, label_map='cityscapes', + save_images=True, apply_colormap=False), test_dir) + + def test_can_save_cityscapes_dataset_with_no_subsets(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='defaultcity_1_2', + image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 0]]), label=0, id=0, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 1, 1, 0, 1]]), label=3, id=3, + attributes={'is_crowd': True}), + ]), + + DatasetItem(id='defaultcity_1_3', + image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[1, 1, 0, 1, 0]]), label=1, id=1, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 0, 1, 0, 1]]), label=2, id=2, + attributes={'is_crowd': True}), + ]), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + partial(CityscapesConverter.convert, label_map='cityscapes', + save_images=True), test_dir) + + def test_can_save_cityscapes_dataset_without_frame_and_sequence(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='justcity', subset='test', + image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 1]]), label=3, id=3, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 1, 1, 0, 0]]), label=24, id=1, + attributes={'is_crowd': False}), + ]), + ]) + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + partial(CityscapesConverter.convert, label_map='cityscapes', + save_images=True), test_dir) + + def test_can_save_dataset_with_cyrillic_and_spaces_in_filename(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='кириллица с пробелом', + image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 1]]), label=3, id=3, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 1, 1, 0, 0]]), label=24, id=1, + attributes={'is_crowd': False}), + ]), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + partial(CityscapesConverter.convert, label_map='cityscapes', + save_images=True), test_dir) + + def test_can_save_cityscapes_dataset_with_strange_id(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='a/b/1', subset='test', + image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 1]]), label=3, id=3, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 1, 1, 0, 0]]), label=24, id=1, + attributes={'is_crowd': False}), + ]), + ]) + + class DstExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='a_b_1', subset='test', + image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 1]]), label=3, id=3, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 1, 1, 0, 0]]), label=24, id=1, + attributes={'is_crowd': False}), + ]), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + partial(CityscapesConverter.convert, label_map='cityscapes', + save_images=True), test_dir, target_dataset=DstExtractor()) + + def test_can_save_with_no_masks(self): + class TestExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='city_1_2', subset='test', + image=np.ones((2, 5, 3)), + ), + ]) + + with TestDir() as test_dir: + self._test_save_and_load(TestExtractor(), + partial(CityscapesConverter.convert, label_map='cityscapes', + save_images=True), test_dir) + + def test_dataset_with_source_labelmap_undefined(self): + class SrcExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 1]]), label=1, id=1, + attributes={'is_crowd': False}), + Mask(image=np.array([[0, 1, 1, 0, 0]]), label=2, id=2, + attributes={'is_crowd': False}), + ]) + + def categories(self): + label_cat = LabelCategories() + label_cat.add('background') + label_cat.add('Label_1') + label_cat.add('label_2') + return { + AnnotationType.label: label_cat, + } + + class DstExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 1]]), + attributes={'is_crowd': False}, id=1, + label=self._label('Label_1')), + Mask(image=np.array([[0, 1, 1, 0, 0]]), + attributes={'is_crowd': False}, id=2, + label=self._label('label_2')), + ]) + + def categories(self): + label_map = OrderedDict() + label_map['background'] = None + label_map['Label_1'] = None + label_map['label_2'] = None + return Cityscapes.make_cityscapes_categories(label_map) + + with TestDir() as test_dir: + self._test_save_and_load(SrcExtractor(), + partial(CityscapesConverter.convert, label_map='source', + save_images=True), test_dir, target_dataset=DstExtractor()) + + def test_dataset_with_source_labelmap_defined(self): + class SrcExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 1]]), label=1, id=1, + attributes={'is_crowd': False}), + Mask(image=np.array([[0, 1, 1, 0, 0]]), label=2, id=2, + attributes={'is_crowd': False}), + ]) + + def categories(self): + label_map = OrderedDict() + label_map['background'] = (0, 0, 0) + label_map['label_1'] = (1, 2, 3) + label_map['label_2'] = (3, 2, 1) + return Cityscapes.make_cityscapes_categories(label_map) + + class DstExtractor(TestExtractorBase): + def __iter__(self): + yield DatasetItem(id=1, image=np.ones((1, 5, 3)), annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 1]]), + attributes={'is_crowd': False}, id=1, + label=self._label('label_1')), + Mask(image=np.array([[0, 1, 1, 0, 0]]), + attributes={'is_crowd': False}, id=2, + label=self._label('label_2')), + ]) + + def categories(self): + label_map = OrderedDict() + label_map['background'] = (0, 0, 0) + label_map['label_1'] = (1, 2, 3) + label_map['label_2'] = (3, 2, 1) + return Cityscapes.make_cityscapes_categories(label_map) + + with TestDir() as test_dir: + self._test_save_and_load(SrcExtractor(), + partial(CityscapesConverter.convert, label_map='source', + save_images=True), test_dir, target_dataset=DstExtractor()) + + def test_can_save_and_load_image_with_arbitrary_extension(self): + class SrcExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='q/1', image=Image(path='q/1.JPEG', + data=np.zeros((4, 3, 3)))), + + DatasetItem(id='a/b/c/2', image=Image( + path='a/b/c/2.bmp', data=np.ones((1, 5, 3)) + ), annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 0]]), label=0, id=0, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 1, 1, 0, 1]]), label=1, id=1, + attributes={'is_crowd': True}), + ]), + ]) + + def categories(self): + label_map = OrderedDict() + label_map['a'] = None + label_map['b'] = None + return Cityscapes.make_cityscapes_categories(label_map) + + class DstExtractor(TestExtractorBase): + def __iter__(self): + return iter([ + DatasetItem(id='q_1', image=Image(path='q_1.JPEG', + data=np.zeros((4, 3, 3)))), + + DatasetItem(id='a_b_c_2', image=Image( + path='a_b_c_2.bmp', data=np.ones((1, 5, 3)) + ), annotations=[ + Mask(image=np.array([[1, 0, 0, 1, 0]]), label=0, id=0, + attributes={'is_crowd': True}), + Mask(image=np.array([[0, 1, 1, 0, 1]]), label=1, id=1, + attributes={'is_crowd': True}), + ]), + ]) + + def categories(self): + label_map = OrderedDict() + label_map['a'] = None + label_map['b'] = None + return Cityscapes.make_cityscapes_categories(label_map) + + with TestDir() as test_dir: + self._test_save_and_load(SrcExtractor(), + partial(CityscapesConverter.convert, save_images=True), + test_dir, require_images=True, + target_dataset=DstExtractor()) From c8380b1e996e04a8b50cfbf7ab288710d40314a0 Mon Sep 17 00:00:00 2001 From: "Maslova, Zoya" Date: Tue, 25 May 2021 09:25:45 +0300 Subject: [PATCH 2/6] fixes after review --- datumaro/components/extractor.py | 3 +- datumaro/plugins/cityscapes_format.py | 94 ++++++++++++++++---------- datumaro/util/image.py | 3 + docs/formats/cityscapes_user_manual.md | 10 +-- requirements.txt | 1 - setup.py | 1 - tests/test_cityscapes_format.py | 63 +++++------------ 7 files changed, 84 insertions(+), 91 deletions(-) diff --git a/datumaro/components/extractor.py b/datumaro/components/extractor.py index 785861d363..ca57193b52 100644 --- a/datumaro/components/extractor.py +++ b/datumaro/components/extractor.py @@ -674,7 +674,8 @@ def __call__(self, path, **extra_params): def _find_sources_recursive(cls, path, ext, extractor_name, filename='*', dirname='', file_filter=None, max_depth=3): if (path.endswith(ext) and osp.isfile(path)) or \ - (ext == '' and osp.isdir(path) and not dirname == '' and dirname in path): + (not ext and osp.isdir(path) and dirname and \ + osp.normpath(dirname) in osp.abspath(path)): sources = [{'url': path, 'format': extractor_name}] else: sources = [] diff --git a/datumaro/plugins/cityscapes_format.py b/datumaro/plugins/cityscapes_format.py index b3ab0d1a14..219a01cf59 100644 --- a/datumaro/plugins/cityscapes_format.py +++ b/datumaro/plugins/cityscapes_format.py @@ -8,12 +8,10 @@ import os.path as osp from collections import OrderedDict from enum import Enum +from glob import iglob import numpy as np -from cityscapesscripts.helpers.labels import labels as CityscapesLabels -from glob import iglob - from datumaro.components.converter import Converter from datumaro.components.extractor import (AnnotationType, CompiledMask, DatasetItem, Importer, LabelCategories, Mask, @@ -23,31 +21,62 @@ from datumaro.util.image import save_image, load_image from datumaro.util.mask_tools import generate_colormap, paint_mask +CityscapesLabelMap = OrderedDict([ + ('unlabeled', (0, 0, 0)), + ('egovehicle', (0, 0, 0)), + ('rectificationborder', (0, 0, 0)), + ('outofroi', (0, 0, 0)), + ('static', (0, 0, 0)), + ('dynamic', (111, 74, 0)), + ('ground', (81, 0, 81)), + ('road', (128, 64, 128)), + ('sidewalk', (244, 35, 232)), + ('parking', (250, 170, 160)), + ('railtrack', (230, 150, 140)), + ('building', (70, 70, 70)), + ('wall', (102, 102, 156)), + ('fence', (190, 153, 153)), + ('guardrail', (180, 165, 180)), + ('bridge', (150, 100, 100)), + ('tunnel', (150, 120, 90)), + ('pole', (153, 153, 153)), + ('polegroup', (153, 153, 153)), + ('trafficlight', (250, 170, 30)), + ('trafficsign', (220, 220, 0)), + ('vegetation', (107, 142, 35)), + ('terrain', (152, 251, 152)), + ('sky', (70, 130, 180)), + ('person', (220, 20, 60)), + ('rider', (255, 0, 0)), + ('car', (0, 0, 142)), + ('truck', (0, 0, 70)), + ('bus', (0, 60, 100)), + ('caravan', (0, 0, 90)), + ('trailer', (0, 0, 110)), + ('train', (0, 80, 100)), + ('motorcycle', (0, 0, 230)), + ('bicycle', (119, 11, 32)), + ('licenseplate', (0, 0, 142)), +]) class CityscapesPath: GT_FINE_DIR = 'gtFine' IMGS_FINE_DIR = 'imgsFine' ORIGINAL_IMAGE_DIR = 'leftImg8bit' - ORIGINAL_IMAGE = '_'+ORIGINAL_IMAGE_DIR+'.png' + ORIGINAL_IMAGE = '_%s.png' % ORIGINAL_IMAGE_DIR INSTANCES_IMAGE = '_instanceIds.png' COLOR_IMAGE = '_color.png' LABELIDS_IMAGE = '_labelIds.png' LABELMAP_FILE = 'label_colors.txt' -def make_cityscapes_label_map(): - label_map = OrderedDict() - for label in CityscapesLabels: - label_map[label.name.replace(' ', '_')] = label.color - return label_map - def make_cityscapes_categories(label_map=None): if label_map is None: - label_map = make_cityscapes_label_map() + label_map = CityscapesLabelMap categories = {} label_categories = LabelCategories() - for label, desc in label_map.items(): + for label in label_map: label_categories.add(label) categories[AnnotationType.label] = label_categories @@ -119,19 +148,20 @@ def _load_categories(self, path): if osp.isfile(label_map_path): label_map = parse_label_map(label_map_path) else: - label_map = make_cityscapes_label_map() + label_map = CityscapesLabelMap self._labels = [label for label in label_map] return make_cityscapes_categories(label_map) def _load_items(self): items = {} + annotations_path = osp.normpath(osp.join(self._path, '../../../', + CityscapesPath.GT_FINE_DIR, self._subset)) - for image_path in iglob(osp.join(self._path, '*', '*'+CityscapesPath.ORIGINAL_IMAGE), recursive=True): - city_name, sample_id = self._get_city_and_sample(image_path) - instances_path = osp.join(self._path, '../../../', - CityscapesPath.GT_FINE_DIR, self._subset, city_name, - sample_id+'_'+CityscapesPath.GT_FINE_DIR+CityscapesPath.INSTANCES_IMAGE) + for image_path in iglob(osp.join(self._path, '**', '*'+CityscapesPath.ORIGINAL_IMAGE), recursive=True): + sample_id = osp.relpath(image_path, self._path).replace(CityscapesPath.ORIGINAL_IMAGE, '') anns = [] + instances_path = osp.join(annotations_path, sample_id + '_' + + CityscapesPath.GT_FINE_DIR+CityscapesPath.INSTANCES_IMAGE) if osp.isfile(instances_path): instances_mask = load_image(instances_path, dtype=np.int32) segm_ids = np.unique(instances_mask) @@ -155,22 +185,14 @@ def _load_items(self): def _lazy_extract_mask(mask, c): return lambda: mask == c - def _get_city_and_sample(self, full_path): - related_path = osp.relpath(full_path, self._path) - city_name = related_path.split('/')[0] - - sample_id = osp.basename(full_path) - sample_id = sample_id.split(CityscapesPath.ORIGINAL_IMAGE)[0] - - return city_name, sample_id - class CityscapesImporter(Importer): @classmethod def find_sources(cls, path): return cls._find_sources_recursive(path, '', 'cityscapes', dirname=osp.join(CityscapesPath.IMGS_FINE_DIR, - CityscapesPath.ORIGINAL_IMAGE_DIR), max_depth=1) + CityscapesPath.ORIGINAL_IMAGE_DIR), + max_depth=1) LabelmapType = Enum('LabelmapType', ['cityscapes', 'source']) @@ -214,16 +236,14 @@ def apply(self): for subset_name, subset in self._extractor.subsets().items(): for item in subset: - item.id = item.id.replace('/', '_') - city_name = item.id.split('_')[0] image_path = osp.join(CityscapesPath.IMGS_FINE_DIR, CityscapesPath.ORIGINAL_IMAGE_DIR, subset_name, - city_name, item.id+CityscapesPath.ORIGINAL_IMAGE) + item.id+CityscapesPath.ORIGINAL_IMAGE) if self._save_images: self._save_image(item, osp.join(self._save_dir, image_path)) common_folder_path = osp.join(CityscapesPath.GT_FINE_DIR, - subset_name, city_name) + subset_name) masks = [a for a in item.annotations if a.type == AnnotationType.mask] @@ -235,12 +255,12 @@ def apply(self): for m in masks]) color_mask_path = osp.join(common_folder_path, common_image_name+CityscapesPath.COLOR_IMAGE) - self.save_segm(osp.join(self._save_dir, color_mask_path), + self.save_mask(osp.join(self._save_dir, color_mask_path), compiled_class_mask.class_mask) labelids_mask_path = osp.join(common_folder_path, common_image_name+CityscapesPath.LABELIDS_IMAGE) - self.save_segm(osp.join(self._save_dir, labelids_mask_path), + self.save_mask(osp.join(self._save_dir, labelids_mask_path), compiled_class_mask.class_mask, apply_colormap=False, dtype=np.int32) @@ -249,7 +269,7 @@ def apply(self): else m.label*1000+m.id for m in masks]) inst_path = osp.join(common_folder_path, common_image_name+CityscapesPath.INSTANCES_IMAGE) - self.save_segm(osp.join(self._save_dir, inst_path), + self.save_mask(osp.join(self._save_dir, inst_path), compiled_instance_mask.class_mask, apply_colormap=False, dtype=np.int32) self.save_label_map() @@ -261,7 +281,7 @@ def save_label_map(self): def _load_categories(self, label_map_source): if label_map_source == LabelmapType.cityscapes.name: # use the default Cityscapes colormap - label_map = make_cityscapes_label_map() + label_map = CityscapesLabelMap elif label_map_source == LabelmapType.source.name and \ AnnotationType.mask not in self._extractor.categories(): @@ -321,7 +341,7 @@ def _make_label_id_map(self): return map_id - def save_segm(self, path, mask, colormap=None, apply_colormap=True, + def save_mask(self, path, mask, colormap=None, apply_colormap=True, dtype=np.uint8): if self._apply_colormap and apply_colormap: if colormap is None: diff --git a/datumaro/util/image.py b/datumaro/util/image.py index c23049f78d..e1acd4792d 100644 --- a/datumaro/util/image.py +++ b/datumaro/util/image.py @@ -65,6 +65,9 @@ def save_image(path, image, create_dir=False, dtype=np.uint8, **kwargs): if not kwargs: kwargs = {} + # NOTE: OpenCV documentation says "If the image format is not supported, + # the image will be converted to 8-bit unsigned and saved that way". + # Conversion from np.int32 to np.uint8 is not working properly backend = _IMAGE_BACKEND if dtype == np.int32: backend = _IMAGE_BACKENDS.PIL diff --git a/docs/formats/cityscapes_user_manual.md b/docs/formats/cityscapes_user_manual.md index 49597fa981..f8e98b71b3 100644 --- a/docs/formats/cityscapes_user_manual.md +++ b/docs/formats/cityscapes_user_manual.md @@ -40,7 +40,7 @@ Cityscapes dataset directory should have the following structure: │ ├── leftImg8bit │ │ ├── │ │ | ├── {city1} - │ │ │ | ├── {city1}_{seq:0>6}_{frame:0>6}_leftImg8bit.png + │ │ │ | ├── {city1}_{seq:[0...6]}_{frame:[0...6]}_leftImg8bit.png │ │ │ │ └── ... │ │ | ├── {city2} │ │ │ └── ... @@ -48,16 +48,16 @@ Cityscapes dataset directory should have the following structure: ├── gtFine/ │ ├── │ │ ├── {city1} - │ │ | ├── {city1}_{seq:0>6}_{frame:0>6}_gtFine_color.png - │ │ | ├── {city1}_{seq:0>6}_{frame:0>6}_gtFine_instanceIds.png - │ │ | ├── {city1}_{seq:0>6}_{frame:0>6}_gtFine_labelIds.png + │ │ | ├── {city1}_{seq:[0...6]}_{frame:[0...6]}_gtFine_color.png + │ │ | ├── {city1}_{seq:[0...6]}_{frame:[0...6]}_gtFine_instanceIds.png + │ │ | ├── {city1}_{seq:[0...6]}_{frame:[0...6]}_gtFine_labelIds.png │ │ │ └── ... │ │ ├── {city2} │ │ └── ... │ └── ... ``` -Annorated files description: +Annotated files description: 1. *leftImg8bit.png - left images in 8-bit LDR format 1. *color.png - class labels are encoded by its color 1. *instanceIds.png - class and instance labels are encoded by an instance ID. diff --git a/requirements.txt b/requirements.txt index c7318c75b1..5cfc7dd4f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,3 @@ PyYAML>=5.3.1 scikit-image>=0.15.0 tensorboardX>=1.8 pandas>=1.1.5 -cityscapesscripts>=2.2.0 diff --git a/setup.py b/setup.py index df3f169c7f..d1e5ff0152 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,6 @@ def get_requirements(): 'PyYAML', 'scikit-image', 'tensorboardX', - 'cityscapesscripts', ] if strtobool(os.getenv('DATUMARO_HEADLESS', '0').lower()): requirements.append('opencv-python-headless') diff --git a/tests/test_cityscapes_format.py b/tests/test_cityscapes_format.py index ea9b700f1e..fd23de9d76 100644 --- a/tests/test_cityscapes_format.py +++ b/tests/test_cityscapes_format.py @@ -8,16 +8,18 @@ from datumaro.components.extractor import (AnnotationType, DatasetItem, Extractor, LabelCategories, Mask) from datumaro.components.dataset import Dataset -from datumaro.plugins.cityscapes_format import CityscapesImporter, CityscapesConverter +from datumaro.plugins.cityscapes_format import (CityscapesImporter, + CityscapesConverter) from datumaro.util.image import Image from datumaro.util.test_utils import (TestDir, compare_datasets, test_save_and_load) -DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'cityscapes_dataset') +DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', + 'cityscapes_dataset') class CityscapesFormatTest(TestCase): def test_can_write_and_parse_labelmap(self): - src_label_map = Cityscapes.make_cityscapes_label_map() + src_label_map = Cityscapes.CityscapesLabelMap with TestDir() as test_dir: file_path = osp.join(test_dir, 'label_colors.txt') @@ -30,7 +32,8 @@ def test_can_write_and_parse_labelmap(self): class CityscapesImportTest(TestCase): def test_can_import(self): source_dataset = Dataset.from_iterable([ - DatasetItem(id='defaultcity_000001_000031', subset='test', + DatasetItem(id='defaultcity/defaultcity_000001_000031', + subset='test', image=np.ones((1, 5, 3)), annotations=[ Mask(image=np.array([[1, 1, 0, 0, 0]]), id=3, label=3, @@ -41,7 +44,8 @@ def test_can_import(self): attributes={'is_crowd': False}), ] ), - DatasetItem(id='defaultcity_000001_000032', subset='test', + DatasetItem(id='defaultcity/defaultcity_000001_000032', + subset='test', image=np.ones((1, 5, 3)), annotations=[ Mask(image=np.array([[1, 1, 0, 0, 0]]), id=1, label=31, @@ -52,7 +56,8 @@ def test_can_import(self): attributes={'is_crowd': True}), ] ), - DatasetItem(id='defaultcity_000002_000045', subset='train', + DatasetItem(id='defaultcity/defaultcity_000002_000045', + subset='train', image=np.ones((1, 5, 3)), annotations=[ Mask(image=np.array([[1, 1, 0, 1, 1]]), id=3, label=3, @@ -61,7 +66,8 @@ def test_can_import(self): attributes={'is_crowd': False}), ] ), - DatasetItem(id='defaultcity_000001_000019', subset = 'val', + DatasetItem(id='defaultcity/defaultcity_000001_000019', + subset = 'val', image=np.ones((1, 5, 3)), annotations=[ Mask(image=np.array([[1, 0, 0, 1, 1]]), id=3, label=3, @@ -214,22 +220,10 @@ def __iter__(self): ]), ]) - class DstExtractor(TestExtractorBase): - def __iter__(self): - return iter([ - DatasetItem(id='a_b_1', subset='test', - image=np.ones((1, 5, 3)), annotations=[ - Mask(image=np.array([[1, 0, 0, 1, 1]]), label=3, id=3, - attributes={'is_crowd': True}), - Mask(image=np.array([[0, 1, 1, 0, 0]]), label=24, id=1, - attributes={'is_crowd': False}), - ]), - ]) - with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), partial(CityscapesConverter.convert, label_map='cityscapes', - save_images=True), test_dir, target_dataset=DstExtractor()) + save_images=True), test_dir) def test_can_save_with_no_masks(self): class TestExtractor(TestExtractorBase): @@ -328,7 +322,7 @@ def categories(self): save_images=True), test_dir, target_dataset=DstExtractor()) def test_can_save_and_load_image_with_arbitrary_extension(self): - class SrcExtractor(TestExtractorBase): + class TestExtractor(TestExtractorBase): def __iter__(self): return iter([ DatasetItem(id='q/1', image=Image(path='q/1.JPEG', @@ -350,30 +344,7 @@ def categories(self): label_map['b'] = None return Cityscapes.make_cityscapes_categories(label_map) - class DstExtractor(TestExtractorBase): - def __iter__(self): - return iter([ - DatasetItem(id='q_1', image=Image(path='q_1.JPEG', - data=np.zeros((4, 3, 3)))), - - DatasetItem(id='a_b_c_2', image=Image( - path='a_b_c_2.bmp', data=np.ones((1, 5, 3)) - ), annotations=[ - Mask(image=np.array([[1, 0, 0, 1, 0]]), label=0, id=0, - attributes={'is_crowd': True}), - Mask(image=np.array([[0, 1, 1, 0, 1]]), label=1, id=1, - attributes={'is_crowd': True}), - ]), - ]) - - def categories(self): - label_map = OrderedDict() - label_map['a'] = None - label_map['b'] = None - return Cityscapes.make_cityscapes_categories(label_map) - with TestDir() as test_dir: - self._test_save_and_load(SrcExtractor(), + self._test_save_and_load(TestExtractor(), partial(CityscapesConverter.convert, save_images=True), - test_dir, require_images=True, - target_dataset=DstExtractor()) + test_dir, require_images=True) From 9163bb44b4da09a03a7366bdbc1b16e08d5e6aea Mon Sep 17 00:00:00 2001 From: "Maslova, Zoya" Date: Tue, 25 May 2021 09:35:47 +0300 Subject: [PATCH 3/6] update for static code analysis --- datumaro/plugins/cityscapes_format.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datumaro/plugins/cityscapes_format.py b/datumaro/plugins/cityscapes_format.py index 219a01cf59..feebfc429f 100644 --- a/datumaro/plugins/cityscapes_format.py +++ b/datumaro/plugins/cityscapes_format.py @@ -169,13 +169,13 @@ def _load_items(self): if segm_id < 1000: semanticId = segm_id isCrowd = True - id = segm_id + ann_id = segm_id else: semanticId = segm_id // 1000 isCrowd = False - id = segm_id % 1000 + ann_id = segm_id % 1000 anns.append(Mask(image=self._lazy_extract_mask(instances_mask, segm_id), - label=semanticId, id=id, + label=semanticId, id=ann_id, attributes = { 'is_crowd': isCrowd })) items[sample_id] = DatasetItem(id=sample_id, subset=self._subset, image=image_path, annotations=anns) From 75a726e5a72ad14aa6dbb2a1ac42e0b364f32525 Mon Sep 17 00:00:00 2001 From: "Maslova, Zoya" Date: Tue, 25 May 2021 12:54:02 +0300 Subject: [PATCH 4/6] make more complex check for sources --- datumaro/components/extractor.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/datumaro/components/extractor.py b/datumaro/components/extractor.py index ca57193b52..bdf011c706 100644 --- a/datumaro/components/extractor.py +++ b/datumaro/components/extractor.py @@ -673,9 +673,21 @@ def __call__(self, path, **extra_params): @classmethod def _find_sources_recursive(cls, path, ext, extractor_name, filename='*', dirname='', file_filter=None, max_depth=3): - if (path.endswith(ext) and osp.isfile(path)) or \ - (not ext and osp.isdir(path) and dirname and \ - osp.normpath(dirname) in osp.abspath(path)): + + def check_for_sources(path, ext, dirname): + if path.endswith(ext) and osp.isfile(path): + return True + if not ext and osp.isdir(path) and dirname: + dirname = osp.normpath(dirname) + path = osp.abspath(path) + if dirname in path: + subpath = path.split(dirname)[0] + if osp.isdir(subpath) and \ + osp.isdir(osp.join(subpath, dirname)): + return True + return False + + if check_for_sources(path, ext, dirname): sources = [{'url': path, 'format': extractor_name}] else: sources = [] From d8f754d90ef58c3ac21dc946671a4ada27e1dd39 Mon Sep 17 00:00:00 2001 From: "Maslova, Zoya" Date: Tue, 25 May 2021 15:09:17 +0300 Subject: [PATCH 5/6] update check for sources --- datumaro/components/extractor.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/datumaro/components/extractor.py b/datumaro/components/extractor.py index bdf011c706..b00b2ecc0c 100644 --- a/datumaro/components/extractor.py +++ b/datumaro/components/extractor.py @@ -7,6 +7,7 @@ from glob import iglob from typing import Iterable, List, Dict, Optional import numpy as np +import os import os.path as osp import attr @@ -674,20 +675,9 @@ def __call__(self, path, **extra_params): def _find_sources_recursive(cls, path, ext, extractor_name, filename='*', dirname='', file_filter=None, max_depth=3): - def check_for_sources(path, ext, dirname): - if path.endswith(ext) and osp.isfile(path): - return True - if not ext and osp.isdir(path) and dirname: - dirname = osp.normpath(dirname) - path = osp.abspath(path) - if dirname in path: - subpath = path.split(dirname)[0] - if osp.isdir(subpath) and \ - osp.isdir(osp.join(subpath, dirname)): - return True - return False - - if check_for_sources(path, ext, dirname): + if (path.endswith(ext) and osp.isfile(path)) or \ + (not ext and osp.isdir(path) and dirname and \ + os.sep + osp.normpath(dirname) + os.sep in osp.abspath(path) + os.sep): sources = [{'url': path, 'format': extractor_name}] else: sources = [] From a268b54cedf160383352d8f0a9e0fe88f470b8d9 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 25 May 2021 16:13:56 +0300 Subject: [PATCH 6/6] fix formatting --- datumaro/components/extractor.py | 5 +- datumaro/plugins/cityscapes_format.py | 67 +++++++++++++++------------ 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/datumaro/components/extractor.py b/datumaro/components/extractor.py index b00b2ecc0c..ebeaf01ecc 100644 --- a/datumaro/components/extractor.py +++ b/datumaro/components/extractor.py @@ -676,8 +676,9 @@ def _find_sources_recursive(cls, path, ext, extractor_name, filename='*', dirname='', file_filter=None, max_depth=3): if (path.endswith(ext) and osp.isfile(path)) or \ - (not ext and osp.isdir(path) and dirname and \ - os.sep + osp.normpath(dirname) + os.sep in osp.abspath(path) + os.sep): + (not ext and osp.isdir(path) and dirname and \ + os.sep + osp.normpath(dirname) + os.sep in \ + osp.abspath(path) + os.sep): sources = [{'url': path, 'format': extractor_name}] else: sources = [] diff --git a/datumaro/plugins/cityscapes_format.py b/datumaro/plugins/cityscapes_format.py index feebfc429f..34aca8bd1c 100644 --- a/datumaro/plugins/cityscapes_format.py +++ b/datumaro/plugins/cityscapes_format.py @@ -21,6 +21,7 @@ from datumaro.util.image import save_image, load_image from datumaro.util.mask_tools import generate_colormap, paint_mask + CityscapesLabelMap = OrderedDict([ ('unlabeled', (0, 0, 0)), ('egovehicle', (0, 0, 0)), @@ -157,11 +158,14 @@ def _load_items(self): annotations_path = osp.normpath(osp.join(self._path, '../../../', CityscapesPath.GT_FINE_DIR, self._subset)) - for image_path in iglob(osp.join(self._path, '**', '*'+CityscapesPath.ORIGINAL_IMAGE), recursive=True): - sample_id = osp.relpath(image_path, self._path).replace(CityscapesPath.ORIGINAL_IMAGE, '') + for image_path in iglob( + osp.join(self._path, '**', '*' + CityscapesPath.ORIGINAL_IMAGE), + recursive=True): + sample_id = osp.relpath(image_path, self._path) \ + .replace(CityscapesPath.ORIGINAL_IMAGE, '') anns = [] instances_path = osp.join(annotations_path, sample_id + '_' + - CityscapesPath.GT_FINE_DIR+CityscapesPath.INSTANCES_IMAGE) + CityscapesPath.GT_FINE_DIR + CityscapesPath.INSTANCES_IMAGE) if osp.isfile(instances_path): instances_mask = load_image(instances_path, dtype=np.int32) segm_ids = np.unique(instances_mask) @@ -174,7 +178,8 @@ def _load_items(self): semanticId = segm_id // 1000 isCrowd = False ann_id = segm_id % 1000 - anns.append(Mask(image=self._lazy_extract_mask(instances_mask, segm_id), + anns.append(Mask( + image=self._lazy_extract_mask(instances_mask, segm_id), label=semanticId, id=ann_id, attributes = { 'is_crowd': isCrowd })) items[sample_id] = DatasetItem(id=sample_id, subset=self._subset, @@ -238,7 +243,7 @@ def apply(self): for item in subset: image_path = osp.join(CityscapesPath.IMGS_FINE_DIR, CityscapesPath.ORIGINAL_IMAGE_DIR, subset_name, - item.id+CityscapesPath.ORIGINAL_IMAGE) + item.id + CityscapesPath.ORIGINAL_IMAGE) if self._save_images: self._save_image(item, osp.join(self._save_dir, image_path)) @@ -247,31 +252,33 @@ def apply(self): masks = [a for a in item.annotations if a.type == AnnotationType.mask] - if masks: - common_image_name = item.id+'_'+CityscapesPath.GT_FINE_DIR - - compiled_class_mask = CompiledMask.from_instance_masks(masks, - instance_labels=[self._label_id_mapping(m.label) - for m in masks]) - color_mask_path = osp.join(common_folder_path, - common_image_name+CityscapesPath.COLOR_IMAGE) - self.save_mask(osp.join(self._save_dir, color_mask_path), - compiled_class_mask.class_mask) - - labelids_mask_path = osp.join(common_folder_path, - common_image_name+CityscapesPath.LABELIDS_IMAGE) - self.save_mask(osp.join(self._save_dir, labelids_mask_path), - compiled_class_mask.class_mask, apply_colormap=False, - dtype=np.int32) - - compiled_instance_mask = CompiledMask.from_instance_masks(masks, - instance_labels=[m.id if m.attributes.get('is_crowd', True) - else m.label*1000+m.id for m in masks]) - inst_path = osp.join(common_folder_path, - common_image_name+CityscapesPath.INSTANCES_IMAGE) - self.save_mask(osp.join(self._save_dir, inst_path), - compiled_instance_mask.class_mask, apply_colormap=False, - dtype=np.int32) + if not masks: + continue + + common_image_name = item.id + '_' + CityscapesPath.GT_FINE_DIR + + compiled_class_mask = CompiledMask.from_instance_masks(masks, + instance_labels=[self._label_id_mapping(m.label) + for m in masks]) + color_mask_path = osp.join(common_folder_path, + common_image_name + CityscapesPath.COLOR_IMAGE) + self.save_mask(osp.join(self._save_dir, color_mask_path), + compiled_class_mask.class_mask) + + labelids_mask_path = osp.join(common_folder_path, + common_image_name + CityscapesPath.LABELIDS_IMAGE) + self.save_mask(osp.join(self._save_dir, labelids_mask_path), + compiled_class_mask.class_mask, apply_colormap=False, + dtype=np.int32) + + compiled_instance_mask = CompiledMask.from_instance_masks(masks, + instance_labels=[m.id if m.attributes.get('is_crowd', True) + else m.label * 1000 + m.id for m in masks]) + inst_path = osp.join(common_folder_path, + common_image_name + CityscapesPath.INSTANCES_IMAGE) + self.save_mask(osp.join(self._save_dir, inst_path), + compiled_instance_mask.class_mask, apply_colormap=False, + dtype=np.int32) self.save_label_map() def save_label_map(self):