From 9d7876c00e99c477aacc9497e92d32833159a27e Mon Sep 17 00:00:00 2001 From: wufan Date: Mon, 10 Apr 2023 14:06:46 +0800 Subject: [PATCH] [Feature] Support DSDL Dataset --- configs/dsdl/README.md | 105 ++++++++++++++++ configs/dsdl/cityscapes.py | 70 +++++++++++ configs/dsdl/metafile.yaml | 12 ++ configs/dsdl/voc.py | 65 ++++++++++ docs/en/user_guides/2_dataset_prepare.md | 1 + docs/zh_cn/user_guides/2_dataset_prepare.md | 1 + mmseg/datasets/__init__.py | 5 + mmseg/datasets/dsdl.py | 116 ++++++++++++++++++ model-index.yml | 1 + tests/data/dsdl_seg/config.py | 13 ++ tests/data/dsdl_seg/defs/class-dom.yaml | 24 ++++ .../data/dsdl_seg/defs/segmentation-def.yaml | 15 +++ tests/data/dsdl_seg/set-train/train.yaml | 15 +++ .../dsdl_seg/set-train/train_samples.json | 1 + tests/test_datasets/test_dataset.py | 25 +++- 15 files changed, 464 insertions(+), 5 deletions(-) create mode 100644 configs/dsdl/README.md create mode 100644 configs/dsdl/cityscapes.py create mode 100644 configs/dsdl/metafile.yaml create mode 100644 configs/dsdl/voc.py create mode 100644 mmseg/datasets/dsdl.py create mode 100755 tests/data/dsdl_seg/config.py create mode 100755 tests/data/dsdl_seg/defs/class-dom.yaml create mode 100755 tests/data/dsdl_seg/defs/segmentation-def.yaml create mode 100755 tests/data/dsdl_seg/set-train/train.yaml create mode 100755 tests/data/dsdl_seg/set-train/train_samples.json diff --git a/configs/dsdl/README.md b/configs/dsdl/README.md new file mode 100644 index 00000000000..f9257f02c7b --- /dev/null +++ b/configs/dsdl/README.md @@ -0,0 +1,105 @@ +# DSDL: Standard Description Language for DataSet + + + + + +## Abstract + + + +Data is the cornerstone of artificial intelligence. The efficiency of data acquisition, exchange, and application directly impacts the advances in technologies and applications. Over the long history of AI, a vast quantity of data sets have been developed and distributed. However, these datasets are defined in very different forms, which incurs significant overhead when it comes to exchange, integration, and utilization -- it is often the case that one needs to develop a new customized tool or script in order to incorporate a new dataset into a workflow. + +To overcome such difficulties, we develop **Data Set Description Language (DSDL)**. More details please visit our [official documents](https://opendatalab.github.io/dsdl-docs/getting_started/overview/), dsdl datasets can be downloaded from our platform [OpenDataLab](https://opendatalab.com/). + + + +## Steps + +- install dsdl and opendatalab: + + ``` + pip install dsdl + pip install opendatalab + ``` + +- install mmseg and pytorch: + please refer this [installation documents](https://mmsegmentation.readthedocs.io/en/latest/get_started.html). + +- prepare dsdl dataset (take voc2012 as an example) + + - dowaload dsdl dataset (you will need an opendatalab account to do so. [register one now](https://opendatalab.com/)) + + ``` + cd data + + odl login + odl get PASCAL_VOC2012 + ``` + + usually, dataset is compressed on opendatalab platform, the downloaded voc 2012 dataset should be like this: + + ``` + data/ + ├── PASCAL_VOC2012 + │   ├── dsdl + │   │   ├── dsdl_Det_full.zip + │   │   └── dsdl_SemSeg_full.zip + │   ├── raw + │   │   ├── VOC2012test.tar + │   │   ├── VOCdevkit_18-May-2011.tar + │   │   └── VOCtrainval_11-May-2012.tar + │   └── README.md + └── ... + ``` + + - decompress dataset + + ``` + cd dsdl + unzip dsdl_SemSeg_full.zip + ``` + + as we do not need detection dsdl files, we only decompress the semantic segmentation files here. + + ``` + cd ../raw + tar -xvf VOCtrainval_11-May-2012.tar + tar -xvf VOC2012test.tar + + cd ../../ + ``` + +- change traning config + + here , we open the [voc config file](voc.py), and set some file paths as below: + + ``` + data_root = 'data/PASCAL_VOC2012' + img_prefix = 'raw/VOCdevkit/VOC2012' + train_ann = 'dsdl/dsdl_SemSeg_full/set-train/train.yaml' + val_ann = 'dsdl/dsdl_SemSeg_full/set-val/val.yaml' + ``` + + as dsdl datasets with one task using one dataloader, we can simplly change these file paths to train a model on a different dataset. + +- train: + + - using single gpu: + + ``` + python tools/train.py {config_file} + ``` + + - using slrum: + + ``` + ./tools/slurm_train.sh {partition} {job_name} {config_file} {work_dir} {gpu_nums} + ``` + +## Test Results + +| Datasets | Model | mIoU(%) | Config | +| :--------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----: | :-----------------------: | +| voc2012 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_20k_voc12aug/deeplabv3_r50-d8_512x512_20k_voc12aug_20200617_010906-596905ef.pth) | 76.73 | [config](./voc.py) | +| cityscapes | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x1024_40k_cityscapes/deeplabv3_r50-d8_512x1024_40k_cityscapes_20200605_022449-acadc2f8.pth) | 79.01 | [config](./cityscapes.py) | diff --git a/configs/dsdl/cityscapes.py b/configs/dsdl/cityscapes.py new file mode 100644 index 00000000000..94ccc068e06 --- /dev/null +++ b/configs/dsdl/cityscapes.py @@ -0,0 +1,70 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] + +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +# dataset settings +dataset_type = 'DSDLSegDataset' +data_root = 'data/CityScapes' +img_prefix = 'raw/CityScapes' +train_ann = 'dsdl/dsdl_SemSeg_full/set-train/train.yaml' +val_ann = 'dsdl/dsdl_SemSeg_full/set-val/val.yaml' + +used_labels = [ + 'road', 'sidewalk', 'building', 'wall', 'fence', 'pole', 'traffic_light', + 'traffic_sign', 'vegetation', 'terrain', 'sky', 'person', 'rider', 'car', + 'truck', 'bus', 'train', 'motorcycle', 'bicycle' +] + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path=img_prefix, seg_map_path=img_prefix), + ann_file=train_ann, + used_labels=used_labels, + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path=img_prefix, seg_map_path=img_prefix), + ann_file=val_ann, + used_labels=used_labels, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/configs/dsdl/metafile.yaml b/configs/dsdl/metafile.yaml new file mode 100644 index 00000000000..19ad07ed636 --- /dev/null +++ b/configs/dsdl/metafile.yaml @@ -0,0 +1,12 @@ +Collections: +- Name: '' + License: Apache License 2.0 + Metadata: + Training Data: [] + Paper: + Title: '' + URL: '' + README: configs/dsdl/README.md + Frameworks: + - PyTorch +Models: [] diff --git a/configs/dsdl/voc.py b/configs/dsdl/voc.py new file mode 100644 index 00000000000..c1895f7c7d3 --- /dev/null +++ b/configs/dsdl/voc.py @@ -0,0 +1,65 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] + +# dataset settings +dataset_type = 'DSDLSegDataset' +data_root = 'data/PASCAL_VOC2012' +img_prefix = 'raw/VOCdevkit/VOC2012' +train_ann = 'dsdl/dsdl_SemSeg_full/set-train/train.yaml' +val_ann = 'dsdl/dsdl_SemSeg_full/set-val/val.yaml' +crop_size = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path=img_prefix, seg_map_path=img_prefix), + ann_file=train_ann, + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path=img_prefix, seg_map_path=img_prefix), + ann_file=val_ann, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator + +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/docs/en/user_guides/2_dataset_prepare.md b/docs/en/user_guides/2_dataset_prepare.md index 0c7e25433c4..e930f7f1d5f 100644 --- a/docs/en/user_guides/2_dataset_prepare.md +++ b/docs/en/user_guides/2_dataset_prepare.md @@ -2,6 +2,7 @@ It is recommended to symlink the dataset root to `$MMSEGMENTATION/data`. If your folder structure is different, you may need to change the corresponding paths in config files. +For users in China, we also recommend you get the dsdl dataset from our opensource platform [OpenDataLab](https://opendatalab.com/), for better download and use experience,here is an example: [DSDLReadme](../../../configs/dsdl/README.md), welcome to try. ```none mmsegmentation diff --git a/docs/zh_cn/user_guides/2_dataset_prepare.md b/docs/zh_cn/user_guides/2_dataset_prepare.md index b21f041e6f9..ad2c617ef4a 100644 --- a/docs/zh_cn/user_guides/2_dataset_prepare.md +++ b/docs/zh_cn/user_guides/2_dataset_prepare.md @@ -2,6 +2,7 @@ 我们建议将数据集根目录符号链接到 `$MMSEGMENTATION/data`。 如果您的目录结构不同,您可能需要更改配置文件中相应的路径。 +对于中国境内的用户,我们也推荐通过开源数据平台 [OpenDataLab](https://opendatalab.com/) 来下载dsdl标准数据,以获得更好的下载和使用体验,这里有一个下载dsdl数据集并进行训练的案例[DSDLReadme](../../../configs/dsdl/README.md),欢迎尝试。 ```none mmsegmentation diff --git a/mmseg/datasets/__init__.py b/mmseg/datasets/__init__.py index dc71db9c3f7..d48779cb1b1 100644 --- a/mmseg/datasets/__init__.py +++ b/mmseg/datasets/__init__.py @@ -9,6 +9,7 @@ from .dataset_wrappers import MultiImageMixDataset from .decathlon import DecathlonDataset from .drive import DRIVEDataset +from .dsdl import DSDLSegDataset from .hrf import HRFDataset from .isaid import iSAIDDataset from .isprs import ISPRSDataset @@ -54,7 +55,11 @@ 'BioMedicalGaussianNoise', 'BioMedicalGaussianBlur', 'BioMedicalRandomGamma', 'BioMedical3DPad', 'RandomRotFlip', 'SynapseDataset', 'REFUGEDataset', 'MapillaryDataset_v1', +<<<<<<< HEAD 'MapillaryDataset_v2', 'Albu', 'LEVIRCDDataset', 'LoadMultipleRSImageFromFile', 'LoadSingleRSImageFromFile', 'ConcatCDInput', 'BaseCDDataset' +======= + 'MapillaryDataset_v2', 'DSDLSegDataset' +>>>>>>> [Feature] Support DSDL Dataset ] diff --git a/mmseg/datasets/dsdl.py b/mmseg/datasets/dsdl.py new file mode 100644 index 00000000000..bf7e4e61b5f --- /dev/null +++ b/mmseg/datasets/dsdl.py @@ -0,0 +1,116 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +from typing import Dict, List, Optional, Sequence, Union + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + +try: + from dsdl.dataset import DSDLDataset +except ImportError: + DSDLDataset = None + + +@DATASETS.register_module() +class DSDLSegDataset(BaseSegDataset): + """Dataset for dsdl segmentation. + + Args: + specific_key_path(dict): Path of specific key which can not + be loaded by it's field name. + pre_transform(dict): pre-transform functions before loading. + used_labels(sequence): list of actual used classes in train steps, + this must be subset of class domain. + """ + + METAINFO = {} + + def __init__(self, + specific_key_path: Dict = {}, + pre_transform: Dict = {}, + used_labels: Optional[Sequence] = None, + **kwargs) -> None: + + if DSDLDataset is None: + raise RuntimeError( + 'Package dsdl is not installed. Please run "pip install dsdl".' + ) + self.used_labels = used_labels + + loc_config = dict(type='LocalFileReader', working_dir='') + if kwargs.get('data_root'): + kwargs['ann_file'] = os.path.join(kwargs['data_root'], + kwargs['ann_file']) + required_fields = ['Image', 'LabelMap'] + + self.dsdldataset = DSDLDataset( + dsdl_yaml=kwargs['ann_file'], + location_config=loc_config, + required_fields=required_fields, + specific_key_path=specific_key_path, + transform=pre_transform, + ) + BaseSegDataset.__init__(self, **kwargs) + + def load_data_list(self) -> List[Dict]: + """Load data info from a dsdl yaml file named as ``self.ann_file`` + + Returns: + List[dict]: A list of data list. + """ + + if self.used_labels: + self._metainfo['classes'] = tuple(self.used_labels) + self.label_map = self.get_label_map(self.used_labels) + else: + self._metainfo['classes'] = tuple(['background'] + + self.dsdldataset.class_names) + data_list = [] + + for i, data in enumerate(self.dsdldataset): + datainfo = dict( + img_path=os.path.join(self.data_prefix['img_path'], + data['Image'][0].location), + seg_map_path=os.path.join(self.data_prefix['seg_map_path'], + data['LabelMap'][0].location), + label_map=self.label_map, + reduce_zero_label=self.reduce_zero_label, + seg_fields=[], + ) + data_list.append(datainfo) + + return data_list + + def get_label_map(self, + new_classes: Optional[Sequence] = None + ) -> Union[Dict, None]: + """Require label mapping. + + The ``label_map`` is a dictionary, its keys are the old label ids and + its values are the new label ids, and is used for changing pixel + labels in load_annotations. If and only if old classes in class_dom + is not equal to new classes in args and nether of them is not + None, `label_map` is not None. + Args: + new_classes (list, tuple, optional): The new classes name from + metainfo. Default to None. + Returns: + dict, optional: The mapping from old classes to new classes. + """ + old_classes = ['background'] + self.dsdldataset.class_names + if (new_classes is not None and old_classes is not None + and list(new_classes) != list(old_classes)): + + label_map = {} + if not set(new_classes).issubset(old_classes): + raise ValueError( + f'new classes {new_classes} is not a ' + f'subset of classes {old_classes} in class_dom.') + for i, c in enumerate(old_classes): + if c not in new_classes: + label_map[i] = 255 + else: + label_map[i] = new_classes.index(c) + return label_map + else: + return None diff --git a/model-index.yml b/model-index.yml index 3ed1c1cdc33..3fe6a2e03da 100644 --- a/model-index.yml +++ b/model-index.yml @@ -14,6 +14,7 @@ Import: - configs/dmnet/metafile.yaml - configs/dnlnet/metafile.yaml - configs/dpt/metafile.yaml +- configs/dsdl/metafile.yaml - configs/emanet/metafile.yaml - configs/encnet/metafile.yaml - configs/erfnet/metafile.yaml diff --git a/tests/data/dsdl_seg/config.py b/tests/data/dsdl_seg/config.py new file mode 100755 index 00000000000..8eed751c2f7 --- /dev/null +++ b/tests/data/dsdl_seg/config.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. +local = dict( + type='LocalFileReader', + working_dir='/nvme/share_data/VOC2012', +) + +ali_oss = dict( + type='AliOSSFileReader', + access_key_secret='your secret key of aliyun oss', + endpoint='your endpoint of aliyun oss', + access_key_id='your access key of aliyun oss', + bucket_name='your bucket name of aliyun oss', + working_dir='the relative path of your media dir in the bucket') diff --git a/tests/data/dsdl_seg/defs/class-dom.yaml b/tests/data/dsdl_seg/defs/class-dom.yaml new file mode 100755 index 00000000000..e5dd598c4ae --- /dev/null +++ b/tests/data/dsdl_seg/defs/class-dom.yaml @@ -0,0 +1,24 @@ +$dsdl-version: "0.5.0" +VOCClassDom: + $def: class_domain + classes: + - aeroplane + - bicycle + - bird + - boat + - bottle + - bus + - car + - cat + - chair + - cow + - diningtable + - dog + - horse + - motorbike + - person + - pottedplant + - sheep + - sofa + - train + - tvmonitor diff --git a/tests/data/dsdl_seg/defs/segmentation-def.yaml b/tests/data/dsdl_seg/defs/segmentation-def.yaml new file mode 100755 index 00000000000..057139ed57e --- /dev/null +++ b/tests/data/dsdl_seg/defs/segmentation-def.yaml @@ -0,0 +1,15 @@ +$dsdl-version: "0.5.0" + +ImageMedia: + $def: struct + $fields: + image: Image + image_shape: ImageShape + +SegmentationSample: + $def: struct + $params: ['cdom'] + $fields: + media: ImageMedia + label_map: LabelMap[dom=$cdom] + instance_map: InstanceMap diff --git a/tests/data/dsdl_seg/set-train/train.yaml b/tests/data/dsdl_seg/set-train/train.yaml new file mode 100755 index 00000000000..69872445a53 --- /dev/null +++ b/tests/data/dsdl_seg/set-train/train.yaml @@ -0,0 +1,15 @@ +$dsdl-version: "0.5.0" +$import: + - ../defs/segmentation-def + - ../defs/class-dom +meta: + dataset_name: "VOC2012" + sub_dataset_name: "train" + task_type: "Segmentation" + dataset_homepage: "http://host.robots.ox.ac.uk/pascal/VOC/voc2012/index.html" + dataset_publisher: "University of Leeds | ETHZ, Zurich | University of Edinburgh\ + \ |Microsoft Research Cambridge | University of Oxford" + OpenDataLab_address: "https://opendatalab.com/PASCAL_VOC2012/download" +data: + sample-type: SegmentationSample[cdom=VOCClassDom] + sample-path: train_samples.json diff --git a/tests/data/dsdl_seg/set-train/train_samples.json b/tests/data/dsdl_seg/set-train/train_samples.json new file mode 100755 index 00000000000..559f5845721 --- /dev/null +++ b/tests/data/dsdl_seg/set-train/train_samples.json @@ -0,0 +1 @@ +{"samples": [{"media": {"image": "JPEGImages/2007_000032.jpg", "image_shape": [281, 500]}, "label_map": "SegmentationClass/2007_000032.png", "instance_map": "SegmentationObject/2007_000032.png"}, {"media": {"image": "JPEGImages/2007_000039.jpg", "image_shape": [375, 500]}, "label_map": "SegmentationClass/2007_000039.png", "instance_map": "SegmentationObject/2007_000039.png"}, {"media": {"image": "JPEGImages/2007_000063.jpg", "image_shape": [375, 500]}, "label_map": "SegmentationClass/2007_000063.png", "instance_map": "SegmentationObject/2007_000063.png"}]} diff --git a/tests/test_datasets/test_dataset.py b/tests/test_datasets/test_dataset.py index db4a7799069..219108c7bec 100644 --- a/tests/test_datasets/test_dataset.py +++ b/tests/test_datasets/test_dataset.py @@ -6,14 +6,19 @@ import pytest from mmseg.datasets import (ADE20KDataset, BaseSegDataset, CityscapesDataset, - COCOStuffDataset, DecathlonDataset, ISPRSDataset, - LIPDataset, LoveDADataset, MapillaryDataset_v1, - MapillaryDataset_v2, PascalVOCDataset, - PotsdamDataset, REFUGEDataset, SynapseDataset, - iSAIDDataset) + COCOStuffDataset, DecathlonDataset, DSDLSegDataset, + ISPRSDataset, LIPDataset, LoveDADataset, + MapillaryDataset_v1, MapillaryDataset_v2, + PascalVOCDataset, PotsdamDataset, REFUGEDataset, + SynapseDataset, iSAIDDataset) from mmseg.registry import DATASETS from mmseg.utils import get_classes, get_palette +try: + from dsdl.dataset import DSDLDataset +except ImportError: + DSDLDataset = None + def test_classes(): assert list( @@ -433,3 +438,13 @@ def test_custom_dataset_custom_palette(): ann_file=tempfile.mkdtemp(), metainfo=dict(classes=('bus', 'car'), palette=[[200, 200, 200]]), lazy_init=True) + + +def test_dsdlseg_dataset(): + if DSDLDataset is not None: + dataset = DSDLSegDataset( + data_root='tests/data/dsdl_seg', ann_file='set-train/train.yaml') + assert len(dataset) == 3 + assert len(dataset.metainfo['classes']) == 21 + else: + ImportWarning('Package `dsdl` is not installed.')