From 1ccf7b0bf46b5f6645937e0d3b03ff52035e3495 Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Fri, 18 Feb 2022 14:47:21 +0000 Subject: [PATCH 1/6] Implementation for cityscapes in proto datasets --- .../prototype/datasets/_builtin/__init__.py | 1 + .../prototype/datasets/_builtin/cityscapes.py | 182 ++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 torchvision/prototype/datasets/_builtin/cityscapes.py diff --git a/torchvision/prototype/datasets/_builtin/__init__.py b/torchvision/prototype/datasets/_builtin/__init__.py index 9fdfca904f5..98757da7033 100644 --- a/torchvision/prototype/datasets/_builtin/__init__.py +++ b/torchvision/prototype/datasets/_builtin/__init__.py @@ -1,6 +1,7 @@ from .caltech import Caltech101, Caltech256 from .celeba import CelebA from .cifar import Cifar10, Cifar100 +from .cityscapes import Cityscapes from .clevr import CLEVR from .coco import Coco from .cub200 import CUB200 diff --git a/torchvision/prototype/datasets/_builtin/cityscapes.py b/torchvision/prototype/datasets/_builtin/cityscapes.py new file mode 100644 index 00000000000..0fbae6ec464 --- /dev/null +++ b/torchvision/prototype/datasets/_builtin/cityscapes.py @@ -0,0 +1,182 @@ +from functools import partial +from pathlib import Path +from typing import Any, Dict, List + +from torchdata.datapipes.iter import IterDataPipe, Mapper, Filter, Demultiplexer, IterKeyZipper, JsonParser +from torchvision.prototype.datasets.utils import ( + Dataset, + DatasetInfo, + DatasetConfig, + ManualDownloadResource, + OnlineResource, +) +from torchvision.prototype.datasets.utils._internal import INFINITE_BUFFER_SIZE +from torchvision.prototype.features import EncodedImage + + +class CityscapesDatasetInfo(DatasetInfo): + def __init__(self, *args: Any, **kwargs: Any): + super().__init__(*args, **kwargs) + self._configs = tuple( + config + for config in self._configs + if not ( + (config.split == "test" and config.mode == "coarse") + or (config.split == "train_extra" and config.mode == "fine") + ) + ) + + def make_config(self, **options: Any) -> DatasetConfig: + config = super().make_config(**options) + if config.split == "test" and config.mode == "coarse": + raise ValueError("`split='test'` is only available for `mode='fine'`") + if config.split == "train_extra" and config.mode == "fine": + raise ValueError("`split='train_extra'` is only available for `mode='coarse'`") + + return config + + +class CityscapesResource(ManualDownloadResource): + def __init__(self, **kwargs: Any) -> None: + super().__init__( + "Register on https://www.cityscapes-dataset.com/login/ and follow the instructions there.", **kwargs + ) + + +class Cityscapes(Dataset): + def _make_info(self) -> DatasetInfo: + name = "cityscapes" + categories = None + + return CityscapesDatasetInfo( + name, + categories=categories, + homepage="http://www.cityscapes-dataset.com/", + valid_options=dict( + split=("train", "val", "test", "train_extra"), + mode=("fine", "coarse"), + # target_type=("instance", "semantic", "polygon", "color") + ), + ) + + _FILES_CHECKSUMS = { + "gtCoarse.zip": "3555e09349ed49127053d940eaa66a87a79a175662b329c1a26a58d47e602b5b", + "gtFine_trainvaltest.zip": "40461a50097844f400fef147ecaf58b18fd99e14e4917fb7c3bf9c0d87d95884", + "leftImg8bit_trainextra.zip": "e41cc14c0c06aad051d52042465d9b8c22bacf6e4c93bb98de273ed7177b7133", + "leftImg8bit_trainvaltest.zip": "3ccff9ac1fa1d80a6a064407e589d747ed0657aac7dc495a4403ae1235a37525", + } + + def resources(self, config: DatasetConfig) -> List[OnlineResource]: + if config.mode == "fine": + resources = [ + CityscapesResource( + file_name="leftImg8bit_trainvaltest.zip", + sha256=self._FILES_CHECKSUMS["leftImg8bit_trainvaltest.zip"], + ), + CityscapesResource( + file_name="gtFine_trainvaltest.zip", sha256=self._FILES_CHECKSUMS["gtFine_trainvaltest.zip"] + ), + ] + else: + resources = [ + CityscapesResource( + file_name="leftImg8bit_trainextra.zip", sha256=self._FILES_CHECKSUMS["leftImg8bit_trainextra.zip"] + ), + CityscapesResource(file_name="gtCoarse.zip", sha256=self._FILES_CHECKSUMS["gtCoarse.zip"]), + ] + return resources + + def _filter_split_images(self, data, *, req_split: str): + path = Path(data[0]) + split = path.parent.parts[-2] + return split == req_split and ".png" == path.suffix + + def _filter_classify_targets(self, data, *, req_split: str): + path = Path(data[0]) + name = path.name + split = path.parent.parts[-2] + if split != req_split: + return None + for i, target_type in enumerate(["instance", "label", "polygon", "color"]): + ext = ".json" if target_type == "polygon" else ".png" + if ext in path.suffix and target_type in name: + return i + return None + + def _prepare_sample(self, data): + (img_path, img_data), target_data = data + + color_path, color_data = target_data[1] + target_data = target_data[0] + polygon_path, polygon_data = target_data[1] + target_data = target_data[0] + label_path, label_data = target_data[1] + target_data = target_data[0] + instance_path, instance_data = target_data + + return dict( + image_path=img_path, + image=EncodedImage.from_file(img_data), + color_path=color_path, + color=EncodedImage.from_file(color_data), + polygon_path=polygon_path, + polygon=polygon_data, + segmentation_path=label_path, + segmentation=EncodedImage.from_file(label_data), + instances_path=color_path, + instances=EncodedImage.from_file(instance_data), + ) + + def _make_datapipe( + self, + resource_dps: List[IterDataPipe], + *, + config: DatasetConfig, + ) -> IterDataPipe[Dict[str, Any]]: + archive_images, archive_targets = resource_dps + + images_dp = Filter(archive_images, filter_fn=partial(self._filter_split_images, req_split=config.split)) + + targets_dps = Demultiplexer( + archive_targets, + 4, + classifier_fn=partial(self._filter_classify_targets, req_split=config.split), + drop_none=True, + buffer_size=INFINITE_BUFFER_SIZE, + ) + + # targets_dps[2] is for json polygon, we have to decode them + targets_dps[2] = JsonParser(targets_dps[2]) + + def img_key_fn(data): + stem = Path(data[0]).stem + stem = stem[: -len("_leftImg8bit")] + return stem + + def target_key_fn(data, level=0): + path = data[0] + for _ in range(level): + path = path[0] + stem = Path(path).stem + i = stem.rfind("_gt") + stem = stem[:i] + return stem + + zipped_targets_dp = targets_dps[0] + for level, data_dp in enumerate(targets_dps[1:]): + zipped_targets_dp = IterKeyZipper( + zipped_targets_dp, + data_dp, + key_fn=partial(target_key_fn, level=level), + ref_key_fn=target_key_fn, + buffer_size=INFINITE_BUFFER_SIZE, + ) + + samples = IterKeyZipper( + images_dp, + zipped_targets_dp, + key_fn=img_key_fn, + ref_key_fn=partial(target_key_fn, level=len(targets_dps) - 1), + buffer_size=INFINITE_BUFFER_SIZE, + ) + return Mapper(samples, fn=self._prepare_sample) From e003b714d8fbdca092577bc1baf5e60f7bd6938b Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Mon, 21 Feb 2022 13:28:34 +0000 Subject: [PATCH 2/6] Fixed mypy issue and added tests and category infos --- test/builtin_dataset_mocks.py | 93 +++++++++++++++++++ .../prototype/datasets/_builtin/cityscapes.py | 83 ++++++++++++++--- 2 files changed, 161 insertions(+), 15 deletions(-) diff --git a/test/builtin_dataset_mocks.py b/test/builtin_dataset_mocks.py index 123d8f29d3f..9f4f8a54167 100644 --- a/test/builtin_dataset_mocks.py +++ b/test/builtin_dataset_mocks.py @@ -1344,3 +1344,96 @@ def pcam(info, root, config): compressed_file.write(compressed_data) return num_images + + +class CityScapesMockData: + + _ARCHIVE_NAMES = { + ("Coarse", "train"): [("gtCoarse.zip", "gtCoarse"), ("leftImg8bit_trainvaltest.zip", "leftImg8bit")], + ("Coarse", "train_extra"): [("gtCoarse.zip", "gtCoarse"), ("leftImg8bit_trainextra.zip", "leftImg8bit")], + ("Coarse", "val"): [("gtCoarse.zip", "gtCoarse"), ("leftImg8bit_trainvaltest.zip", "leftImg8bit")], + ("Fine", "train"): [("gtFine_trainvaltest.zip", "gtFine"), ("leftImg8bit_trainvaltest.zip", "leftImg8bit")], + ("Fine", "test"): [("gtFine_trainvaltest.zip", "gtFine"), ("leftImg8bit_trainvaltest.zip", "leftImg8bit")], + ("Fine", "val"): [("gtFine_trainvaltest.zip", "gtFine"), ("leftImg8bit_trainvaltest.zip", "leftImg8bit")], + } + + @classmethod + def generate(cls, root, config): + + mode = config.mode.capitalize() + split = config.split + + if split in ["train", "train_extra"]: + cities = ["bochum", "bremen"] + num_samples = 3 + else: + cities = ["bochum"] + num_samples = 2 + + polygon_target = { + "imgHeight": 1024, + "imgWidth": 2048, + "objects": [ + { + "label": "sky", + "polygon": [ + [1241, 0], + [1234, 156], + [1478, 197], + [1611, 172], + [1606, 0], + ], + }, + { + "label": "road", + "polygon": [ + [0, 448], + [1331, 274], + [1473, 265], + [2047, 605], + [2047, 1023], + [0, 1023], + ], + }, + ], + } + + gt_dir = root / f"gt{mode}" + + for city in cities: + + def make_image(name, size=10): + create_image_folder( + root=gt_dir / split, + name=city, + file_name_fn=lambda idx: name.format(idx=idx), + size=size, + num_examples=num_samples, + ) + + make_image(f"{city}_000000_00000" + "{idx}" + f"_gt{mode}_instanceIds.png") + make_image(f"{city}_000000_00000" + "{idx}" + f"_gt{mode}_labelIds.png") + make_image(f"{city}_000000_00000" + "{idx}" + f"_gt{mode}_color.png", size=(4, 10, 10)) + + for idx in range(num_samples): + polygon_target_name = gt_dir / split / city / f"{city}_000000_00000{idx}_gt{mode}_polygons.json" + with open(polygon_target_name, "w") as outfile: + json.dump(polygon_target, outfile) + + # Create leftImg8bit folder + for city in cities: + create_image_folder( + root=root / "leftImg8bit" / split, + name=city, + file_name_fn=lambda idx: f"{city}_000000_00000{idx}_leftImg8bit.png", + num_examples=num_samples, + ) + + for zip_name, folder_name in cls._ARCHIVE_NAMES[(mode, split)]: + make_zip(root, zip_name, folder_name) + return len(cities) * num_samples + + +@register_mock +def cityscapes(info, root, config): + return CityScapesMockData.generate(root, config) diff --git a/torchvision/prototype/datasets/_builtin/cityscapes.py b/torchvision/prototype/datasets/_builtin/cityscapes.py index 0fbae6ec464..c6aaa011cb9 100644 --- a/torchvision/prototype/datasets/_builtin/cityscapes.py +++ b/torchvision/prototype/datasets/_builtin/cityscapes.py @@ -1,6 +1,7 @@ +from collections import namedtuple from functools import partial from pathlib import Path -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional, Tuple from torchdata.datapipes.iter import IterDataPipe, Mapper, Filter, Demultiplexer, IterKeyZipper, JsonParser from torchvision.prototype.datasets.utils import ( @@ -10,8 +11,9 @@ ManualDownloadResource, OnlineResource, ) -from torchvision.prototype.datasets.utils._internal import INFINITE_BUFFER_SIZE +from torchvision.prototype.datasets.utils._internal import INFINITE_BUFFER_SIZE, hint_sharding, hint_shuffling from torchvision.prototype.features import EncodedImage +from torchvision.prototype.utils._internal import FrozenMapping class CityscapesDatasetInfo(DatasetInfo): @@ -43,20 +45,66 @@ def __init__(self, **kwargs: Any) -> None: ) +CityscapesClass = namedtuple( + "CityscapesClass", + ["name", "id", "train_id", "category", "category_id", "has_instances", "ignore_in_eval", "color"], +) + + class Cityscapes(Dataset): + + categories_to_details: FrozenMapping = FrozenMapping( + { + "unlabeled": CityscapesClass("unlabeled", 0, 255, "void", 0, False, True, (0, 0, 0)), + "ego vehicle": CityscapesClass("ego vehicle", 1, 255, "void", 0, False, True, (0, 0, 0)), + "rectification border": CityscapesClass("rectification border", 2, 255, "void", 0, False, True, (0, 0, 0)), + "out of roi": CityscapesClass("out of roi", 3, 255, "void", 0, False, True, (0, 0, 0)), + "static": CityscapesClass("static", 4, 255, "void", 0, False, True, (0, 0, 0)), + "dynamic": CityscapesClass("dynamic", 5, 255, "void", 0, False, True, (111, 74, 0)), + "ground": CityscapesClass("ground", 6, 255, "void", 0, False, True, (81, 0, 81)), + "road": CityscapesClass("road", 7, 0, "flat", 1, False, False, (128, 64, 128)), + "sidewalk": CityscapesClass("sidewalk", 8, 1, "flat", 1, False, False, (244, 35, 232)), + "parking": CityscapesClass("parking", 9, 255, "flat", 1, False, True, (250, 170, 160)), + "rail track": CityscapesClass("rail track", 10, 255, "flat", 1, False, True, (230, 150, 140)), + "building": CityscapesClass("building", 11, 2, "construction", 2, False, False, (70, 70, 70)), + "wall": CityscapesClass("wall", 12, 3, "construction", 2, False, False, (102, 102, 156)), + "fence": CityscapesClass("fence", 13, 4, "construction", 2, False, False, (190, 153, 153)), + "guard rail": CityscapesClass("guard rail", 14, 255, "construction", 2, False, True, (180, 165, 180)), + "bridge": CityscapesClass("bridge", 15, 255, "construction", 2, False, True, (150, 100, 100)), + "tunnel": CityscapesClass("tunnel", 16, 255, "construction", 2, False, True, (150, 120, 90)), + "pole": CityscapesClass("pole", 17, 5, "object", 3, False, False, (153, 153, 153)), + "polegroup": CityscapesClass("polegroup", 18, 255, "object", 3, False, True, (153, 153, 153)), + "traffic light": CityscapesClass("traffic light", 19, 6, "object", 3, False, False, (250, 170, 30)), + "traffic sign": CityscapesClass("traffic sign", 20, 7, "object", 3, False, False, (220, 220, 0)), + "vegetation": CityscapesClass("vegetation", 21, 8, "nature", 4, False, False, (107, 142, 35)), + "terrain": CityscapesClass("terrain", 22, 9, "nature", 4, False, False, (152, 251, 152)), + "sky": CityscapesClass("sky", 23, 10, "sky", 5, False, False, (70, 130, 180)), + "person": CityscapesClass("person", 24, 11, "human", 6, True, False, (220, 20, 60)), + "rider": CityscapesClass("rider", 25, 12, "human", 6, True, False, (255, 0, 0)), + "car": CityscapesClass("car", 26, 13, "vehicle", 7, True, False, (0, 0, 142)), + "truck": CityscapesClass("truck", 27, 14, "vehicle", 7, True, False, (0, 0, 70)), + "bus": CityscapesClass("bus", 28, 15, "vehicle", 7, True, False, (0, 60, 100)), + "caravan": CityscapesClass("caravan", 29, 255, "vehicle", 7, True, True, (0, 0, 90)), + "trailer": CityscapesClass("trailer", 30, 255, "vehicle", 7, True, True, (0, 0, 110)), + "train": CityscapesClass("train", 31, 16, "vehicle", 7, True, False, (0, 80, 100)), + "motorcycle": CityscapesClass("motorcycle", 32, 17, "vehicle", 7, True, False, (0, 0, 230)), + "bicycle": CityscapesClass("bicycle", 33, 18, "vehicle", 7, True, False, (119, 11, 32)), + "license plate": CityscapesClass("license plate", -1, -1, "vehicle", 7, False, True, (0, 0, 142)), + } + ) + def _make_info(self) -> DatasetInfo: name = "cityscapes" - categories = None return CityscapesDatasetInfo( name, - categories=categories, + categories=list(self.categories_to_details.keys()), homepage="http://www.cityscapes-dataset.com/", valid_options=dict( split=("train", "val", "test", "train_extra"), mode=("fine", "coarse"), - # target_type=("instance", "semantic", "polygon", "color") ), + extra=dict(classname_to_details=self.categories_to_details), ) _FILES_CHECKSUMS = { @@ -67,8 +115,9 @@ def _make_info(self) -> DatasetInfo: } def resources(self, config: DatasetConfig) -> List[OnlineResource]: + resources: List[OnlineResource] = [] if config.mode == "fine": - resources = [ + resources += [ CityscapesResource( file_name="leftImg8bit_trainvaltest.zip", sha256=self._FILES_CHECKSUMS["leftImg8bit_trainvaltest.zip"], @@ -78,20 +127,22 @@ def resources(self, config: DatasetConfig) -> List[OnlineResource]: ), ] else: - resources = [ + split_label = "trainextra" if config.split == "train_extra" else "trainvaltest" + resources += [ CityscapesResource( - file_name="leftImg8bit_trainextra.zip", sha256=self._FILES_CHECKSUMS["leftImg8bit_trainextra.zip"] + file_name=f"leftImg8bit_{split_label}.zip", + sha256=self._FILES_CHECKSUMS[f"leftImg8bit_{split_label}.zip"], ), CityscapesResource(file_name="gtCoarse.zip", sha256=self._FILES_CHECKSUMS["gtCoarse.zip"]), ] return resources - def _filter_split_images(self, data, *, req_split: str): + def _filter_split_images(self, data: Tuple[str, Any], *, req_split: str) -> bool: path = Path(data[0]) split = path.parent.parts[-2] return split == req_split and ".png" == path.suffix - def _filter_classify_targets(self, data, *, req_split: str): + def _filter_classify_targets(self, data: Tuple[str, Any], *, req_split: str) -> Optional[int]: path = Path(data[0]) name = path.name split = path.parent.parts[-2] @@ -103,7 +154,7 @@ def _filter_classify_targets(self, data, *, req_split: str): return i return None - def _prepare_sample(self, data): + def _prepare_sample(self, data: Tuple[Tuple[str, Any], Any]) -> Dict[str, Any]: (img_path, img_data), target_data = data color_path, color_data = target_data[1] @@ -112,7 +163,7 @@ def _prepare_sample(self, data): target_data = target_data[0] label_path, label_data = target_data[1] target_data = target_data[0] - instance_path, instance_data = target_data + instances_path, instance_data = target_data return dict( image_path=img_path, @@ -123,7 +174,7 @@ def _prepare_sample(self, data): polygon=polygon_data, segmentation_path=label_path, segmentation=EncodedImage.from_file(label_data), - instances_path=color_path, + instances_path=instances_path, instances=EncodedImage.from_file(instance_data), ) @@ -148,12 +199,12 @@ def _make_datapipe( # targets_dps[2] is for json polygon, we have to decode them targets_dps[2] = JsonParser(targets_dps[2]) - def img_key_fn(data): + def img_key_fn(data: Tuple[str, Any]) -> str: stem = Path(data[0]).stem stem = stem[: -len("_leftImg8bit")] return stem - def target_key_fn(data, level=0): + def target_key_fn(data: Tuple[Any, Any], level: int = 0) -> str: path = data[0] for _ in range(level): path = path[0] @@ -179,4 +230,6 @@ def target_key_fn(data, level=0): ref_key_fn=partial(target_key_fn, level=len(targets_dps) - 1), buffer_size=INFINITE_BUFFER_SIZE, ) + samples = hint_sharding(samples) + samples = hint_shuffling(samples) return Mapper(samples, fn=self._prepare_sample) From 440fbdb729b6cdd999f6250e4b31ac318e760314 Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Mon, 21 Feb 2022 15:45:17 +0000 Subject: [PATCH 3/6] Recoded with Grouper --- .../prototype/datasets/_builtin/cityscapes.py | 84 +++++++------------ 1 file changed, 30 insertions(+), 54 deletions(-) diff --git a/torchvision/prototype/datasets/_builtin/cityscapes.py b/torchvision/prototype/datasets/_builtin/cityscapes.py index c6aaa011cb9..bded9d70126 100644 --- a/torchvision/prototype/datasets/_builtin/cityscapes.py +++ b/torchvision/prototype/datasets/_builtin/cityscapes.py @@ -1,9 +1,10 @@ +import json from collections import namedtuple from functools import partial from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Tuple -from torchdata.datapipes.iter import IterDataPipe, Mapper, Filter, Demultiplexer, IterKeyZipper, JsonParser +from torchdata.datapipes.iter import Grouper, IterDataPipe, Mapper, Filter, IterKeyZipper from torchvision.prototype.datasets.utils import ( Dataset, DatasetInfo, @@ -142,41 +143,32 @@ def _filter_split_images(self, data: Tuple[str, Any], *, req_split: str) -> bool split = path.parent.parts[-2] return split == req_split and ".png" == path.suffix - def _filter_classify_targets(self, data: Tuple[str, Any], *, req_split: str) -> Optional[int]: + def _filter_group_targets(self, data: Tuple[str, Any]) -> str: path = Path(data[0]) - name = path.name split = path.parent.parts[-2] - if split != req_split: - return None - for i, target_type in enumerate(["instance", "label", "polygon", "color"]): - ext = ".json" if target_type == "polygon" else ".png" - if ext in path.suffix and target_type in name: - return i - return None + stem = Path(path).stem + i = stem.rfind("_gt") + stem = stem[:i] + return f"{split}_{stem}" def _prepare_sample(self, data: Tuple[Tuple[str, Any], Any]) -> Dict[str, Any]: (img_path, img_data), target_data = data - color_path, color_data = target_data[1] - target_data = target_data[0] - polygon_path, polygon_data = target_data[1] - target_data = target_data[0] - label_path, label_data = target_data[1] - target_data = target_data[0] - instances_path, instance_data = target_data - - return dict( - image_path=img_path, - image=EncodedImage.from_file(img_data), - color_path=color_path, - color=EncodedImage.from_file(color_data), - polygon_path=polygon_path, - polygon=polygon_data, - segmentation_path=label_path, - segmentation=EncodedImage.from_file(label_data), - instances_path=instances_path, - instances=EncodedImage.from_file(instance_data), - ) + output = dict(image_path=img_path, image=EncodedImage.from_file(img_data)) + # reorder inside group of targets: + for path, data in target_data: + stem = Path(path).stem + for target_type in ["instance", "label", "polygon", "color"]: + if target_type in stem: + if target_type == "polygon": + enc_data = json.loads(data.read()) + else: + enc_data = EncodedImage.from_file(data) + output[target_type] = enc_data + output[f"{target_type}_path"] = path + break + + return output def _make_datapipe( self, @@ -188,46 +180,30 @@ def _make_datapipe( images_dp = Filter(archive_images, filter_fn=partial(self._filter_split_images, req_split=config.split)) - targets_dps = Demultiplexer( + targets_dp = Grouper( archive_targets, - 4, - classifier_fn=partial(self._filter_classify_targets, req_split=config.split), - drop_none=True, + group_key_fn=self._filter_group_targets, buffer_size=INFINITE_BUFFER_SIZE, + group_size=4, ) - # targets_dps[2] is for json polygon, we have to decode them - targets_dps[2] = JsonParser(targets_dps[2]) - def img_key_fn(data: Tuple[str, Any]) -> str: stem = Path(data[0]).stem stem = stem[: -len("_leftImg8bit")] return stem - def target_key_fn(data: Tuple[Any, Any], level: int = 0) -> str: - path = data[0] - for _ in range(level): - path = path[0] + def target_key_fn(data: Tuple[Any, Any]) -> str: + path, _ = data[0] stem = Path(path).stem i = stem.rfind("_gt") stem = stem[:i] return stem - zipped_targets_dp = targets_dps[0] - for level, data_dp in enumerate(targets_dps[1:]): - zipped_targets_dp = IterKeyZipper( - zipped_targets_dp, - data_dp, - key_fn=partial(target_key_fn, level=level), - ref_key_fn=target_key_fn, - buffer_size=INFINITE_BUFFER_SIZE, - ) - samples = IterKeyZipper( images_dp, - zipped_targets_dp, + targets_dp, key_fn=img_key_fn, - ref_key_fn=partial(target_key_fn, level=len(targets_dps) - 1), + ref_key_fn=target_key_fn, buffer_size=INFINITE_BUFFER_SIZE, ) samples = hint_sharding(samples) From f35b87be72d1ba623fc24495041632eb8d78b38b Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Tue, 22 Feb 2022 14:28:29 +0000 Subject: [PATCH 4/6] Addressed reviewer comments --- .../prototype/datasets/_builtin/cityscapes.py | 68 ++++++++----------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/torchvision/prototype/datasets/_builtin/cityscapes.py b/torchvision/prototype/datasets/_builtin/cityscapes.py index bded9d70126..fd050736d3c 100644 --- a/torchvision/prototype/datasets/_builtin/cityscapes.py +++ b/torchvision/prototype/datasets/_builtin/cityscapes.py @@ -105,7 +105,7 @@ def _make_info(self) -> DatasetInfo: split=("train", "val", "test", "train_extra"), mode=("fine", "coarse"), ), - extra=dict(classname_to_details=self.categories_to_details), + extra=dict(categories_to_details=self.categories_to_details), ) _FILES_CHECKSUMS = { @@ -116,26 +116,16 @@ def _make_info(self) -> DatasetInfo: } def resources(self, config: DatasetConfig) -> List[OnlineResource]: - resources: List[OnlineResource] = [] + if config.mode == "fine": - resources += [ - CityscapesResource( - file_name="leftImg8bit_trainvaltest.zip", - sha256=self._FILES_CHECKSUMS["leftImg8bit_trainvaltest.zip"], - ), - CityscapesResource( - file_name="gtFine_trainvaltest.zip", sha256=self._FILES_CHECKSUMS["gtFine_trainvaltest.zip"] - ), - ] + filenames = ("leftImg8bit_trainvaltest.zip", "gtFine_trainvaltest.zip") else: split_label = "trainextra" if config.split == "train_extra" else "trainvaltest" - resources += [ - CityscapesResource( - file_name=f"leftImg8bit_{split_label}.zip", - sha256=self._FILES_CHECKSUMS[f"leftImg8bit_{split_label}.zip"], - ), - CityscapesResource(file_name="gtCoarse.zip", sha256=self._FILES_CHECKSUMS["gtCoarse.zip"]), - ] + filenames = (f"leftImg8bit_{split_label}.zip", "gtCoarse.zip") + + resources: List[OnlineResource] = [ + CityscapesResource(file_name=file_name, sha256=self._FILES_CHECKSUMS[file_name]) for file_name in filenames + ] return resources def _filter_split_images(self, data: Tuple[str, Any], *, req_split: str) -> bool: @@ -145,17 +135,16 @@ def _filter_split_images(self, data: Tuple[str, Any], *, req_split: str) -> bool def _filter_group_targets(self, data: Tuple[str, Any]) -> str: path = Path(data[0]) - split = path.parent.parts[-2] - stem = Path(path).stem - i = stem.rfind("_gt") - stem = stem[:i] - return f"{split}_{stem}" + # Taregt path looks like + # gtFine/val/frankfurt/frankfurt_000001_066574_gtFine_polygons.json + # and we produce the key: frankfurt_000001_066574 + return path.name.rsplit("_", 1)[0] def _prepare_sample(self, data: Tuple[Tuple[str, Any], Any]) -> Dict[str, Any]: (img_path, img_data), target_data = data output = dict(image_path=img_path, image=EncodedImage.from_file(img_data)) - # reorder inside group of targets: + # reorder inside group of targets and setup output dictionary: for path, data in target_data: stem = Path(path).stem for target_type in ["instance", "label", "polygon", "color"]: @@ -180,6 +169,9 @@ def _make_datapipe( images_dp = Filter(archive_images, filter_fn=partial(self._filter_split_images, req_split=config.split)) + images_dp = hint_sharding(images_dp) + images_dp = hint_shuffling(images_dp) + targets_dp = Grouper( archive_targets, group_key_fn=self._filter_group_targets, @@ -187,25 +179,23 @@ def _make_datapipe( group_size=4, ) - def img_key_fn(data: Tuple[str, Any]) -> str: - stem = Path(data[0]).stem - stem = stem[: -len("_leftImg8bit")] - return stem - - def target_key_fn(data: Tuple[Any, Any]) -> str: - path, _ = data[0] - stem = Path(path).stem - i = stem.rfind("_gt") - stem = stem[:i] - return stem + def key_fn(data: Tuple[Any, Any]) -> str: + data0 = data[0] + if isinstance(data0, tuple): + data0 = data0[0] + path = Path(data0) + # The pathes for images and targets are + # - leftImg8bit/val/frankfurt/frankfurt_000001_066574_leftImg8bit.png + # - gtFine/val/frankfurt/frankfurt_000001_066574_gtFine_polygons.json + # - gtFine/val/frankfurt/frankfurt_000001_066574_gtFine_labelIds.png + # we transform them into "frankfurt_000001_066574" + return "_".join(path.name.split("_", 3)[:3]) samples = IterKeyZipper( images_dp, targets_dp, - key_fn=img_key_fn, - ref_key_fn=target_key_fn, + key_fn=key_fn, + ref_key_fn=key_fn, buffer_size=INFINITE_BUFFER_SIZE, ) - samples = hint_sharding(samples) - samples = hint_shuffling(samples) return Mapper(samples, fn=self._prepare_sample) From 4fdeb31fc5c4646e8eb218050f68dc1dac694ffd Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Fri, 25 Feb 2022 15:04:53 +0000 Subject: [PATCH 5/6] Addressed some comments from the review --- test/builtin_dataset_mocks.py | 18 +++---- .../prototype/datasets/_builtin/cityscapes.py | 53 ++++++++++--------- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/test/builtin_dataset_mocks.py b/test/builtin_dataset_mocks.py index 9f4f8a54167..4378de852bd 100644 --- a/test/builtin_dataset_mocks.py +++ b/test/builtin_dataset_mocks.py @@ -1364,10 +1364,10 @@ def generate(cls, root, config): split = config.split if split in ["train", "train_extra"]: - cities = ["bochum", "bremen"] + cities = ["bochum", "bremen", "jena"] num_samples = 3 else: - cities = ["bochum"] + cities = ["frankfurt", "munster"] num_samples = 2 polygon_target = { @@ -1402,21 +1402,21 @@ def generate(cls, root, config): for city in cities: - def make_image(name, size=10): + def make_images(file_name_fn, size=10): create_image_folder( root=gt_dir / split, name=city, - file_name_fn=lambda idx: name.format(idx=idx), + file_name_fn=file_name_fn, size=size, num_examples=num_samples, ) - make_image(f"{city}_000000_00000" + "{idx}" + f"_gt{mode}_instanceIds.png") - make_image(f"{city}_000000_00000" + "{idx}" + f"_gt{mode}_labelIds.png") - make_image(f"{city}_000000_00000" + "{idx}" + f"_gt{mode}_color.png", size=(4, 10, 10)) + make_images(lambda idx: f"{city}_000000_{idx:06d}_gt{mode}_instanceIds.png") + make_images(lambda idx: f"{city}_000000_{idx:06d}_gt{mode}_labelIds.png") + make_images(lambda idx: f"{city}_000000_{idx:06d}_gt{mode}_color.png", size=(4, 10, 10)) for idx in range(num_samples): - polygon_target_name = gt_dir / split / city / f"{city}_000000_00000{idx}_gt{mode}_polygons.json" + polygon_target_name = gt_dir / split / city / f"{city}_000000_{idx:06d}_gt{mode}_polygons.json" with open(polygon_target_name, "w") as outfile: json.dump(polygon_target, outfile) @@ -1425,7 +1425,7 @@ def make_image(name, size=10): create_image_folder( root=root / "leftImg8bit" / split, name=city, - file_name_fn=lambda idx: f"{city}_000000_00000{idx}_leftImg8bit.png", + file_name_fn=lambda idx: f"{city}_000000_{idx:06d}_leftImg8bit.png", num_examples=num_samples, ) diff --git a/torchvision/prototype/datasets/_builtin/cityscapes.py b/torchvision/prototype/datasets/_builtin/cityscapes.py index fd050736d3c..555c11e3b2d 100644 --- a/torchvision/prototype/datasets/_builtin/cityscapes.py +++ b/torchvision/prototype/datasets/_builtin/cityscapes.py @@ -2,7 +2,7 @@ from collections import namedtuple from functools import partial from pathlib import Path -from typing import Any, Dict, List, Tuple +from typing import cast, Any, Dict, List, Tuple from torchdata.datapipes.iter import Grouper, IterDataPipe, Mapper, Filter, IterKeyZipper from torchvision.prototype.datasets.utils import ( @@ -123,22 +123,30 @@ def resources(self, config: DatasetConfig) -> List[OnlineResource]: split_label = "trainextra" if config.split == "train_extra" else "trainvaltest" filenames = (f"leftImg8bit_{split_label}.zip", "gtCoarse.zip") - resources: List[OnlineResource] = [ - CityscapesResource(file_name=file_name, sha256=self._FILES_CHECKSUMS[file_name]) for file_name in filenames - ] - return resources + return cast( + List[OnlineResource], + [ + CityscapesResource(file_name=file_name, sha256=self._FILES_CHECKSUMS[file_name]) + for file_name in filenames + ], + ) def _filter_split_images(self, data: Tuple[str, Any], *, req_split: str) -> bool: path = Path(data[0]) split = path.parent.parts[-2] return split == req_split and ".png" == path.suffix - def _filter_group_targets(self, data: Tuple[str, Any]) -> str: - path = Path(data[0]) - # Taregt path looks like - # gtFine/val/frankfurt/frankfurt_000001_066574_gtFine_polygons.json - # and we produce the key: frankfurt_000001_066574 - return path.name.rsplit("_", 1)[0] + def _get_key_from_path(self, data: Tuple[Any, Any]) -> str: + data0 = data[0] + if isinstance(data0, tuple): + data0 = data0[0] + path = Path(data0) + # The pathes for images and targets are + # - leftImg8bit/val/frankfurt/frankfurt_000001_066574_leftImg8bit.png + # - gtFine/val/frankfurt/frankfurt_000001_066574_gtFine_polygons.json + # - gtFine/val/frankfurt/frankfurt_000001_066574_gtFine_labelIds.png + # we transform them into "frankfurt_000001_066574" + return "_".join(path.name.split("_", 3)[:3]) def _prepare_sample(self, data: Tuple[Tuple[str, Any], Any]) -> Dict[str, Any]: (img_path, img_data), target_data = data @@ -151,6 +159,10 @@ def _prepare_sample(self, data: Tuple[Tuple[str, Any], Any]) -> Dict[str, Any]: if target_type in stem: if target_type == "polygon": enc_data = json.loads(data.read()) + elif target_type == "label": + # TODO: We need an EncodedSegmentationMask feature now that we also + # have a separate SegementationMask. + enc_data = EncodedImage.from_file(data) else: enc_data = EncodedImage.from_file(data) output[target_type] = enc_data @@ -172,30 +184,19 @@ def _make_datapipe( images_dp = hint_sharding(images_dp) images_dp = hint_shuffling(images_dp) + # As city names are unique per split we can group targets by + # keys like "frankfurt_000001_066574" targets_dp = Grouper( archive_targets, - group_key_fn=self._filter_group_targets, + group_key_fn=self._get_key_from_path, buffer_size=INFINITE_BUFFER_SIZE, group_size=4, ) - def key_fn(data: Tuple[Any, Any]) -> str: - data0 = data[0] - if isinstance(data0, tuple): - data0 = data0[0] - path = Path(data0) - # The pathes for images and targets are - # - leftImg8bit/val/frankfurt/frankfurt_000001_066574_leftImg8bit.png - # - gtFine/val/frankfurt/frankfurt_000001_066574_gtFine_polygons.json - # - gtFine/val/frankfurt/frankfurt_000001_066574_gtFine_labelIds.png - # we transform them into "frankfurt_000001_066574" - return "_".join(path.name.split("_", 3)[:3]) - samples = IterKeyZipper( images_dp, targets_dp, - key_fn=key_fn, - ref_key_fn=key_fn, + key_fn=self._get_key_from_path, buffer_size=INFINITE_BUFFER_SIZE, ) return Mapper(samples, fn=self._prepare_sample) From 19dbebf7adfdb686948f2f72b4aef146cc291be2 Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Mon, 28 Feb 2022 11:12:45 +0000 Subject: [PATCH 6/6] Updated cityscapes mock dataset code --- test/builtin_dataset_mocks.py | 63 +++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/test/builtin_dataset_mocks.py b/test/builtin_dataset_mocks.py index 4378de852bd..3f0885f0cf1 100644 --- a/test/builtin_dataset_mocks.py +++ b/test/builtin_dataset_mocks.py @@ -1361,15 +1361,21 @@ class CityScapesMockData: def generate(cls, root, config): mode = config.mode.capitalize() - split = config.split + req_split = config.split - if split in ["train", "train_extra"]: + if req_split in ["train", "train_extra"]: cities = ["bochum", "bremen", "jena"] num_samples = 3 else: cities = ["frankfurt", "munster"] num_samples = 2 + if mode == "Fine": + splits = ["train", "test", "val"] + else: + splits = ["train", "train_extra", "val"] + + # Below values are just example values and not some special values polygon_target = { "imgHeight": 1024, "imgWidth": 2048, @@ -1400,37 +1406,44 @@ def generate(cls, root, config): gt_dir = root / f"gt{mode}" - for city in cities: + for split in splits: + for city in cities: + + def make_images(file_name_fn, size=10): + create_image_folder( + root=gt_dir / split, + name=city, + file_name_fn=file_name_fn, + size=size, + num_examples=num_samples, + ) + + make_images(lambda idx: f"{city}_000000_{idx:06d}_gt{mode}_instanceIds.png") + make_images(lambda idx: f"{city}_000000_{idx:06d}_gt{mode}_labelIds.png") + make_images(lambda idx: f"{city}_000000_{idx:06d}_gt{mode}_color.png", size=(4, 10, 10)) - def make_images(file_name_fn, size=10): + for idx in range(num_samples): + polygon_target_name = gt_dir / split / city / f"{city}_000000_{idx:06d}_gt{mode}_polygons.json" + with open(polygon_target_name, "w") as outfile: + json.dump(polygon_target, outfile) + + # Create leftImg8bit folder + for city in cities: create_image_folder( - root=gt_dir / split, + root=root / "leftImg8bit" / split, name=city, - file_name_fn=file_name_fn, - size=size, + file_name_fn=lambda idx: f"{city}_000000_{idx:06d}_leftImg8bit.png", num_examples=num_samples, ) - make_images(lambda idx: f"{city}_000000_{idx:06d}_gt{mode}_instanceIds.png") - make_images(lambda idx: f"{city}_000000_{idx:06d}_gt{mode}_labelIds.png") - make_images(lambda idx: f"{city}_000000_{idx:06d}_gt{mode}_color.png", size=(4, 10, 10)) - - for idx in range(num_samples): - polygon_target_name = gt_dir / split / city / f"{city}_000000_{idx:06d}_gt{mode}_polygons.json" - with open(polygon_target_name, "w") as outfile: - json.dump(polygon_target, outfile) + for zip_name, folder_name in cls._ARCHIVE_NAMES[(mode, req_split)]: + # Create dummy README and license.txt + for filename in ["README", "license.txt"]: + with (root / filename).open("w") as handler: + handler.write("Content\n") - # Create leftImg8bit folder - for city in cities: - create_image_folder( - root=root / "leftImg8bit" / split, - name=city, - file_name_fn=lambda idx: f"{city}_000000_{idx:06d}_leftImg8bit.png", - num_examples=num_samples, - ) + make_zip(root, zip_name, folder_name, root / "README", root / "license.txt") - for zip_name, folder_name in cls._ARCHIVE_NAMES[(mode, split)]: - make_zip(root, zip_name, folder_name) return len(cities) * num_samples