diff --git a/CHANGELOG.md b/CHANGELOG.md index ba3923cbdec1..d7814ded8cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [VGGFace2](https://github.com/ox-vgg/vgg_face2) format support () - [Backup/Restore guide](cvat/apps/documentation/backup_guide.md) () - Label deletion from tasks and projects () +- [Market-1501](https://www.aitribune.com/dataset/2018051063) format support () ### Changed diff --git a/README.md b/README.md index ca8b1739d6b5..0c62b9facbc9 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ For more information about supported formats look at the | [CamVid](http://mi.eng.cam.ac.uk/research/projects/VideoRec/CamVid/) | X | X | | [WIDER Face](http://shuoyang1213.me/WIDERFACE/) | X | X | | [VGGFace2](https://github.com/ox-vgg/vgg_face2) | X | X | +| [Market-1501](https://www.aitribune.com/dataset/2018051063) | X | X | ## Deep learning serverless functions for automatic labeling diff --git a/cvat/apps/dataset_manager/formats/README.md b/cvat/apps/dataset_manager/formats/README.md index a89cf6e1fb5f..318b4cfe87e8 100644 --- a/cvat/apps/dataset_manager/formats/README.md +++ b/cvat/apps/dataset_manager/formats/README.md @@ -22,6 +22,7 @@ - [CamVid](#camvid) - [WIDER Face](#widerface) - [VGGFace2](#vggface2) + - [Market-1501](#market1501) ## How to add a new annotation format support @@ -937,3 +938,39 @@ label1 Uploaded file: a zip archive of the structure above - supported annotations: Rectangles, Points (landmarks - groups of 5 points) + +### [Market-1501](https://www.aitribune.com/dataset/2018051063) + +#### Market-1501 Dumper + +Downloaded file: a zip archive of the following structure: + +```bash +taskname.zip/ +├── bounding_box_/ +│ └── image_name_1.jpg +└── query + ├── image_name_2.jpg + └── image_name_3.jpg +# if we keep only annotation: +taskname.zip/ +└── images_.txt +# images_.txt +query/image_name_1.jpg +bounding_box_/image_name_2.jpg +bounding_box_/image_name_3.jpg +# image_name = 0001_c1s1_000015_00.jpg +0001 - person id +c1 - camera id (there are totally 6 cameras) +s1 - sequence +000015 - frame number in sequence +00 - means that this bounding box is the first one among the several +``` + +- supported annotations: Label `market-1501` with atrributes (`query`, `person_id`, `camera_id`) + +#### Market-1501 Loader + +Uploaded file: a zip archive of the structure above + +- supported annotations: Label `market-1501` with atrributes (`query`, `person_id`, `camera_id`) diff --git a/cvat/apps/dataset_manager/formats/market1501.py b/cvat/apps/dataset_manager/formats/market1501.py new file mode 100644 index 000000000000..1b6e24f43f1a --- /dev/null +++ b/cvat/apps/dataset_manager/formats/market1501.py @@ -0,0 +1,77 @@ +# Copyright (C) 2021 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import zipfile +from tempfile import TemporaryDirectory + +from datumaro.components.dataset import Dataset +from datumaro.components.extractor import (AnnotationType, Label, + LabelCategories, Transform) + +from cvat.apps.dataset_manager.bindings import (CvatTaskDataExtractor, + import_dm_annotations) +from cvat.apps.dataset_manager.util import make_zip_archive + +from .registry import dm_env, exporter, importer + +class AttrToLabelAttr(Transform): + def __init__(self, extractor, label): + super().__init__(extractor) + + assert isinstance(label, str) + self._categories = {} + label_cat = self._extractor.categories().get(AnnotationType.label) + if not label_cat: + label_cat = LabelCategories() + self._label = label_cat.add(label) + self._categories[AnnotationType.label] = label_cat + + def categories(self): + return self._categories + + def transform_item(self, item): + annotations = item.annotations + if item.attributes: + annotations.append(Label(self._label, attributes=item.attributes)) + item.attributes = {} + return item.wrap(annotations=annotations) + +class LabelAttrToAttr(Transform): + def __init__(self, extractor, label): + super().__init__(extractor) + + assert isinstance(label, str) + label_cat = self._extractor.categories().get(AnnotationType.label) + self._label = label_cat.find(label)[0] + + def transform_item(self, item): + annotations = item.annotations + attributes = item.attributes + if self._label != None: + labels = [ann for ann in annotations + if ann.type == AnnotationType.label \ + and ann.label == self._label] + if len(labels) == 1: + attributes.update(labels[0].attributes) + annotations.remove(labels[0]) + return item.wrap(annotations=annotations, attributes=attributes) + + +@exporter(name='Market-1501', ext='ZIP', version='1.0') +def _export(dst_file, task_data, save_images=False): + dataset = Dataset.from_extractors(CvatTaskDataExtractor( + task_data, include_images=save_images), env=dm_env) + with TemporaryDirectory() as temp_dir: + dataset.transform(LabelAttrToAttr, 'market-1501') + dataset.export(temp_dir, 'market1501', save_images=save_images) + make_zip_archive(temp_dir, dst_file) + +@importer(name='Market-1501', ext='ZIP', version='1.0') +def _import(src_file, task_data): + with TemporaryDirectory() as tmp_dir: + zipfile.ZipFile(src_file).extractall(tmp_dir) + + dataset = Dataset.import_from(tmp_dir, 'market1501', env=dm_env) + dataset.transform(AttrToLabelAttr, 'market-1501') + import_dm_annotations(dataset, task_data) diff --git a/cvat/apps/dataset_manager/formats/registry.py b/cvat/apps/dataset_manager/formats/registry.py index c14a732d3c5c..f46a988ff430 100644 --- a/cvat/apps/dataset_manager/formats/registry.py +++ b/cvat/apps/dataset_manager/formats/registry.py @@ -97,3 +97,4 @@ def make_exporter(name): import cvat.apps.dataset_manager.formats.camvid import cvat.apps.dataset_manager.formats.widerface import cvat.apps.dataset_manager.formats.vggface2 +import cvat.apps.dataset_manager.formats.market1501 diff --git a/cvat/apps/dataset_manager/tests/test_formats.py b/cvat/apps/dataset_manager/tests/test_formats.py index 75ff51d01b41..faee5d182910 100644 --- a/cvat/apps/dataset_manager/tests/test_formats.py +++ b/cvat/apps/dataset_manager/tests/test_formats.py @@ -284,6 +284,7 @@ def test_export_formats_query(self): 'CamVid 1.0', 'WiderFace 1.0', 'VGGFace2 1.0', + 'Market-1501 1.0', }) def test_import_formats_query(self): @@ -304,6 +305,7 @@ def test_import_formats_query(self): 'CamVid 1.0', 'WiderFace 1.0', 'VGGFace2 1.0', + 'Market-1501 1.0', }) def test_exports(self): @@ -346,6 +348,7 @@ def test_empty_images_are_exported(self): ('CamVid 1.0', 'camvid'), ('WiderFace 1.0', 'wider_face'), ('VGGFace2 1.0', 'vgg_face2'), + ('Market-1501 1.0', 'market1501'), ]: with self.subTest(format=format_name): if not dm.formats.registry.EXPORT_FORMATS[format_name].ENABLED: diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py index f999f64997b7..f39bf95acf75 100644 --- a/cvat/apps/engine/tests/test_rest_api.py +++ b/cvat/apps/engine/tests/test_rest_api.py @@ -2778,7 +2778,7 @@ def setUp(self): def setUpTestData(cls): create_db_users(cls) - def _create_task(self, owner, assignee): + def _create_task(self, owner, assignee, annotation_format=""): data = { "name": "my task #1", "owner_id": owner.id, @@ -2833,6 +2833,30 @@ def _create_task(self, owner, assignee): }, ] } + if annotation_format == "Market-1501 1.0": + data["labels"] = [{ + "name": "market-1501", + "attributes": [ + { + "name": "query", + "mutable": False, + "input_type": "select", + "values": ["True", "False"] + }, + { + "name": "camera_id", + "mutable": False, + "input_type": "number", + "values": ["0", "1", "2", "3", "4", "5"] + }, + { + "name": "person_id", + "mutable": False, + "input_type": "number", + "values": ["1", "2", "3"] + }, + ] + }] with ForceLogin(owner, self.client): response = self.client.post('/api/v1/tasks', data=data, format="json") @@ -3812,147 +3836,148 @@ def _run_api_v1_tasks_id_annotations_dump_load(self, owner, assignee, annotator) HTTP_201_CREATED = status.HTTP_401_UNAUTHORIZED def _get_initial_annotation(annotation_format): - rectangle_tracks_with_attrs = [{ - "frame": 0, - "label_id": task["labels"][0]["id"], - "group": 0, - "source": "manual", - "attributes": [ - { - "spec_id": task["labels"][0]["attributes"][0]["id"], - "value": task["labels"][0]["attributes"][0]["values"][0] - }, - ], - "shapes": [ - { - "frame": 0, - "points": [1.0, 2.1, 50.1, 30.22], - "type": "rectangle", - "occluded": False, - "outside": False, - "attributes": [ - { - "spec_id": task["labels"][0]["attributes"][1]["id"], - "value": task["labels"][0]["attributes"][1]["default_value"] - } - ] - }, - { - "frame": 1, - "points": [2.0, 2.1, 77.2, 36.22], - "type": "rectangle", - "occluded": True, - "outside": False, - "attributes": [ - { - "spec_id": task["labels"][0]["attributes"][1]["id"], - "value": task["labels"][0]["attributes"][1]["default_value"] - } - ] - }, - { - "frame": 2, - "points": [2.0, 2.1, 77.2, 36.22], - "type": "rectangle", - "occluded": True, - "outside": True, - "attributes": [ - { - "spec_id": task["labels"][0]["attributes"][1]["id"], - "value": task["labels"][0]["attributes"][1]["default_value"] - } - ] - }, - ] - }] - rectangle_tracks_wo_attrs = [{ - "frame": 0, - "label_id": task["labels"][1]["id"], - "group": 0, - "source": "manual", - "attributes": [], - "shapes": [ - { - "frame": 0, - "attributes": [], - "points": [1.0, 2.1, 50.2, 36.6], - "type": "rectangle", - "occluded": False, - "outside": False, - }, - { - "frame": 1, - "attributes": [], - "points": [1.0, 2.1, 51, 36.6], - "type": "rectangle", - "occluded": False, - "outside": False - }, - { - "frame": 2, - "attributes": [], - "points": [1.0, 2.1, 51, 36.6], - "type": "rectangle", - "occluded": False, - "outside": True, - } - ] - }] - polygon_tracks_wo_attrs = [{ - "frame": 0, - "label_id": task["labels"][1]["id"], - "group": 0, - "source": "manual", - "attributes": [], - "shapes": [ - { - "frame": 0, - "attributes": [], - "points": [1.0, 2.1, 50.2, 36.6, 7.0, 10.0], - "type": "polygon", - "occluded": False, - "outside": False, - }, - { - "frame": 1, - "attributes": [], - "points": [1.0, 2.1, 51, 36.6, 8.0, 11.0], - "type": "polygon", - "occluded": False, - "outside": False - }, - { - "frame": 2, - "attributes": [], - "points": [1.0, 2.1, 51, 36.6, 14.0, 15.0], - "type": "polygon", - "occluded": False, - "outside": True, - } - ] - }] + if annotation_format != "Market-1501 1.0": + rectangle_tracks_with_attrs = [{ + "frame": 0, + "label_id": task["labels"][0]["id"], + "group": 0, + "source": "manual", + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][0]["id"], + "value": task["labels"][0]["attributes"][0]["values"][0] + }, + ], + "shapes": [ + { + "frame": 0, + "points": [1.0, 2.1, 50.1, 30.22], + "type": "rectangle", + "occluded": False, + "outside": False, + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ] + }, + { + "frame": 1, + "points": [2.0, 2.1, 77.2, 36.22], + "type": "rectangle", + "occluded": True, + "outside": False, + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ] + }, + { + "frame": 2, + "points": [2.0, 2.1, 77.2, 36.22], + "type": "rectangle", + "occluded": True, + "outside": True, + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ] + }, + ] + }] + rectangle_tracks_wo_attrs = [{ + "frame": 0, + "label_id": task["labels"][1]["id"], + "group": 0, + "source": "manual", + "attributes": [], + "shapes": [ + { + "frame": 0, + "attributes": [], + "points": [1.0, 2.1, 50.2, 36.6], + "type": "rectangle", + "occluded": False, + "outside": False, + }, + { + "frame": 1, + "attributes": [], + "points": [1.0, 2.1, 51, 36.6], + "type": "rectangle", + "occluded": False, + "outside": False + }, + { + "frame": 2, + "attributes": [], + "points": [1.0, 2.1, 51, 36.6], + "type": "rectangle", + "occluded": False, + "outside": True, + } + ] + }] + polygon_tracks_wo_attrs = [{ + "frame": 0, + "label_id": task["labels"][1]["id"], + "group": 0, + "source": "manual", + "attributes": [], + "shapes": [ + { + "frame": 0, + "attributes": [], + "points": [1.0, 2.1, 50.2, 36.6, 7.0, 10.0], + "type": "polygon", + "occluded": False, + "outside": False, + }, + { + "frame": 1, + "attributes": [], + "points": [1.0, 2.1, 51, 36.6, 8.0, 11.0], + "type": "polygon", + "occluded": False, + "outside": False + }, + { + "frame": 2, + "attributes": [], + "points": [1.0, 2.1, 51, 36.6, 14.0, 15.0], + "type": "polygon", + "occluded": False, + "outside": True, + } + ] + }] - rectangle_shapes_with_attrs = [{ - "frame": 0, - "label_id": task["labels"][0]["id"], - "group": 0, - "source": "manual", - "attributes": [ - { - "spec_id": task["labels"][0]["attributes"][0]["id"], - "value": task["labels"][0]["attributes"][0]["values"][0] - }, - { - "spec_id": task["labels"][0]["attributes"][1]["id"], - "value": task["labels"][0]["attributes"][1]["default_value"] - } - ], - "points": [1.0, 2.1, 10.6, 53.22], - "type": "rectangle", - "occluded": False, - }] + rectangle_shapes_with_attrs = [{ + "frame": 0, + "label_id": task["labels"][0]["id"], + "group": 0, + "source": "manual", + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][0]["id"], + "value": task["labels"][0]["attributes"][0]["values"][0] + }, + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ], + "points": [1.0, 2.1, 10.6, 53.22], + "type": "rectangle", + "occluded": False, + }] - rectangle_shapes_with_wider_attrs = [{ + rectangle_shapes_with_wider_attrs = [{ "frame": 0, "label_id": task["labels"][2]["id"], "group": 0, @@ -3976,59 +4001,59 @@ def _get_initial_annotation(annotation_format): "occluded": False, }] - rectangle_shapes_wo_attrs = [{ - "frame": 1, - "label_id": task["labels"][1]["id"], - "group": 0, - "source": "manual", - "attributes": [], - "points": [2.0, 2.1, 40, 50.7], - "type": "rectangle", - "occluded": False, - }] + rectangle_shapes_wo_attrs = [{ + "frame": 1, + "label_id": task["labels"][1]["id"], + "group": 0, + "source": "manual", + "attributes": [], + "points": [2.0, 2.1, 40, 50.7], + "type": "rectangle", + "occluded": False, + }] - polygon_shapes_wo_attrs = [{ - "frame": 1, - "label_id": task["labels"][1]["id"], - "group": 0, - "source": "manual", - "attributes": [], - "points": [2.0, 2.1, 100, 30.22, 40, 77, 1, 3], - "type": "polygon", - "occluded": False, - }] + polygon_shapes_wo_attrs = [{ + "frame": 1, + "label_id": task["labels"][1]["id"], + "group": 0, + "source": "manual", + "attributes": [], + "points": [2.0, 2.1, 100, 30.22, 40, 77, 1, 3], + "type": "polygon", + "occluded": False, + }] - polygon_shapes_with_attrs = [{ - "frame": 2, - "label_id": task["labels"][0]["id"], - "group": 1, - "source": "manual", - "attributes": [ - { - "spec_id": task["labels"][0]["attributes"][0]["id"], - "value": task["labels"][0]["attributes"][0]["values"][1] - }, - { - "spec_id": task["labels"][0]["attributes"][1]["id"], - "value": task["labels"][0]["attributes"][1]["default_value"] - } - ], - "points": [20.0, 0.1, 10, 3.22, 4, 7, 10, 30, 1, 2, 4.44, 5.55], - "type": "polygon", - "occluded": True, - }, - { - "frame": 2, - "label_id": task["labels"][1]["id"], - "group": 1, - "source": "manual", - "attributes": [], - "points": [4, 7, 10, 30, 4, 5.55], - "type": "polygon", - "occluded": False, - }] + polygon_shapes_with_attrs = [{ + "frame": 2, + "label_id": task["labels"][0]["id"], + "group": 1, + "source": "manual", + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][0]["id"], + "value": task["labels"][0]["attributes"][0]["values"][1] + }, + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ], + "points": [20.0, 0.1, 10, 3.22, 4, 7, 10, 30, 1, 2, 4.44, 5.55], + "type": "polygon", + "occluded": True, + }, + { + "frame": 2, + "label_id": task["labels"][1]["id"], + "group": 1, + "source": "manual", + "attributes": [], + "points": [4, 7, 10, 30, 4, 5.55], + "type": "polygon", + "occluded": False, + }] - points_wo_attrs = [{ + points_wo_attrs = [{ "frame": 1, "label_id": task["labels"][1]["id"], "group": 0, @@ -4039,36 +4064,36 @@ def _get_initial_annotation(annotation_format): "occluded": False, }] - tags_wo_attrs = [{ - "frame": 2, - "label_id": task["labels"][1]["id"], - "group": 0, - "source": "manual", - "attributes": [], - }] - tags_with_attrs = [{ - "frame": 1, - "label_id": task["labels"][0]["id"], - "group": 3, - "source": "manual", - "attributes": [ - { - "spec_id": task["labels"][0]["attributes"][0]["id"], - "value": task["labels"][0]["attributes"][0]["values"][1] - }, - { - "spec_id": task["labels"][0]["attributes"][1]["id"], - "value": task["labels"][0]["attributes"][1]["default_value"] - } - ], - }] + tags_wo_attrs = [{ + "frame": 2, + "label_id": task["labels"][1]["id"], + "group": 0, + "source": "manual", + "attributes": [], + }] + tags_with_attrs = [{ + "frame": 1, + "label_id": task["labels"][0]["id"], + "group": 3, + "source": "manual", + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][0]["id"], + "value": task["labels"][0]["attributes"][0]["values"][1] + }, + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["default_value"] + } + ], + }] annotations = { - "version": 0, - "tags": [], - "shapes": [], - "tracks": [], - } + "version": 0, + "tags": [], + "shapes": [], + "tracks": [], + } if annotation_format == "CVAT for video 1.1": annotations["tracks"] = rectangle_tracks_with_attrs \ + rectangle_tracks_wo_attrs \ @@ -4133,6 +4158,29 @@ def _get_initial_annotation(annotation_format): annotations["shapes"] = points_wo_attrs \ + rectangle_shapes_wo_attrs + elif annotation_format == "Market-1501 1.0": + tags_with_attrs = [{ + "frame": 1, + "label_id": task["labels"][0]["id"], + "group": 0, + "source": "manual", + "attributes": [ + { + "spec_id": task["labels"][0]["attributes"][0]["id"], + "value": task["labels"][0]["attributes"][0]["values"][1] + }, + { + "spec_id": task["labels"][0]["attributes"][1]["id"], + "value": task["labels"][0]["attributes"][1]["values"][2] + }, + { + "spec_id": task["labels"][0]["attributes"][2]["id"], + "value": task["labels"][0]["attributes"][2]["values"][0] + } + ], + }] + annotations["tags"] = tags_with_attrs + else: raise Exception("Unknown format {}".format(annotation_format)) @@ -4169,7 +4217,7 @@ def _get_initial_annotation(annotation_format): with self.subTest(export_format=export_format, import_format=import_format): # 1. create task - task, jobs = self._create_task(owner, assignee) + task, jobs = self._create_task(owner, assignee, import_format) # 2. add annotation data = _get_initial_annotation(export_format)