diff --git a/cvat/apps/dataset_manager/tests/assets/annotations.json b/cvat/apps/dataset_manager/tests/assets/annotations.json new file mode 100644 index 000000000000..3f1f3dec8c7c --- /dev/null +++ b/cvat/apps/dataset_manager/tests/assets/annotations.json @@ -0,0 +1,860 @@ +{ + "CVAT for images 1.1": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [5.54, 3.5, 19.64, 11.19], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": true, + "z_order": 0, + "points": [25.04, 13.7, 35.85, 20.2, 16.65, 19.8], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polyline", + "occluded": false, + "z_order": 1, + "points": [27.15, 26.7, 53.25, 24.8], + "frame": 0, + "label_id": null, + "group": 2, + "source": "manual", + "attributes": [] + }, + { + "type": "points", + "occluded": false, + "z_order": 1, + "points": [42.95, 33.59], + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "cuboid", + "occluded": false, + "z_order": 2, + "points": [ + 51.65, + 37.3, + 51.65, + 46.8, + 70.25, + 37.2, + 70.25, + 46.8, + 72.11, + 36.34, + 72.11, + 45.74, + 53.51, + 36.34, + 53.51, + 45.74 + ], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "CVAT for video 1.1": { + "version": 0, + "tags": [ + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [], + "tracks": [ + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [4.75, 4.8, 13.06, 11.39], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polyline", + "occluded": false, + "z_order": 1, + "points": [30.99, 31.37, 47.51, 26.94, 50.21, 22.73], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "points", + "occluded": false, + "z_order": 2, + "points": [59.82, 31.26], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 2, + "source": "manual", + "shapes": [ + { + "type": "cuboid", + "occluded": false, + "z_order": 2, + "points": [ + 52.48, + 37.95, + 52.48, + 44.43, + 67.38, + 37.85, + 67.38, + 44.43, + 68.87, + 37.29, + 68.87, + 43.67, + 53.97, + 37.29, + 53.97, + 43.67 + ], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [65.65, 7.07, 78.83, 16.14], + "frame": 0, + "outside": false, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [65.65, 7.07, 78.83, 16.14], + "frame": 1, + "outside": true, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [65.65, 7.073, 78.83, 16.14], + "frame": 2, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CamVid 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [83.0, 228.5, 69.5, 228.0, 69.0, 183.5, 164.5, 223.0, 164.0, 224.5, 83.0, 228.5], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "COCO 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [7.29, 8.58, 18.45, 19.22], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [27.03, 26.07, 63.94, 37.87, 18.34, 34.97], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "ImageNet 1.0": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [], + "tracks": [] + }, + "LabelMe 3.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [10.0, 8.79, 20.5, 15.69], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [35.0, 22.5, 53.32, 30.63, 22.34, 29.45], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "MOT 1.1": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.4, 12.09, 17.2, 18.19], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [10.5, 27.29, 23.3, 33.49], + "frame": 0, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "MOTS PNG 1.0": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [19.4, 19.2, 41.7, 22.0, 38.8, 29.5, 21.5, 29.3], + "frame": 0, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "PASCAL VOC 1.1": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [9.4, 12.09, 17.2, 18.19], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "Segmentation mask 1.1": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [11.9, 21.5, 35.2, 21.9, 33.6, 31.9, 12.4, 30.47], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "TFRecord 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [16.5, 17.2, 38.89, 25.63], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "YOLO 1.1": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [8.3, 9.1, 19.2, 14.8], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "WiderFace 1.0": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [7.55, 9.75, 16.44, 15.85], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [3.55, 27.75, 11.33, 33.71], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "VGGFace2 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "points", + "occluded": false, + "z_order": 0, + "points": [28.05, 18.0, 36.65, 17.7, 36.85, 23.7, 26.95, 23.2, 30.35, 28.9], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [57.15, 20.9, 74.25, 32.0], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "Datumaro 1.0": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [5.54, 3.5, 19.64, 11.19], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": true, + "z_order": 0, + "points": [25.04, 13.7, 35.85, 20.2, 16.65, 19.8], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polyline", + "occluded": false, + "z_order": 1, + "points": [27.15, 26.7, 53.25, 24.8], + "frame": 0, + "label_id": null, + "group": 2, + "source": "manual", + "attributes": [] + }, + { + "type": "points", + "occluded": false, + "z_order": 1, + "points": [42.95, 33.59], + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "cuboid", + "occluded": false, + "z_order": 2, + "points": [ + 51.65, + 37.3, + 51.65, + 46.8, + 70.25, + 37.2, + 70.25, + 46.8, + 72.11, + 36.34, + 72.11, + 45.74, + 53.51, + 36.34, + 53.51, + 45.74 + ], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "CVAT for images 1.1 many jobs": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "frame": 8, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [5.54, 3.5, 19.64, 11.19], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": true, + "z_order": 0, + "points": [25.04, 13.7, 35.85, 20.2, 16.65, 19.8], + "frame": 11, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "CVAT for video 1.1 many jobs": { + "version": 0, + "tags": [ + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "frame": 8, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [4.75, 4.8, 13.06, 11.39], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 10, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for video 1.1 slice track": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [66.45, 147.08, 182.16, 204.56], + "frame": 0, + "outside": true, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [66.45, 147.08, 182.16, 204.56], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for images 1.1 merge": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.95, 8.09, 18.65, 13.39], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [11.545, 11.7, 19.44, 17.4], + "frame": 3, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [4.54, 19.59, 21.34, 26.89], + "frame": 0, + "outside": false, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [4.54, 19.59, 21.34, 26.89], + "frame": 6, + "outside": true, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 3, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.65, 23.59, 22.65, 29.79], + "frame": 3, + "outside": false, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.65, 23.59, 22.65, 29.79], + "frame": 9, + "outside": true, + "attributes": [] + } + ], + "attributes": [] + } + ] + } +} diff --git a/cvat/apps/dataset_manager/tests/assets/tasks.json b/cvat/apps/dataset_manager/tests/assets/tasks.json new file mode 100644 index 000000000000..f1203a14a614 --- /dev/null +++ b/cvat/apps/dataset_manager/tests/assets/tasks.json @@ -0,0 +1,96 @@ +{ + "main": { + "name": "main task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [ + { + "name": "select_name", + "mutable": false, + "input_type": "select", + "default_value": "bmw", + "values": ["bmw", "mazda", "renault"] + }, + { + "name": "radio_name", + "mutable": false, + "input_type": "radio", + "default_value": "x1", + "values": ["x1", "x2", "x3"] + }, + { + "name": "check_name", + "mutable": true, + "input_type": "checkbox", + "default_value": "false", + "values": ["false"] + }, + { + "name": "text_name", + "mutable": false, + "input_type": "text", + "default_value": "qwerty", + "values": ["qwerty"] + }, + { + "name": "number_name", + "mutable": false, + "input_type": "number", + "default_value": "-4", + "values": ["-4", "4", "1"] + } + ] + }, + { + "name": "person", + "color": "#c06060", + "attributes": [] + } + ] + }, + "no attributes": { + "name": "no attributes", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + }, + "many jobs": { + "name": "many jobs", + "overlap": 0, + "segment_size": 5, + "owner_id": 1, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + }, + "change ovelap and segment size": { + "name": "change ovelap and segment size", + "overlap": 3, + "segment_size": 6, + "owner_id": 1, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + } +} diff --git a/cvat/apps/dataset_manager/tests/test_rest_api_formats.py b/cvat/apps/dataset_manager/tests/test_rest_api_formats.py new file mode 100644 index 000000000000..23a0094ce778 --- /dev/null +++ b/cvat/apps/dataset_manager/tests/test_rest_api_formats.py @@ -0,0 +1,778 @@ + +# Copyright (C) 2021 Intel Corporation +# +# SPDX-License-Identifier: MIT + + +import copy +import json +import os.path as osp +import random +import xml.etree.ElementTree as ET +import zipfile +from io import BytesIO + +from datumaro.components.dataset import Dataset +from datumaro.util.test_utils import compare_datasets, TestDir +from django.contrib.auth.models import Group, User +from PIL import Image +from rest_framework import status +from rest_framework.test import APIClient, APITestCase + +import cvat.apps.dataset_manager as dm +from cvat.apps.dataset_manager.bindings import CvatTaskDataExtractor, TaskData +from cvat.apps.dataset_manager.task import TaskAnnotation +from cvat.apps.engine.models import Task + +path = osp.join(osp.dirname(__file__), 'assets', 'tasks.json') +with open(path) as f: + tasks = json.load(f) + +path = osp.join(osp.dirname(__file__), 'assets', 'annotations.json') +with open(path) as f: + annotations = json.load(f) + +def generate_image_file(filename, size=(100, 50)): + f = BytesIO() + image = Image.new('RGB', size=size) + image.save(f, 'jpeg') + f.name = filename + f.seek(0) + return f + +class ForceLogin: + def __init__(self, user, client): + self.user = user + self.client = client + + def __enter__(self): + if self.user: + self.client.force_login(self.user, + backend='django.contrib.auth.backends.ModelBackend') + + return self + + def __exit__(self, exception_type, exception_value, traceback): + if self.user: + self.client.logout() + +class _DbTestBase(APITestCase): + def setUp(self): + self.client = APIClient() + + @classmethod + def setUpTestData(cls): + cls.create_db_users() + + @classmethod + def create_db_users(cls): + (group_admin, _) = Group.objects.get_or_create(name="admin") + (group_user, _) = Group.objects.get_or_create(name="user") + + user_admin = User.objects.create_superuser(username="admin", email="", + password="admin") + user_admin.groups.add(group_admin) + user_dummy = User.objects.create_user(username="user", password="user") + user_dummy.groups.add(group_user) + + cls.admin = user_admin + cls.user = user_dummy + + def _put_api_v1_task_id_annotations(self, tid, data): + with ForceLogin(self.admin, self.client): + response = self.client.put("/api/v1/tasks/%s/annotations" % tid, + data=data, format="json") + + return response + + def _generate_task_images(self, count): # pylint: disable=no-self-use + images = { + "client_files[%d]" % i: generate_image_file("image_%d.jpg" % i) + for i in range(count) + } + images["image_quality"] = 75 + return images + + def _create_task(self, data, image_data): + with ForceLogin(self.admin, self.client): + response = self.client.post('/api/v1/tasks', data=data, format="json") + assert response.status_code == status.HTTP_201_CREATED, response.status_code + tid = response.data["id"] + + response = self.client.post("/api/v1/tasks/%s/data" % tid, + data=image_data) + assert response.status_code == status.HTTP_202_ACCEPTED, response.status_code + + response = self.client.get("/api/v1/tasks/%s" % tid) + task = response.data + + return task + + def _get_request(self, path, user): + with ForceLogin(user, self.client): + response = self.client.get(path) + return response + + def _get_request_with_data(self, path, data, user): + with ForceLogin(user, self.client): + response = self.client.get(path, data) + return response + + def _delete_request(self, path, user): + with ForceLogin(user, self.client): + response = self.client.delete(path) + return response + + def _create_annotations(self, task, name_ann, key_get_values): + tmp_annotations = copy.deepcopy(annotations[name_ann]) + + # change attributes in all annotations + for item in tmp_annotations: + if item in ["tags", "shapes", "tracks"]: + for index_elem, _ in enumerate(tmp_annotations[item]): + tmp_annotations[item][index_elem]["label_id"] = task["labels"][0]["id"] + + for index_attribute, attribute in enumerate(task["labels"][0]["attributes"]): + spec_id = task["labels"][0]["attributes"][index_attribute]["id"] + + if key_get_values == "random": + if attribute["input_type"] == "number": + start = int(attribute["values"][0]) + stop = int(attribute["values"][1]) + 1 + step = int(attribute["values"][2]) + value = str(random.randrange(start, stop, step)) + else: + value = random.choice(task["labels"][0]["attributes"][index_attribute]["values"]) + elif key_get_values == "dafault": + value = attribute["default_value"] + + if item == "tracks" and attribute["mutable"]: + for index_shape, _ in enumerate(tmp_annotations[item][index_elem]["shapes"]): + tmp_annotations[item][index_elem]["shapes"][index_shape]["attributes"].append({ + "spec_id": spec_id, + "value": value, + }) + else: + tmp_annotations[item][index_elem]["attributes"].append({ + "spec_id": spec_id, + "value": value, + }) + + response = self._put_api_v1_task_id_annotations(task["id"], tmp_annotations) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def _download_file(self, url, data, user, file_name): + for _ in range(5): + response = self._get_request_with_data(url, data, user) + if response.status_code == 200: + content = BytesIO(b"".join(response.streaming_content)) + with open(file_name, "wb") as f: + f.write(content.getvalue()) + break + return response + + def _upload_file(self, url, binary_file, user): + with ForceLogin(user, self.client): + for _ in range(5): + response = self.client.put(url, {"annotation_file": binary_file}) + if response.status_code == 202: + break + return response + + def _check_downloaded_file(self, file_name): + if not osp.exists(file_name): + raise FileNotFoundError(f"File '{file_name}' was not downloaded") + + def _generate_url_dump_tasks_annotations(self, task_id): + return f"/api/v1/tasks/{task_id}/annotations" + + def _generate_url_upload_tasks_annotations(self, task_id, upload_format_name): + return f"/api/v1/tasks/{task_id}/annotations?format={upload_format_name}" + + def _remove_annotations(self, url, user): + response = self._delete_request(url, user) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + return response + +class TaskDumpUploadTest(_DbTestBase): + def test_api_v1_tasks_annotations_dump_and_upload_with_datumaro(self): + test_name = self._testMethodName + # get formats + dump_formats = dm.views.get_export_formats() + + for dump_format in dump_formats: + if dump_format.ENABLED: + dump_format_name = dump_format.DISPLAY_NAME + + with self.subTest(): + # TODO skip failed formats + if dump_format_name in [ + "CVAT for video 1.1", # issues #2923 and #2924 + "MOT 1.1", # issue #2925 + "Datumaro 1.0", # not uploaded + "WiderFace 1.0", # issue #2944 + "CamVid 1.0", # issue #2840 and changed points values + "MOTS PNG 1.0", # issue #2925 and changed points values + "Segmentation mask 1.1", # changed points values + ]: + self.skipTest("Format is fail") + + for include_images in (False, True): + # create task + images = self._generate_task_images(3) + task = self._create_task(tasks["main"], images) + + # create annotations + if dump_format_name in [ + "MOT 1.1", "MOTS PNG 1.0", \ + "PASCAL VOC 1.1", "Segmentation mask 1.1", \ + "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ + "WiderFace 1.0", "VGGFace2 1.0", \ + ]: + self._create_annotations(task, dump_format_name, "dafault") + else: + self._create_annotations(task, dump_format_name, "random") + + task_id = task["id"] + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_data = TaskData(task_ann.ir_data, Task.objects.get(pk=task_id)) + extractor = CvatTaskDataExtractor(task_data, include_images=include_images) + data_from_task_before_upload = Dataset.from_extractors(extractor) + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + data = { + "format": dump_format_name, + "action": "download", + } + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # remove annotations + self._remove_annotations(url, self.admin) + + # upload annotations + if dump_format_name in ["CVAT for images 1.1", "CVAT for video 1.1"]: + upload_format_name = "CVAT 1.1" + else: + upload_format_name = dump_format_name + + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name, 'rb') as binary_file: + response = self._upload_file(url, binary_file, self.admin) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + + # equals annotations + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_data = TaskData(task_ann.ir_data, Task.objects.get(pk=task_id)) + extractor = CvatTaskDataExtractor(task_data, include_images=include_images) + data_from_task_after_upload = Dataset.from_extractors(extractor) + compare_datasets(self, data_from_task_before_upload, data_from_task_after_upload) + + def test_api_v1_tasks_annotations_update_wrong_label(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + upload_format_name = "CVAT 1.1" + + # create task with annotations + images = self._generate_task_images(3) + task = self._create_task(tasks["main"], images) + self._create_annotations(task, dump_format_name, "dafault") + task_id = task["id"] + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + data = { + "format": dump_format_name, + "action": "download", + } + with TestDir() as test_dir: + file_zip_name_before_change = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + file_zip_name_after_change = osp.join(test_dir, f'{test_name}_{dump_format_name}_wrong_label.zip') + + # download zip file + self._download_file(url, data, self.admin, file_zip_name_before_change) + self._check_downloaded_file(file_zip_name_before_change) + + # remove annotations + self._remove_annotations(url, self.admin) + + # extract zip + folder_name = osp.join(test_dir, f'{test_name}_{dump_format_name}') + with zipfile.ZipFile(file_zip_name_before_change, 'r') as zip_ref: + zip_ref.extractall(folder_name) + + # change right label to wrong + wrong_label = "wrong_label" + tree = ET.parse(osp.join(folder_name, 'annotations.xml')) + root = tree.getroot() + element = root.find("./image[@id='0']/box[@label='car']") + element.attrib["label"] = wrong_label + tree.write(osp.join(folder_name, 'annotations.xml')) + with zipfile.ZipFile(file_zip_name_after_change, 'w') as zip_ref: + zip_ref.write(osp.join(folder_name, 'annotations.xml'), 'annotations.xml') + + # upload annotations + url_upload = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name_after_change, 'rb') as binary_file: + with self.assertRaisesRegex(ValueError, f"Label '{wrong_label}' is not registered for this task"): + response = self._upload_file(url_upload, binary_file, self.admin) + + # check for missing annotations + response = self._get_request(url, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["tags"]), 0) + self.assertEqual(len(response.data["shapes"]), 0) + self.assertEqual(len(response.data["tracks"]), 0) + + def test_api_v1_tasks_annotations_update_wrong_value_in_checkbox(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + upload_format_name = "CVAT 1.1" + + # create task with annotations + images = self._generate_task_images(3) + task = self._create_task(tasks["main"], images) + self._create_annotations(task, dump_format_name, "random") + task_id = task["id"] + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + data = { + "format": dump_format_name, + "action": "download", + } + with TestDir() as test_dir: + file_zip_name_before_change = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + file_zip_name_after_change = osp.join(test_dir, f'{test_name}_{dump_format_name}_wrong_checkbox_value.zip') + + # download zip file + self._download_file(url, data, self.admin, file_zip_name_before_change) + self._check_downloaded_file(file_zip_name_before_change) + + # remove annotations + self._remove_annotations(url, self.admin) + + # extract zip + folder_name = osp.join(test_dir, f'{test_name}_{dump_format_name}') + with zipfile.ZipFile(file_zip_name_before_change, 'r') as zip_ref: + zip_ref.extractall(folder_name) + + # change right label to wrong + wrong_checkbox_value = "wrong_checkbox_value" + tree = ET.parse(osp.join(folder_name, 'annotations.xml')) + root = tree.getroot() + element = root.find("./image[@id='0']/box[@label='car']/attribute[@name='check_name']") + element.text = wrong_checkbox_value + tree.write(osp.join(folder_name, 'annotations.xml')) + with zipfile.ZipFile(file_zip_name_after_change, 'w') as zip_ref: + zip_ref.write(osp.join(folder_name, 'annotations.xml'), 'annotations.xml') + + # upload annotations + url_upload = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name_after_change, 'rb') as binary_file: + with self.assertRaisesRegex(Exception, f"Failed to convert attribute 'car'='{wrong_checkbox_value}'"): + response = self._upload_file(url_upload, binary_file, self.admin) + + # check for missing annotations + response = self._get_request(url, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["tags"]), 0) + self.assertEqual(len(response.data["shapes"]), 0) + self.assertEqual(len(response.data["tracks"]), 0) + + def test_api_v1_tasks_annotations_dump_others_user(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + + # create task with annotations + images = self._generate_task_images(3) + task = self._create_task(tasks["main"], images) + self._create_annotations(task, dump_format_name, "random") + task_id = task["id"] + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + data = { + "format": dump_format_name, + "action": "download", + } + + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_admin_{dump_format_name}.zip') + response = self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self._check_downloaded_file(file_zip_name) + + file_zip_name = osp.join(test_dir, f'{test_name}_user_{dump_format_name}.zip') + response = self._download_file(url, data, self.user, file_zip_name) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self._check_downloaded_file(file_zip_name) + + file_zip_name = osp.join(test_dir, f'{test_name}_None_{dump_format_name}.zip') + response = self._download_file(url, data, None, file_zip_name) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + with self.assertRaises(FileNotFoundError): + self._check_downloaded_file(file_zip_name) + + def test_api_v1_tasks_dataset_export_others_user(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + + # create task with annotations + images = self._generate_task_images(3) + task = self._create_task(tasks["main"], images) + self._create_annotations(task, dump_format_name, "random") + task_id = task["id"] + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + + # dump dataset + url = f"/api/v1/tasks/{task_id}/dataset" + data = { + "format": dump_format_name, + "action": "download", + } + + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_admin_{dump_format_name}.zip') + response = self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self._check_downloaded_file(file_zip_name) + + file_zip_name = osp.join(test_dir, f'{test_name}_user_{dump_format_name}.zip') + response = self._download_file(url, data, self.user, file_zip_name) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self._check_downloaded_file(file_zip_name) + + file_zip_name = osp.join(test_dir, f'{test_name}_None_{dump_format_name}.zip') + response = self._download_file(url, data, None, file_zip_name) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + with self.assertRaises(FileNotFoundError): + self._check_downloaded_file(file_zip_name) + + def test_api_v1_tasks_annotations_update_others_user(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + upload_format_name = "CVAT 1.1" + + # create task with annotations + images = self._generate_task_images(3) + task = self._create_task(tasks["main"], images) + self._create_annotations(task, dump_format_name, "random") + task_id = task["id"] + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + data = { + "format": dump_format_name, + "action": "download", + } + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + response = self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + url_upload = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + for user in [self.admin, self.user, None]: + with open(file_zip_name, 'rb') as binary_file: + # remove annotations + self._remove_annotations(url, self.admin) + + # upload annotations + response = self._upload_file(url_upload, binary_file, user) + + if user is None: + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + # check for missing annotations + response = self._get_request(url, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["tags"]), 0) + self.assertEqual(len(response.data["shapes"]), 0) + else: + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + # check for presence annotations + response = self._get_request(url, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertNotEqual(len(response.data["tags"]), 0) + self.assertNotEqual(len(response.data["shapes"]), 0) + + def test_api_v1_tasks_annotations_dump_and_dataset_export_with_datumaro(self): + test_name = self._testMethodName + importer_format_name = { + 'COCO 1.0': 'coco', + 'CVAT for images 1.1': 'cvat', + 'CVAT for video 1.1': 'cvat', + 'Datumaro 1.0': 'datumaro_project', + 'LabelMe 3.0': 'label_me', + 'MOT 1.1': 'mot_seq', + 'MOTS PNG 1.0': 'mots_png', + 'PASCAL VOC 1.1': 'voc', + 'Segmentation mask 1.1': 'voc', + 'TFRecord 1.0': 'tf_detection_api', + 'YOLO 1.1': 'yolo', + 'ImageNet 1.0': 'imagenet_txt', + 'CamVid 1.0': 'camvid', + 'WiderFace 1.0': 'wider_face', + 'VGGFace2 1.0': 'vgg_face2', + } + + # get formats + dump_formats = dm.views.get_export_formats() + + for dump_format in dump_formats: + if dump_format.ENABLED: + dump_format_name = dump_format.DISPLAY_NAME + + with self.subTest(): + # TODO skip failed formats + if dump_format_name in [ + "CVAT for video 1.1", + "YOLO 1.1", + "ImageNet 1.0", + "Datumaro 1.0", + ]: + self.skipTest("Format is fail") + + # create task + images = self._generate_task_images(3) + task = self._create_task(tasks["main"], images) + + # create annotations + if dump_format_name in [ + "MOT 1.1", "MOTS PNG 1.0", \ + "PASCAL VOC 1.1", "Segmentation mask 1.1", \ + "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ + "WiderFace 1.0", "VGGFace2 1.0", \ + ]: + self._create_annotations(task, dump_format_name, "dafault") + else: + self._create_annotations(task, dump_format_name, "random") + + task_id = task["id"] + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + + data_datumaro = { + "dataset": None, + "annotations": None, + } + with TestDir() as test_dir: + for type_file in ("dataset", "annotations"): + # dump file + url = f"/api/v1/tasks/{task_id}/{type_file}" + data = { + "format": dump_format_name, + "action": "download", + } + file_zip_name = osp.join(test_dir, f'{test_name}_{type_file}_{dump_format_name}.zip') + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # extract zip + folder_name = osp.join(test_dir, f'{test_name}_{type_file}_{dump_format_name}') + with zipfile.ZipFile(file_zip_name, 'r') as zip_ref: + zip_ref.extractall(folder_name) + data_datumaro[type_file] = Dataset.import_from(folder_name, importer_format_name[dump_format_name]) + + # equals dataset vs annotations + compare_datasets(self, data_datumaro["dataset"], data_datumaro["annotations"]) + + def test_api_v1_tasks_annotations_dump_and_upload_many_jobs_with_datumaro(self): + test_name = self._testMethodName + upload_format_name = "CVAT 1.1" + + for include_images in (False, True): + for dump_format_name in ("CVAT for images 1.1", "CVAT for video 1.1"): + with self.subTest(): + # TODO skip failed formats + if dump_format_name in [ + "CVAT for video 1.1", # issues #2923 and #2945 + ]: + self.skipTest("Format is fail") + + # create task with annotations + images = self._generate_task_images(13) + task = self._create_task(tasks["many jobs"], images) + self._create_annotations(task, f'{dump_format_name} many jobs', "dafault") + + task_id = task["id"] + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_data = TaskData(task_ann.ir_data, Task.objects.get(pk=task_id)) + extractor = CvatTaskDataExtractor(task_data, include_images=include_images) + data_from_task_before_upload = Dataset.from_extractors(extractor) + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + data = { + "format": dump_format_name, + "action": "download", + } + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # remove annotations + self._remove_annotations(url, self.admin) + + # upload annotations + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name, 'rb') as binary_file: + response = self._upload_file(url, binary_file, self.admin) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + + # equals annotations + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_data = TaskData(task_ann.ir_data, Task.objects.get(pk=task_id)) + extractor = CvatTaskDataExtractor(task_data, include_images=include_images) + data_from_task_after_upload = Dataset.from_extractors(extractor) + compare_datasets(self, data_from_task_before_upload, data_from_task_after_upload) + + def test_api_v1_tasks_annotations_dump_and_upload_slice_track_with_datumaro(self): + test_name = self._testMethodName + dump_format_name = "CVAT for video 1.1" + upload_format_name = "CVAT 1.1" + + # create task with annotations + images = self._generate_task_images(5) + task = self._create_task(tasks["main"], images) + self._create_annotations(task, f'{dump_format_name} slice track', "dafault") + + task_id = task["id"] + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_data = TaskData(task_ann.ir_data, Task.objects.get(pk=task_id)) + extractor = CvatTaskDataExtractor(task_data) + data_from_task_before_upload = Dataset.from_extractors(extractor) + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + data = { + "format": dump_format_name, + "action": "download", + } + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # remove annotations + self._remove_annotations(url, self.admin) + + # upload annotations + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name, 'rb') as binary_file: + response = self._upload_file(url, binary_file, self.admin) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + + # equals annotations + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_data = TaskData(task_ann.ir_data, Task.objects.get(pk=task_id)) + extractor = CvatTaskDataExtractor(task_data) + data_from_task_after_upload = Dataset.from_extractors(extractor) + compare_datasets(self, data_from_task_before_upload, data_from_task_after_upload) + + def test_api_v1_tasks_annotations_dump_and_upload_merge(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + upload_format_name = "CVAT 1.1" + + # create task with annotations + images = self._generate_task_images(10) + task = self._create_task(tasks["change ovelap and segment size"], images) + self._create_annotations(task, f'{dump_format_name} merge', "dafault") + + task_id = task["id"] + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + TaskData(task_ann.ir_data, Task.objects.get(pk=task_id)) + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + data = { + "format": dump_format_name, + "action": "download", + } + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # remove annotations + self._remove_annotations(url, self.admin) + + # upload annotations + url_upload = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name, 'rb') as binary_file: + response = self._upload_file(url_upload, binary_file, self.admin) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + + # equals annotations + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + TaskData(task_ann.ir_data, Task.objects.get(pk=task_id)) + + response = self._get_request(url, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["tags"]), 1) + self.assertEqual(len(response.data["shapes"]), 14) # convert tracks to shapes + self.assertEqual(len(response.data["tracks"]), 0) + + def test_api_v1_tasks_annotations_dump_and_upload_rewrite(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + upload_format_name = "CVAT 1.1" + + # create task with annotations + images = self._generate_task_images(10) + task = self._create_task(tasks["change ovelap and segment size"], images) + self._create_annotations(task, f'{dump_format_name} merge', "dafault") + + task_id = task["id"] + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + TaskData(task_ann.ir_data, Task.objects.get(pk=task_id)) + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + data = { + "format": dump_format_name, + "action": "download", + } + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # upload annotations + url_upload = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name, 'rb') as binary_file: + response = self._upload_file(url_upload, binary_file, self.admin) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + + # equals annotations + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + TaskData(task_ann.ir_data, Task.objects.get(pk=task_id)) + + response = self._get_request(url, self.admin) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["tags"]), 1) + self.assertEqual(len(response.data["shapes"]), 14) # convert tracks to shapes + self.assertEqual(len(response.data["tracks"]), 0)