diff --git a/CHANGELOG.md b/CHANGELOG.md index f465479a..8c2cc96e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add a tutorial on attaching cloud storage AWS-S3 () and Azure Blob Container () - The feature to remove annotations in a specified range of frames () +- Add KITTI segmentation and detection format () - Add LFW format () - Add Cityscapes format () - Add Open Images V6 format () diff --git a/cvat/apps/dataset_manager/formats/kitti.py b/cvat/apps/dataset_manager/formats/kitti.py new file mode 100644 index 00000000..84e05547 --- /dev/null +++ b/cvat/apps/dataset_manager/formats/kitti.py @@ -0,0 +1,53 @@ +# Copyright (C) 2021 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os.path as osp +from tempfile import TemporaryDirectory + +from datumaro.components.dataset import Dataset +from datumaro.plugins.kitti_format.format import KittiPath, write_label_map +from pyunpack import Archive + +from cvat.apps.dataset_manager.bindings import (GetCVATDataExtractor, + ProjectData, import_dm_annotations) +from cvat.apps.dataset_manager.util import make_zip_archive + +from .registry import dm_env, exporter, importer +from .utils import make_colormap + + +@exporter(name='KITTI', ext='ZIP', version='1.0') +def _export(dst_file, instance_data, save_images=False): + dataset = Dataset.from_extractors(GetCVATDataExtractor(instance_data, + include_images=save_images), env=dm_env) + + with TemporaryDirectory() as tmp_dir: + dataset.transform('polygons_to_masks') + dataset.transform('merge_instance_segments') + dataset.export(tmp_dir, format='kitti', + label_map={k: v[0] for k, v in make_colormap(instance_data).items()}, + apply_colormap=True, save_images=save_images + ) + + make_zip_archive(tmp_dir, dst_file) + +@importer(name='KITTI', ext='ZIP', version='1.0') +def _import(src_file, instance_data): + with TemporaryDirectory() as tmp_dir: + Archive(src_file.name).extractall(tmp_dir) + + color_map = {k: v[0] for k, v in make_colormap(instance_data).items()} + color_map_path = osp.join(tmp_dir, KittiPath.LABELMAP_FILE) + if not osp.isfile(color_map_path): + write_label_map(color_map_path, color_map) + + dataset = Dataset.import_from(tmp_dir, format='kitti', env=dm_env) + labels_meta = instance_data.meta['project']['labels'] \ + if isinstance(instance_data, ProjectData) else instance_data.meta['task']['labels'] + if 'background' not in [label['name'] for _, label in labels_meta]: + dataset.filter('/item/annotation[label != "background"]', + filter_annotations=True) + dataset.transform('masks_to_polygons') + + import_dm_annotations(dataset, instance_data) diff --git a/cvat/apps/dataset_manager/formats/registry.py b/cvat/apps/dataset_manager/formats/registry.py index 85dd7263..876c40fc 100644 --- a/cvat/apps/dataset_manager/formats/registry.py +++ b/cvat/apps/dataset_manager/formats/registry.py @@ -121,6 +121,7 @@ import cvat.apps.dataset_manager.formats.market1501 import cvat.apps.dataset_manager.formats.icdar import cvat.apps.dataset_manager.formats.velodynepoint import cvat.apps.dataset_manager.formats.pointcloud +import cvat.apps.dataset_manager.formats.kitti import cvat.apps.dataset_manager.formats.lfw import cvat.apps.dataset_manager.formats.cityscapes import cvat.apps.dataset_manager.formats.openimages diff --git a/cvat/apps/dataset_manager/formats/utils.py b/cvat/apps/dataset_manager/formats/utils.py index df3de855..71044c14 100644 --- a/cvat/apps/dataset_manager/formats/utils.py +++ b/cvat/apps/dataset_manager/formats/utils.py @@ -7,7 +7,6 @@ from hashlib import blake2s from datumaro.util.os_util import make_file_name - def get_color_from_index(index): def get_bit(number, index): return (number >> index) & 1 diff --git a/cvat/apps/dataset_manager/tests/assets/annotations.json b/cvat/apps/dataset_manager/tests/assets/annotations.json index 714f9099..1a447963 100644 --- a/cvat/apps/dataset_manager/tests/assets/annotations.json +++ b/cvat/apps/dataset_manager/tests/assets/annotations.json @@ -1198,6 +1198,24 @@ ], "tracks": [] }, + "KITTI 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [10.0, 20.0, 10.0, 25.0, 20.0, 10.0, 20.0, 25.0, 10.0, 20.0], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, "LFW 1.0": { "version": 0, "tags": [ diff --git a/cvat/apps/dataset_manager/tests/test_formats.py b/cvat/apps/dataset_manager/tests/test_formats.py index a76368bd..977a319f 100644 --- a/cvat/apps/dataset_manager/tests/test_formats.py +++ b/cvat/apps/dataset_manager/tests/test_formats.py @@ -296,6 +296,7 @@ class TaskExportTest(_DbTestBase): 'ICDAR Segmentation 1.0', 'Kitti Raw Format 1.0', 'Sly Point Cloud Format 1.0', + 'KITTI 1.0', 'LFW 1.0', 'Cityscapes 1.0', 'Open Images V6 1.0' @@ -325,6 +326,7 @@ class TaskExportTest(_DbTestBase): 'ICDAR Segmentation 1.0', 'Kitti Raw Format 1.0', 'Sly Point Cloud Format 1.0', + 'KITTI 1.0', 'LFW 1.0', 'Cityscapes 1.0', 'Open Images V6 1.0', @@ -376,6 +378,7 @@ class TaskExportTest(_DbTestBase): ('ICDAR Recognition 1.0', 'icdar_word_recognition'), ('ICDAR Localization 1.0', 'icdar_text_localization'), ('ICDAR Segmentation 1.0', 'icdar_text_segmentation'), + # ('KITTI 1.0', 'kitti') format does not support empty annotations ('LFW 1.0', 'lfw'), # ('Cityscapes 1.0', 'cityscapes'), does not support, empty annotations ]: @@ -785,17 +788,6 @@ class TaskAnnotationsImportTest(_DbTestBase): "type": "rectangle", "occluded": False, }] - elif annotation_format == "VGGFace2 1.0": - shapes = [{ - "frame": 1, - "label_id": task["labels"][1]["id"], - "group": None, - "source": "manual", - "attributes": [], - "points": [2.0, 2.1, 40, 50.7], - "type": "rectangle", - "occluded": False - }] else: rectangle_shape_wo_attrs = { "frame": 1, @@ -803,7 +795,7 @@ class TaskAnnotationsImportTest(_DbTestBase): "group": 0, "source": "manual", "attributes": [], - "points": [2.0, 2.1, 40, 50.7], + "points": [2.0, 2.1, 40, 10.7], "type": "rectangle", "occluded": False, } @@ -823,7 +815,7 @@ class TaskAnnotationsImportTest(_DbTestBase): "value": task["labels"][0]["attributes"][1]["default_value"] } ], - "points": [1.0, 2.1, 10.6, 53.22], + "points": [1.0, 2.1, 10.6, 13.22], "type": "rectangle", "occluded": False, } @@ -838,7 +830,7 @@ class TaskAnnotationsImportTest(_DbTestBase): { "frame": 0, "attributes": [], - "points": [1.0, 2.1, 10.6, 53.22, 90, 90.222], + "points": [1.0, 2.1, 10.6, 53.22, 30, 20.222], "type": "polygon", "occluded": False, "outside": False @@ -871,7 +863,7 @@ class TaskAnnotationsImportTest(_DbTestBase): } if annotation_format == "VGGFace2 1.0": - shapes = rectangle_shape_wo_attrs + shapes = [rectangle_shape_wo_attrs] elif annotation_format == "CVAT 1.1": shapes = [rectangle_shape_wo_attrs, rectangle_shape_with_attrs] diff --git a/cvat/apps/dataset_manager/tests/test_rest_api_formats.py b/cvat/apps/dataset_manager/tests/test_rest_api_formats.py index 780970e5..6e5d2ca7 100644 --- a/cvat/apps/dataset_manager/tests/test_rest_api_formats.py +++ b/cvat/apps/dataset_manager/tests/test_rest_api_formats.py @@ -872,6 +872,7 @@ class TaskDumpUploadTest(_DbTestBase): with self.subTest(format=upload_format_name): if upload_format_name in [ "MOTS PNG 1.0", # issue #2925 and changed points values + "KITTI 1.0", # format does not support empty annotation "Cityscapes 1.0" # formats doesn't support empty annotations ]: self.skipTest("Format is fail") @@ -920,7 +921,7 @@ class TaskDumpUploadTest(_DbTestBase): "PASCAL VOC 1.1", "Segmentation mask 1.1", "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", "WiderFace 1.0", "VGGFace2 1.0", "Cityscapes 1.0", - "Datumaro 1.0", "Open Images V6 1.0" + "Datumaro 1.0", "Open Images V6 1.0", "KITTI 1.0" ]: self._create_annotations(task, dump_format_name, "default") else: @@ -1012,6 +1013,7 @@ class TaskDumpUploadTest(_DbTestBase): "Open Images V6 1.0", # changed points values 'Kitti Raw Format 1.0', 'Sly Point Cloud Format 1.0', + 'KITTI 1.0', # changed points values 'Cityscapes 1.0', # changed points value 'Datumaro 3D 1.0' ]: @@ -1034,7 +1036,7 @@ class TaskDumpUploadTest(_DbTestBase): "PASCAL VOC 1.1", "Segmentation mask 1.1", "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", "WiderFace 1.0", "VGGFace2 1.0", "LFW 1.0", - "Open Images V6 1.0", "Datumaro 1.0" + "Open Images V6 1.0", "Datumaro 1.0", "KITTI 1.0" ]: self._create_annotations(task, dump_format_name, "default") else: diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py index 9655c0e9..d9786cb7 100644 --- a/cvat/apps/engine/tests/test_rest_api.py +++ b/cvat/apps/engine/tests/test_rest_api.py @@ -4822,6 +4822,10 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase): annotations["shapes"] = points_wo_attrs \ + tags_wo_attrs + elif annotation_format == "KITTI 1.0": + annotations["shapes"] = rectangle_shapes_wo_attrs \ + + polygon_shapes_wo_attrs + elif annotation_format == "Market-1501 1.0": tags_with_attrs = [{ "frame": 1, diff --git a/site/content/en/docs/manual/advanced/formats/format-coco.md b/site/content/en/docs/manual/advanced/formats/format-coco.md index 61b74004..48fb487f 100644 --- a/site/content/en/docs/manual/advanced/formats/format-coco.md +++ b/site/content/en/docs/manual/advanced/formats/format-coco.md @@ -5,11 +5,11 @@ weight: 5 # [MS COCO Object Detection](http://cocodataset.org/#format-data) -- [Format specification](https://github.com/openvinotoolkit/datumaro/blob/develop/docs/formats/coco_user_manual.md#format-specification) +- [Format specification](https://openvinotoolkit.github.io/datumaro/docs/formats/coco/) ## COCO export -Downloaded file: a zip archive with the structure described [here](https://github.com/openvinotoolkit/datumaro/blob/develop/docs/formats/coco_user_manual.md#load-coco-dataset) +Downloaded file: a zip archive with the structure described [here](https://openvinotoolkit.github.io/datumaro/docs/formats/coco/#import-coco-dataset) - supported annotations: Polygons, Rectangles - supported attributes: @@ -21,13 +21,13 @@ Downloaded file: a zip archive with the structure described [here](https://githu - `score` (number) - the annotation `score` field - arbitrary attributes - will be stored in the `attributes` annotation section -Support for COCO tasks via Datumaro is described [here](https://github.com/openvinotoolkit/datumaro/blob/develop/docs/formats/coco_user_manual.md#export-to-coco) +Support for COCO tasks via Datumaro is described [here](https://openvinotoolkit.github.io/datumaro/docs/formats/coco/#export-to-other-formats) For example, [support for COCO keypoints over Datumaro](https://github.com/openvinotoolkit/cvat/issues/2910#issuecomment-726077582): 1. Install [Datumaro](https://github.com/openvinotoolkit/datumaro) `pip install datumaro` -1. Export the task in the `Datumaro` format, unzip -1. Export the Datumaro project in `coco` / `coco_person_keypoints` formats +2. Export the task in the `Datumaro` format, unzip +3. Export the Datumaro project in `coco` / `coco_person_keypoints` formats `datum export -f coco -p path/to/project [-- --save-images]` This way, one can export CVAT points as single keypoints or @@ -36,29 +36,29 @@ keypoint lists (without the `visibility` COCO flag). ## COCO import Uploaded file: a single unpacked `*.json` or a zip archive with the structure described -[here](https://github.com/openvinotoolkit/datumaro/blob/develop/docs/formats/coco_user_manual.md#load-coco-dataset) +[here](https://openvinotoolkit.github.io/datumaro/docs/formats/coco/#import-coco-dataset) (without images). - supported annotations: Polygons, Rectangles (if the `segmentation` field is empty) ## How to create a task from MS COCO dataset -1. Download the [MS COCO dataset](https://github.com/openvinotoolkit/datumaro/blob/develop/docs/formats/coco_user_manual.md#load-COCO-dataset). +1. Download the [MS COCO dataset](https://openvinotoolkit.github.io/datumaro/docs/formats/coco/#import-coco-dataset). For example `val images` and `instances` annotations -1. Create a CVAT task with the following labels: +2. Create a CVAT task with the following labels: ```bash person bicycle car motorcycle airplane bus train truck boat "traffic light" "fire hydrant" "stop sign" "parking meter" bench bird cat dog horse sheep cow elephant bear zebra giraffe backpack umbrella handbag tie suitcase frisbee skis snowboard "sports ball" kite "baseball bat" "baseball glove" skateboard surfboard "tennis racket" bottle "wine glass" cup fork knife spoon bowl banana apple sandwich orange broccoli carrot "hot dog" pizza donut cake chair couch "potted plant" bed "dining table" toilet tv laptop mouse remote keyboard "cell phone" microwave oven toaster sink refrigerator book clock vase scissors "teddy bear" "hair drier" toothbrush ``` -1. Select `val2017.zip` as data +3. Select `val2017.zip` as data (See [Creating an annotation task](/docs/manual/basics/creating_an_annotation_task/) guide for details) -2. Unpack `annotations_trainval2017.zip` +4. Unpack `annotations_trainval2017.zip` -3. click `Upload annotation` button, +5. click `Upload annotation` button, choose `COCO 1.1` and select `instances_val2017.json` annotation file. It can take some time. diff --git a/site/content/en/docs/manual/advanced/formats/format-kitti.md b/site/content/en/docs/manual/advanced/formats/format-kitti.md new file mode 100644 index 00000000..b90f91ae --- /dev/null +++ b/site/content/en/docs/manual/advanced/formats/format-kitti.md @@ -0,0 +1,95 @@ +--- +linkTitle: 'KITTI' +weight: 17 +--- + +# [KITTI](http://www.cvlibs.net/datasets/kitti/) + +- [Format specification for KITTI detection](https://s3.eu-central-1.amazonaws.com/avg-kitti/devkit_object.zip) +- [Format specification for KITTI segmentation](https://s3.eu-central-1.amazonaws.com/avg-kitti/devkit_semantics.zip) + +- supported annotations: + + - Rectangles (detection task) + - Polygon (segmentation task) + +- supported attributes: + + - `occluded` (both UI option and a separate attribute). + Indicates that a significant portion of the object within + the bounding box is occluded by another object + - `truncated` supported only for rectangles + (should be defined for labels as `checkbox` -es). + Indicates that the bounding box specified for the object + does not correspond to the full extent of the object + - 'is_crowd' supported only for polygons + (should be defined for labels as `checkbox` -es). + Indicates that the annotation covers multiple instances of the same class + +## KITTI annotations export + +Downloaded file: a zip archive of the following structure: + +``` +└─ annotations.zip/ + ├── label_colors.txt # list of pairs r g b label_name + ├── labels.txt # list of labels + └── default/ + ├── label_2/ # left color camera label files + │ ├── .txt + │ ├── .txt + │ └── ... + ├── instance/ # instance segmentation masks + │ ├── .png + │ ├── .png + │ └── ... + ├── semantic/ # semantic segmentation masks (labels are encoded by its id) + │ ├── .png + │ ├── .png + │ └── ... + └── semantic_rgb/ # semantic segmentation masks (labels are encoded by its color) + ├── .png + ├── .png + └── ... +``` + +## KITTI annotations import + +You can upload KITTI annotations in two ways: +rectangles for the detection task and +masks for the segmentation task. + +For detection tasks the uploading archive should have the following structure: + +``` +└─ annotations.zip/ + ├── labels.txt # optional, labels list for non-original detection labels + └── / + ├── label_2/ # left color camera label files + │ ├── .txt + │ ├── .txt + │ └── ... +``` + +For segmentation tasks the uploading archive should have the following structure: + +``` +└─ annotations.zip/ + ├── label_colors.txt # optional, color map for non-original segmentation labels + └── / + ├── instance/ # instance segmentation masks + │ ├── .png + │ ├── .png + │ └── ... + ├── semantic/ # optional, semantic segmentation masks (labels are encoded by its id) + │ ├── .png + │ ├── .png + │ └── ... + └── semantic_rgb/ # optional, semantic segmentation masks (labels are encoded by its color) + ├── .png + ├── .png + └── ... +``` + +All annotation files and masks should have structures +that are described in the original format specification.