Repository: openvinotoolkit/datumaro

Issue:
Implement import for ADE20K dataset
Implement import of ADE20K dataset which is available [here](https://groups.csail.mit.edu/vision/datasets/ADE20K/). It is public dataset for semantic and instance segmentation tasks. Need to implement reading object and parts masks from directory with ADE20K dataset for Datumaro.


Discussion:

Resolving patch:
diff --git a/datumaro/plugins/ade20k2017_format.py b/datumaro/plugins/ade20k2017_format.py
new file mode 100644
--- /dev/null
+++ b/datumaro/plugins/ade20k2017_format.py
@@ -0,0 +1,149 @@
+# Copyright (C) 2020-2021 Intel Corporation
+#
+# SPDX-License-Identifier: MIT
+
+import glob
+import logging as log
+import os
+import os.path as osp
+import re
+
+import numpy as np
+
+from datumaro.components.extractor import (
+    AnnotationType, CompiledMask, DatasetItem, Extractor, Importer,
+    LabelCategories, Mask,
+)
+from datumaro.util.image import (
+    IMAGE_EXTENSIONS, find_images, lazy_image, load_image,
+)
+
+
+class Ade20k2017Path:
+    MASK_PATTERN = re.compile(r'''.+_seg
+        | .+_parts_\d+
+    ''', re.VERBOSE)
+
+
+class Ade20k2017Extractor(Extractor):
+    def __init__(self, path):
+        if not osp.isdir(path):
+            raise FileNotFoundError("Can't read dataset directory '%s'" % path)
+
+        subsets = os.listdir(path)
+        if len(subsets) < 1:
+            raise FileNotFoundError("Can't read subsets in directory '%s'" % path)
+
+        super().__init__(subsets=sorted(subsets))
+        self._path = path
+
+        self._items = []
+        self._categories  = {}
+
+        for subset in self._subsets:
+            self._load_items(subset)
+
+    def __iter__(self):
+        return iter(self._items)
+
+    def categories(self):
+        return self._categories
+
+    def _load_items(self, subset):
+        labels = self._categories.setdefault(AnnotationType.label,
+            LabelCategories())
+        path = osp.join(self._path, subset)
+
+        images = [i for i in find_images(path, recursive=True)]
+
+        for image_path in sorted(images):
+            item_id = osp.splitext(osp.relpath(image_path, path))[0]
+
+            if Ade20k2017Path.MASK_PATTERN.fullmatch(osp.basename(item_id)):
+                continue
+
+            item_annotations = []
+
+            item_info = self._load_item_info(image_path)
+            for item in item_info:
+                label_idx = labels.find(item['label_name'])[0]
+                if label_idx is None:
+                    labels.add(item['label_name'])
+
+            mask_path = osp.splitext(image_path)[0] + '_seg.png'
+            if not osp.isfile(mask_path):
+                log.warning("Can't find mask for image: %s" % image_path)
+
+            part_level = 0
+            max_part_level = max([p['part_level'] for p in item_info])
+            for part_level in range(max_part_level + 1):
+                if not osp.exists(mask_path):
+                    log.warning('Can`t find part level %s mask for %s' \
+                        % (part_level, image_path))
+                    continue
+
+                mask = lazy_image(mask_path, loader=self._load_instance_mask)
+                mask = CompiledMask(instance_mask=mask)
+
+                for v in item_info:
+                    if v['part_level'] != part_level:
+                        continue
+
+                    label_id = labels.find(v['label_name'])[0]
+                    instance_id = v['id']
+                    attributes = {k: True for k in v['attributes']}
+
+                    item_annotations.append(Mask(label=label_id,
+                        image=mask.lazy_extract(instance_id), id=instance_id,
+                        attributes=attributes, z_order=part_level,
+                        group=instance_id
+                    ))
+
+                mask_path = osp.splitext(image_path)[0] \
+                    + '_parts_%s.png' % (part_level + 1)
+
+            self._items.append(DatasetItem(item_id, subset=subset,
+                image=image_path, annotations=item_annotations))
+
+    def _load_item_info(self, path):
+        attr_path = osp.splitext(path)[0] + '_atr.txt'
+        if not osp.isfile(attr_path):
+            raise Exception("Can't find annotation file for image %s" % path)
+
+        item_info = []
+        with open(attr_path, 'r', encoding='utf-8') as f:
+            for line in f:
+                columns = [s.strip() for s in line.split('#')]
+                if len(columns) != 6:
+                    raise Exception('Invalid line in %s' % attr_path)
+                if columns[5][0] != '"' or columns[5][-1] != '"':
+                    raise Exception('Attributes column are expected \
+                        in double quotes, file %s' % attr_path)
+                attributes = [s.strip()
+                    for s in columns[5][1:-1].split(',') if s]
+
+                item_info.append({
+                    'id': int(columns[0]),
+                    'part_level': int(columns[1]),
+                    'occluded': int(columns[2]),
+                    'label_name': columns[4],
+                    'attributes': attributes
+                })
+
+        return item_info
+
+    @staticmethod
+    def _load_instance_mask(path):
+        mask = load_image(path)
+        _, instance_mask = np.unique(mask[:, :, 0], return_inverse=True)
+        instance_mask = instance_mask.reshape(mask[:, :, 0].shape)
+        return instance_mask
+
+class Ade20k2017Importer(Importer):
+    @classmethod
+    def find_sources(cls, path):
+        for i in range(5):
+            for i in glob.iglob(osp.join(path, *('*' * i))):
+                    if osp.splitext(i)[1].lower() in IMAGE_EXTENSIONS:
+                        return [{'url': path, 'format': 'ade20k2017'}]
+        return []
diff --git a/datumaro/plugins/ade20k2020_format.py b/datumaro/plugins/ade20k2020_format.py
new file mode 100644
--- /dev/null
+++ b/datumaro/plugins/ade20k2020_format.py
@@ -0,0 +1,183 @@
+# Copyright (C) 2020-2021 Intel Corporation
+#
+# SPDX-License-Identifier: MIT
+
+import glob
+import json
+import logging as log
+import os
+import os.path as osp
+import re
+
+import numpy as np
+
+from datumaro.components.extractor import (
+    AnnotationType, CompiledMask, DatasetItem, Extractor, Importer,
+    LabelCategories, Mask, Polygon,
+)
+from datumaro.util.image import (
+    IMAGE_EXTENSIONS, find_images, lazy_image, load_image,
+)
+
+
+class Ade20k2020Path:
+    MASK_PATTERN = re.compile(r'''.+_seg
+        | .+_parts_\d+
+        | instance_.+
+    ''', re.VERBOSE)
+
+
+class Ade20k2020Extractor(Extractor):
+    def __init__(self, path):
+        if not osp.isdir(path):
+            raise FileNotFoundError("Can't read dataset directory '%s'" % path)
+
+        subsets = os.listdir(path)
+        if len(subsets) < 1:
+            raise FileNotFoundError("Can't read subsets in directory '%s'" % path)
+
+        super().__init__(subsets=sorted(subsets))
+        self._path = path
+
+        self._items = []
+        self._categories  = {}
+
+        for subset in self._subsets:
+            self._load_items(subset)
+
+    def __iter__(self):
+        return iter(self._items)
+
+    def categories(self):
+        return self._categories
+
+    def _load_items(self, subset):
+        labels = self._categories.setdefault(AnnotationType.label,
+            LabelCategories())
+        path = osp.join(self._path, subset)
+
+        images = [i for i in find_images(path, recursive=True)]
+
+        for image_path in sorted(images):
+            item_id = osp.splitext(osp.relpath(image_path, path))[0]
+
+            if Ade20k2020Path.MASK_PATTERN.fullmatch(osp.basename(item_id)):
+                continue
+
+            item_annotations = []
+            item_info = self._load_item_info(image_path)
+            for item in item_info:
+                label_idx = labels.find(item['label_name'])[0]
+                if label_idx is None:
+                    labels.add(item['label_name'])
+
+            mask_path = osp.splitext(image_path)[0] + '_seg.png'
+            max_part_level = max([p['part_level'] for p in item_info])
+            for part_level in range(max_part_level + 1):
+                if not osp.exists(mask_path):
+                    log.warning('Can`t find part level %s mask for %s' \
+                        % (part_level, image_path))
+                    continue
+
+                mask = lazy_image(mask_path, loader=self._load_class_mask)
+                mask = CompiledMask(instance_mask=mask)
+
+                classes = {(v['class_idx'], v['label_name'])
+                    for v in item_info if v['part_level'] == part_level}
+
+                for class_idx, label_name in classes:
+                    label_id = labels.find(label_name)[0]
+                    item_annotations.append(Mask(label=label_id, id=class_idx,
+                        image=mask.lazy_extract(class_idx),
+                        group=class_idx, z_order=part_level
+                    ))
+
+                mask_path = osp.splitext(image_path)[0] \
+                    + '_parts_%s.png' % (part_level + 1)
+
+            for item in item_info:
+                instance_path = osp.join(osp.dirname(image_path),
+                    item['instance_mask'])
+                if not osp.isfile(instance_path):
+                    log.warning('Can`t find instance mask: %s' % instance_path)
+                    continue
+
+                mask = lazy_image(instance_path, loader=self._load_instance_mask)
+                mask = CompiledMask(instance_mask=mask)
+
+                label_id = labels.find(item['label_name'])[0]
+                instance_id = item['id']
+                attributes = {k: True for k in item['attributes']}
+                polygon_points = item['polygon_points']
+
+                item_annotations.append(Mask(label=label_id,
+                    image=mask.lazy_extract(1), id=instance_id,
+                    attributes=attributes, z_order=item['part_level'],
+                    group=instance_id
+                ))
+
+                if (len(item['polygon_points']) % 2 == 0 \
+                        and 3 <= len(item['polygon_points']) // 2):
+                    item_annotations.append(Polygon(polygon_points,
+                        label=label_id, attributes=attributes, id=instance_id,
+                        z_order=item['part_level'], group=instance_id
+                    ))
+
+            self._items.append(DatasetItem(item_id, subset=subset,
+                image=image_path, annotations=item_annotations))
+
+    def _load_item_info(self, path):
+        json_path = osp.splitext(path)[0] + '.json'
+        item_info = []
+        if not osp.isfile(json_path):
+            raise Exception("Can't find annotation file (*.json) \
+                for image %s" % path)
+
+        with open(json_path, 'r', encoding='latin-1') as f:
+            item_objects = json.load(f)['annotation']['object']
+            for obj in item_objects:
+                polygon_points = []
+                for x, y in zip(obj['polygon']['x'], obj['polygon']['y']):
+                    polygon_points.append(x)
+                    polygon_points.append(y)
+
+                attributes = obj['attributes']
+                if isinstance(attributes, str):
+                    attributes = [attributes]
+
+                item_info.append({
+                    'id': obj['id'],
+                    'class_idx': obj['name_ndx'],
+                    'part_level': obj['parts']['part_level'],
+                    'occluded': int(obj['occluded'] == 'yes'),
+                    'crop': obj['crop'],
+                    'label_name': obj['raw_name'],
+                    'attributes': attributes,
+                    'instance_mask': obj['instance_mask'],
+                    'polygon_points': polygon_points
+                })
+
+        return item_info
+
+    @staticmethod
+    def _load_instance_mask(path):
+        mask = load_image(path)
+        _, instance_mask = np.unique(mask, return_inverse=True)
+        instance_mask = instance_mask.reshape(mask.shape)
+        return instance_mask
+
+    @staticmethod
+    def _load_class_mask(path):
+        mask = load_image(path)
+        mask = ((mask[:, :, 2] / 10).astype(np.int32) << 8) \
+            + mask[:, :, 1].astype(np.int32)
+        return mask
+
+class Ade20k2020Importer(Importer):
+    @classmethod
+    def find_sources(cls, path):
+        for i in range(5):
+            for i in glob.iglob(osp.join(path, *('*' * i))):
+                    if osp.splitext(i)[1].lower() in IMAGE_EXTENSIONS:
+                        return [{'url': path, 'format': 'ade20k2020'}]
+        return []


Test patch:
diff --git a/tests/assets/ade20k2017_dataset/training/street/1.jpg b/tests/assets/ade20k2017_dataset/training/street/1.jpg
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2017_dataset/training/street/1.jpg differ
diff --git a/tests/assets/ade20k2017_dataset/training/street/1_atr.txt b/tests/assets/ade20k2017_dataset/training/street/1_atr.txt
new file mode 100644
--- /dev/null
+++ b/tests/assets/ade20k2017_dataset/training/street/1_atr.txt
@@ -0,0 +1,3 @@
+001 # 0 # 1 # sky # sky # ""
+002 # 0 # 0 # person, individual, someone, somebody, mortal, soul # person # "walkin"
+001 # 1 # 0 # license plate # license plate # ""
diff --git a/tests/assets/ade20k2017_dataset/training/street/1_parts_1.png b/tests/assets/ade20k2017_dataset/training/street/1_parts_1.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2017_dataset/training/street/1_parts_1.png differ
diff --git a/tests/assets/ade20k2017_dataset/training/street/1_seg.png b/tests/assets/ade20k2017_dataset/training/street/1_seg.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2017_dataset/training/street/1_seg.png differ
diff --git a/tests/assets/ade20k2017_dataset/validation/2.jpg b/tests/assets/ade20k2017_dataset/validation/2.jpg
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2017_dataset/validation/2.jpg differ
diff --git a/tests/assets/ade20k2017_dataset/validation/2_atr.txt b/tests/assets/ade20k2017_dataset/validation/2_atr.txt
new file mode 100644
--- /dev/null
+++ b/tests/assets/ade20k2017_dataset/validation/2_atr.txt
@@ -0,0 +1,4 @@
+001 # 0 # 0 # sky # sky # ""
+002 # 0 # 0 # person, individual, someone, somebody, mortal, soul # person # ""
+001 # 1 # 0 # license plate # license plate # ""
+001 # 2 # 0 # rim # rim # ""
\ No newline at end of file
diff --git a/tests/assets/ade20k2017_dataset/validation/2_parts_1.png b/tests/assets/ade20k2017_dataset/validation/2_parts_1.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2017_dataset/validation/2_parts_1.png differ
diff --git a/tests/assets/ade20k2017_dataset/validation/2_parts_2.png b/tests/assets/ade20k2017_dataset/validation/2_parts_2.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2017_dataset/validation/2_parts_2.png differ
diff --git a/tests/assets/ade20k2017_dataset/validation/2_seg.png b/tests/assets/ade20k2017_dataset/validation/2_seg.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2017_dataset/validation/2_seg.png differ
diff --git a/tests/assets/ade20k2020_dataset/training/street/1.jpg b/tests/assets/ade20k2020_dataset/training/street/1.jpg
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/training/street/1.jpg differ
diff --git a/tests/assets/ade20k2020_dataset/training/street/1.json b/tests/assets/ade20k2020_dataset/training/street/1.json
new file mode 100644
--- /dev/null
+++ b/tests/assets/ade20k2020_dataset/training/street/1.json
@@ -0,0 +1,110 @@
+{
+  "annotation": {
+    "filename": "1.jpg",
+    "folder": "training/street",
+    "imsize": [
+      5,
+      5,
+      3
+    ],
+    "source": {
+      "folder": "",
+      "filename": "",
+      "origin": ""
+    },
+    "scene": [
+      "outdoor",
+      "urban",
+      "street"
+    ],
+    "object": [
+      {
+        "id": 0,
+        "name": "car, auto, automobile, machine, motorcar",
+        "name_ndx": 401,
+        "hypernym": [
+          "car, auto, automobile, machine, motorcar",
+          "motor vehicle, automotive vehicle",
+          "self-propelled vehicle",
+          "wheeled vehicle",
+          "vehicle",
+          "conveyance, transport",
+          "instrumentality, instrumentation",
+          "artifact, artefact",
+          "whole, unit",
+          "object, physical object",
+          "physical entity",
+          "entity"
+        ],
+        "raw_name": "car",
+        "attributes": [],
+        "depth_ordering_rank": 2,
+        "occluded": [],
+        "crop": 0,
+        "parts": {
+          "hasparts": [],
+          "ispartof": [],
+          "part_level": 0
+        },
+        "instance_mask": "1/instance_000_ADE_train_1.png",
+        "polygon": {
+          "x": [],
+          "y": []
+        },
+        "saved_date": "1-Jan-1970 00:00:00"
+      },
+      {
+        "id": 1,
+        "name": "person, individual, someone, somebody, mortal, soul",
+        "name_ndx": 1831,
+        "hypernym": [
+          "person, individual, someone, somebody, mortal, soul",
+          "organism, being",
+          "living thing, animate thing",
+          "whole, unit",
+          "object, physical object",
+          "physical entity",
+          "entity"
+        ],
+        "raw_name": "person",
+        "attributes": ["walkin"],
+        "depth_ordering_rank": 1,
+        "occluded": "no",
+        "crop": 0,
+        "parts": {
+          "hasparts": [],
+          "ispartof": [],
+          "part_level": 0
+        },
+        "instance_mask": "1/instance_001_ADE_train_1.png",
+        "polygon": {
+          "x": [1, 1, 1, 1, 1],
+          "y": [0, 1, 2, 3, 4]
+        },
+        "saved_date": "1-Jan-1970 00:00:00"
+      },
+      {
+        "id": 2,
+        "name": "door",
+        "name_ndx": 774,
+        "hypernym": ["door", "doors", "garage door"],
+        "raw_name": "door",
+        "attributes": [],
+        "depth_ordering_rank": 3,
+        "occluded": "yes",
+        "crop": 0,
+        "parts": {
+          "hasparts": [],
+          "ispartof": 0,
+          "part_level": 1
+        },
+        "instance_mask": "1/instance_002_ADE_train_1.png",
+        "polygon": {
+          "x": [],
+          "y": []
+        },
+        "saved_date": "1-Jan-1970 00:00:00"
+      }
+        ]
+  }
+}
diff --git a/tests/assets/ade20k2020_dataset/training/street/1/instance_000_ADE_train_1.png b/tests/assets/ade20k2020_dataset/training/street/1/instance_000_ADE_train_1.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/training/street/1/instance_000_ADE_train_1.png differ
diff --git a/tests/assets/ade20k2020_dataset/training/street/1/instance_001_ADE_train_1.png b/tests/assets/ade20k2020_dataset/training/street/1/instance_001_ADE_train_1.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/training/street/1/instance_001_ADE_train_1.png differ
diff --git a/tests/assets/ade20k2020_dataset/training/street/1/instance_002_ADE_train_1.png b/tests/assets/ade20k2020_dataset/training/street/1/instance_002_ADE_train_1.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/training/street/1/instance_002_ADE_train_1.png differ
diff --git a/tests/assets/ade20k2020_dataset/training/street/1_parts_1.png b/tests/assets/ade20k2020_dataset/training/street/1_parts_1.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/training/street/1_parts_1.png differ
diff --git a/tests/assets/ade20k2020_dataset/training/street/1_seg.png b/tests/assets/ade20k2020_dataset/training/street/1_seg.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/training/street/1_seg.png differ
diff --git a/tests/assets/ade20k2020_dataset/validation/2.jpg b/tests/assets/ade20k2020_dataset/validation/2.jpg
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/validation/2.jpg differ
diff --git a/tests/assets/ade20k2020_dataset/validation/2.json b/tests/assets/ade20k2020_dataset/validation/2.json
new file mode 100644
--- /dev/null
+++ b/tests/assets/ade20k2020_dataset/validation/2.json
@@ -0,0 +1,132 @@
+{
+  "annotation": {
+    "filename": "2.jpg",
+    "folder": "validation",
+    "imsize": [
+      5,
+      5,
+      3
+    ],
+    "source": {
+      "folder": "",
+      "filename": "",
+      "origin": ""
+    },
+    "scene": [
+      "outdoor",
+      "urban",
+      "street"
+    ],
+    "object": [
+      {
+        "id": 0,
+        "name": "car, auto, automobile, machine, motorcar",
+        "name_ndx": 401,
+        "hypernym": [
+          "car, auto, automobile, machine, motorcar",
+          "motor vehicle, automotive vehicle",
+          "self-propelled vehicle",
+          "wheeled vehicle",
+          "vehicle",
+          "conveyance, transport",
+          "instrumentality, instrumentation",
+          "artifact, artefact",
+          "whole, unit",
+          "object, physical object",
+          "physical entity",
+          "entity"
+        ],
+        "raw_name": "car",
+        "attributes": [],
+        "depth_ordering_rank": 2,
+        "occluded": [],
+        "crop": 0,
+        "parts": {
+          "hasparts": [],
+          "ispartof": [],
+          "part_level": 0
+        },
+        "instance_mask": "2/instance_000_ADE_val_2.png",
+        "polygon": {
+          "x": [],
+          "y": []
+        },
+        "saved_date": "1-Jan-1970 00:00:00"
+      },
+      {
+        "id": 1,
+        "name": "person, individual, someone, somebody, mortal, soul",
+        "name_ndx": 1831,
+        "hypernym": [
+          "person, individual, someone, somebody, mortal, soul",
+          "organism, being",
+          "living thing, animate thing",
+          "whole, unit",
+          "object, physical object",
+          "physical entity",
+          "entity"
+        ],
+        "raw_name": "person",
+        "attributes": ["walkin"],
+        "depth_ordering_rank": 1,
+        "occluded": "no",
+        "crop": 0,
+        "parts": {
+          "hasparts": [],
+          "ispartof": [],
+          "part_level": 0
+        },
+        "instance_mask": "2/instance_001_ADE_val_2.png",
+        "polygon": {
+          "x": [],
+          "y": []
+        },
+        "saved_date": "1-Jan-1970 00:00:00"
+      },
+      {
+        "id": 2,
+        "name": "door",
+        "name_ndx": 774,
+        "hypernym": ["door", "doors", "garage door"],
+        "raw_name": "door",
+        "attributes": [],
+        "depth_ordering_rank": 3,
+        "occluded": "yes",
+        "crop": 0,
+        "parts": {
+          "hasparts": [],
+          "ispartof": 0,
+          "part_level": 1
+        },
+        "instance_mask": "2/instance_002_ADE_val_2.png",
+        "polygon": {
+          "x": [],
+          "y": []
+        },
+        "saved_date": "1-Jan-1970 00:00:00"
+      },
+      {
+        "id": 3,
+        "name": "rim",
+        "name_ndx": 2122,
+        "hypernym": [],
+        "raw_name": "rim",
+        "attributes": [],
+        "depth_ordering_rank": 3,
+        "occluded": "no",
+        "crop": 0,
+        "parts": {
+          "hasparts": [],
+          "ispartof": 2,
+          "part_level": 2
+        },
+        "instance_mask": "2/instance_003_ADE_val_2.png",
+        "polygon": {
+          "x": [],
+          "y": []
+        },
+        "saved_date": "1-Jan-1970 00:00:00"
+      }
+    ]
+  }
+}
diff --git a/tests/assets/ade20k2020_dataset/validation/2/instance_000_ADE_val_2.png b/tests/assets/ade20k2020_dataset/validation/2/instance_000_ADE_val_2.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/validation/2/instance_000_ADE_val_2.png differ
diff --git a/tests/assets/ade20k2020_dataset/validation/2/instance_001_ADE_val_2.png b/tests/assets/ade20k2020_dataset/validation/2/instance_001_ADE_val_2.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/validation/2/instance_001_ADE_val_2.png differ
diff --git a/tests/assets/ade20k2020_dataset/validation/2/instance_002_ADE_val_2.png b/tests/assets/ade20k2020_dataset/validation/2/instance_002_ADE_val_2.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/validation/2/instance_002_ADE_val_2.png differ
diff --git a/tests/assets/ade20k2020_dataset/validation/2/instance_003_ADE_val_2.png b/tests/assets/ade20k2020_dataset/validation/2/instance_003_ADE_val_2.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/validation/2/instance_003_ADE_val_2.png differ
diff --git a/tests/assets/ade20k2020_dataset/validation/2_parts_1.png b/tests/assets/ade20k2020_dataset/validation/2_parts_1.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/validation/2_parts_1.png differ
diff --git a/tests/assets/ade20k2020_dataset/validation/2_parts_2.png b/tests/assets/ade20k2020_dataset/validation/2_parts_2.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/validation/2_parts_2.png differ
diff --git a/tests/assets/ade20k2020_dataset/validation/2_seg.png b/tests/assets/ade20k2020_dataset/validation/2_seg.png
new file mode 100644
Binary files /dev/null and b/tests/assets/ade20k2020_dataset/validation/2_seg.png differ
diff --git a/tests/requirements.py b/tests/requirements.py
--- a/tests/requirements.py
+++ b/tests/requirements.py
@@ -24,6 +24,7 @@ class Requirements:
     DATUM_274 = "Support the Open Images dataset"
     DATUM_280 = "Support KITTI dataset formats"
     DATUM_283 = "Create cli tests for testing convert command for VOC format"
+    DATUM_399 = "Implement import for ADE20K dataset"
 
     # GitHub issues (bugs)
     # https://github.com/openvinotoolkit/datumaro/issues
diff --git a/tests/test_ade20k2017_format.py b/tests/test_ade20k2017_format.py
new file mode 100644
--- /dev/null
+++ b/tests/test_ade20k2017_format.py
@@ -0,0 +1,58 @@
+# Copyright (C) 2021 Intel Corporation
+#
+# SPDX-License-Identifier: MIT
+
+from unittest.case import TestCase
+import os.path as osp
+
+import numpy as np
+
+from datumaro.components.dataset import Dataset, DatasetItem
+from datumaro.components.extractor import AnnotationType, LabelCategories, Mask
+from datumaro.plugins.ade20k2017_format import Ade20k2017Importer
+from datumaro.util.test_utils import compare_datasets
+
+from tests.requirements import Requirements, mark_requirement
+
+DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'ade20k2017_dataset')
+
+class Ade20k2017ImporterTest(TestCase):
+    @mark_requirement(Requirements.DATUM_399)
+    def test_can_detect(self):
+        self.assertTrue(Ade20k2017Importer.detect(DUMMY_DATASET_DIR))
+
+    @mark_requirement(Requirements.DATUM_399)
+    def test_can_import(self):
+        expected_dataset = Dataset.from_iterable(
+            [
+                DatasetItem(id='street/1', subset='training',
+                    image=np.ones((3, 4, 3)),
+                    annotations=[
+                        Mask(image=np.array([[0, 1, 0, 0]] * 3), label=0,
+                            group=1, z_order=0, id=1),
+                        Mask(image=np.array([[0, 0, 0, 1]] * 3), label=2,
+                            group=1, z_order=1, id=1),
+                        Mask(image=np.array([[0, 0, 1, 1]] * 3),
+                            group=2, label=1, z_order=0, id=2,
+                            attributes={'walkin': True})
+                    ]),
+                DatasetItem(id='2', subset='validation',
+                    image=np.ones((3, 4, 3)),
+                    annotations=[
+                        Mask(image=np.array([[0, 1, 0, 1]] * 3), label=0, id=1,
+                            z_order=0, group=1),
+                        Mask(image=np.array([[0, 0, 1, 0]] * 3), label=1, id=2,
+                            z_order=0, group=2),
+                        Mask(image=np.array([[0, 1, 0, 1]] * 3), label=2, id=1,
+                            z_order=1, group=1),
+                        Mask(image=np.array([[0, 1, 0, 0]] * 3), label=3, id=1,
+                            z_order=2, group=1)
+                    ])
+            ], categories={AnnotationType.label: LabelCategories.from_iterable([
+                    'sky', 'person', 'license plate', 'rim'])
+                }
+        )
+
+        imported_dataset = Dataset.import_from(DUMMY_DATASET_DIR, 'ade20k2017')
+        compare_datasets(self, expected_dataset, imported_dataset,
+            require_images=True)
diff --git a/tests/test_ade20k2020_format.py b/tests/test_ade20k2020_format.py
new file mode 100644
--- /dev/null
+++ b/tests/test_ade20k2020_format.py
@@ -0,0 +1,78 @@
+# Copyright (C) 2021 Intel Corporation
+#
+# SPDX-License-Identifier: MIT
+
+from unittest.case import TestCase
+import os.path as osp
+
+import numpy as np
+
+from datumaro.components.dataset import Dataset, DatasetItem
+from datumaro.components.extractor import (
+    AnnotationType, LabelCategories, Mask, Polygon,
+)
+from datumaro.plugins.ade20k2020_format import Ade20k2020Importer
+from datumaro.util.test_utils import compare_datasets
+
+from tests.requirements import Requirements, mark_requirement
+
+DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'ade20k2020_dataset')
+
+class Ade20k2020ImporterTest(TestCase):
+    @mark_requirement(Requirements.DATUM_399)
+    def test_can_detect(self):
+        self.assertTrue(Ade20k2020Importer.detect(DUMMY_DATASET_DIR))
+
+    @mark_requirement(Requirements.DATUM_399)
+    def test_can_import(self):
+        expected_dataset = Dataset.from_iterable(
+            [
+                DatasetItem(id='street/1', subset='training',
+                    image=np.ones((5, 5, 3)),
+                    annotations=[
+                        Polygon([1, 0, 1, 1, 1, 2, 1, 3, 1, 4],
+                            group=1, z_order=0, id=1, label=1,
+                            attributes={'walkin': True}),
+                        Mask(image=np.array([[0, 0, 1, 1, 1]] * 5), label=0,
+                            group=401, z_order=0, id=401),
+                        Mask(image=np.array([[0, 1, 0, 0, 0]] * 5), label=1,
+                            group=1831, z_order=0, id=1831),
+                        Mask(image=np.array([[0, 0, 0, 1, 1]] * 5), label=2,
+                            id=774, group=774, z_order=1),
+                        Mask(image=np.array([[0, 0, 1, 1, 1]] * 5), label=0,
+                            group=0, z_order=0, id=0),
+                        Mask(image=np.array([[0, 1, 0, 0, 0]] * 5), label=1,
+                            group=1, z_order=0, id=1,
+                            attributes={'walkin': True}),
+                        Mask(image=np.array([[0, 0, 0, 1, 1]] * 5), label=2,
+                            group=2, z_order=1, id=2),
+                    ]),
+                DatasetItem(id='2', subset='validation',
+                    image=np.ones((5, 5, 3)),
+                    annotations=[
+                        Mask(image=np.array([[0, 0, 1, 1, 1]] * 5), label=0,
+                            group=401, z_order=0, id=401),
+                        Mask(image=np.array([[0, 1, 0, 0, 0]] * 5), label=1,
+                            group=1831, z_order=0, id=1831),
+                        Mask(image=np.array([[0, 0, 0, 0, 1]] * 5), label=3,
+                            group=2122, z_order=2, id=2122),
+                        Mask(image=np.array([[0, 0, 0, 1, 1]] * 5), label=2,
+                            group=774, z_order=1, id=774),
+                        Mask(image=np.array([[0, 0, 1, 1, 1]] * 5), label=0,
+                            group=0, z_order=0, id=0),
+                        Mask(image=np.array([[0, 1, 0, 0, 0]] * 5), label=1,
+                            group=1, z_order=0, id=1,
+                            attributes={'walkin': True}),
+                        Mask(image=np.array([[0, 0, 0, 1, 1]] * 5), label=2,
+                            group=2, z_order=1, id=2),
+                        Mask(image=np.array([[0, 0, 0, 0, 1]] * 5), label=3,
+                            group=3, z_order=2, id=3),
+                    ])
+            ], categories={AnnotationType.label: LabelCategories.from_iterable([
+                    'car', 'person', 'door', 'rim'])
+                }
+        )
+
+        imported_dataset = Dataset.import_from(DUMMY_DATASET_DIR, 'ade20k2020')
+        compare_datasets(self, expected_dataset, imported_dataset,
+            require_images=True)