Add tests for export job dataset (#5160)

main
Maria Khrustaleva 3 years ago committed by GitHub
parent 0a5b71123d
commit ba74709c40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
from online detectors & interactors) (<https://github.com/opencv/cvat/pull/4543>) from online detectors & interactors) (<https://github.com/opencv/cvat/pull/4543>)
- Added Webhooks (<https://github.com/opencv/cvat/pull/4863>) - Added Webhooks (<https://github.com/opencv/cvat/pull/4863>)
- Authentication with social accounts google & github (<https://github.com/opencv/cvat/pull/5147>) - Authentication with social accounts google & github (<https://github.com/opencv/cvat/pull/5147>)
- REST API tests to export job datasets & annotations and validate their structure (<https://github.com/opencv/cvat/pull/5160>)
### Changed ### Changed
- `api/docs`, `api/swagger`, `api/schema`, `server/about` endpoints now allow unauthorized access (<https://github.com/opencv/cvat/pull/4928>, <https://github.com/opencv/cvat/pull/4935>) - `api/docs`, `api/swagger`, `api/schema`, `server/about` endpoints now allow unauthorized access (<https://github.com/opencv/cvat/pull/4928>, <https://github.com/opencv/cvat/pull/4935>)

@ -70,9 +70,18 @@ def _merge_table_rows(rows, keys_for_merge, field_id):
return list(merged_rows.values()) return list(merged_rows.values())
class JobAnnotation: class JobAnnotation:
def __init__(self, pk): def __init__(self, pk, is_prefetched=False):
self.db_job = models.Job.objects.select_related('segment__task') \ if is_prefetched:
.select_for_update().get(id=pk) self.db_job = models.Job.objects.select_related('segment__task') \
.select_for_update().get(id=pk)
else:
self.db_job = models.Job.objects.prefetch_related(
'segment',
'segment__task',
Prefetch('segment__task__data', queryset=models.Data.objects.select_related('video').prefetch_related(
Prefetch('images', queryset=models.Image.objects.order_by('frame'))
))
).get(pk=pk)
db_segment = self.db_job.segment db_segment = self.db_job.segment
self.start_frame = db_segment.start_frame self.start_frame = db_segment.start_frame
@ -630,7 +639,7 @@ class TaskAnnotation:
self.reset() self.reset()
for db_job in self.db_jobs: for db_job in self.db_jobs:
annotation = JobAnnotation(db_job.id) annotation = JobAnnotation(db_job.id, is_prefetched=True)
annotation.init_from_db() annotation.init_from_db()
if annotation.ir_data.version > self.ir_data.version: if annotation.ir_data.version > self.ir_data.version:
self.ir_data.version = annotation.ir_data.version self.ir_data.version = annotation.ir_data.version

@ -68,8 +68,8 @@ for i, color in enumerate(colormap):
To backup DB and data volume, please use commands below. To backup DB and data volume, please use commands below.
```console ```console
docker exec test_cvat_server_1 python manage.py dumpdata --indent 2 --natural-foreign --exclude=auth.permission --exclude=contenttypes > assets/cvat_db/data.json docker exec test_cvat_server_1 python manage.py dumpdata --indent 2 --natural-foreign --exclude=auth.permission --exclude=contenttypes > shared/assets/cvat_db/data.json
docker exec test_cvat_server_1 tar -cjv /home/django/data > assets/cvat_db/cvat_data.tar.bz2 docker exec test_cvat_server_1 tar -cjv /home/django/data > shared/assets/cvat_db/cvat_data.tar.bz2
``` ```
> Note: if you won't be use --indent options or will be use with other value > Note: if you won't be use --indent options or will be use with other value
@ -166,7 +166,7 @@ Assets directory has two parts:
``` ```
Just dump JSON assets with: Just dump JSON assets with:
``` ```
python3 tests/python/shared/utils/dump_objests.py python3 tests/python/shared/utils/dump_objects.py
``` ```
1. If your test infrastructure has been corrupted and you have errors during db restoring. 1. If your test infrastructure has been corrupted and you have errors during db restoring.

@ -4,8 +4,11 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
import json import json
import xml.etree.ElementTree as ET
import zipfile
from copy import deepcopy from copy import deepcopy
from http import HTTPStatus from http import HTTPStatus
from io import BytesIO
from typing import List from typing import List
import pytest import pytest
@ -489,6 +492,36 @@ class TestPatchJob:
assert response.status == HTTPStatus.FORBIDDEN assert response.status == HTTPStatus.FORBIDDEN
def _check_coco_job_annotations(content, values_to_be_checked):
exported_annotations = json.loads(content)
assert values_to_be_checked["shapes_length"] == len(exported_annotations["annotations"])
assert values_to_be_checked["job_size"] == len(exported_annotations["images"])
assert values_to_be_checked["task_size"] > len(exported_annotations["images"])
def _check_cvat_job_annotations(content, values_to_be_checked):
document = ET.fromstring(content)
# check meta information
meta = document.find("meta")
instance = list(meta)[0]
assert instance.tag == "job"
assert instance.find("id").text == values_to_be_checked["job_id"]
assert instance.find("size").text == str(values_to_be_checked["job_size"])
assert instance.find("start_frame").text == str(values_to_be_checked["start_frame"])
assert instance.find("stop_frame").text == str(values_to_be_checked["stop_frame"])
assert instance.find("mode").text == values_to_be_checked["mode"]
assert len(instance.find("segments")) == 1
# check number of images, their sorting, number of annotations
images = document.findall("image")
assert len(images) == values_to_be_checked["job_size"]
assert len(list(document.iter("box"))) == values_to_be_checked["shapes_length"]
current_id = values_to_be_checked["start_frame"]
for image_elem in images:
assert image_elem.attrib["id"] == str(current_id)
current_id += 1
@pytest.mark.usefixtures("restore_db_per_class") @pytest.mark.usefixtures("restore_db_per_class")
class TestJobDataset: class TestJobDataset:
def _export_dataset(self, username, jid, **kwargs): def _export_dataset(self, username, jid, **kwargs):
@ -510,3 +543,45 @@ class TestJobDataset:
job = jobs_with_shapes[0] job = jobs_with_shapes[0]
response = self._export_annotations(admin_user, job["id"], format="CVAT for images 1.1") response = self._export_annotations(admin_user, job["id"], format="CVAT for images 1.1")
assert response.data assert response.data
@pytest.mark.parametrize("username, jid", [("admin1", 14)])
@pytest.mark.parametrize(
"anno_format, anno_file_name, check_func",
[
("COCO 1.0", "annotations/instances_default.json", _check_coco_job_annotations),
("CVAT for images 1.1", "annotations.xml", _check_cvat_job_annotations),
],
)
def test_exported_job_dataset_structure(
self,
username,
jid,
anno_format,
anno_file_name,
check_func,
tasks,
jobs,
annotations,
):
job_data = jobs[jid]
annotations_before = annotations["job"][str(jid)]
values_to_be_checked = {
"task_size": tasks[job_data["task_id"]]["size"],
# NOTE: data step is not stored in assets, default = 1
"job_size": job_data["stop_frame"] - job_data["start_frame"] + 1,
"start_frame": job_data["start_frame"],
"stop_frame": job_data["stop_frame"],
"shapes_length": len(annotations_before["shapes"]),
"job_id": str(jid),
"mode": job_data["mode"],
}
response = self._export_dataset(username, jid, format=anno_format)
assert response.data
with zipfile.ZipFile(BytesIO(response.data)) as zip_file:
assert (
len(zip_file.namelist()) == values_to_be_checked["job_size"] + 1
) # images + annotation file
content = zip_file.read(anno_file_name)
check_func(content, values_to_be_checked)

@ -280,7 +280,53 @@
"version": 0 "version": 0
}, },
"14": { "14": {
"shapes": [], "shapes": [
{
"attributes": [],
"elements": [],
"frame": 15,
"group": 0,
"id": 41,
"label_id": 6,
"occluded": false,
"outside": false,
"points": [
53.062929061787145,
301.6390160183091,
197.94851258581548,
763.3266590389048
],
"rotation": 0.0,
"source": "manual",
"type": "rectangle",
"z_order": 0
},
{
"attributes": [
{
"spec_id": 1,
"value": "mazda"
}
],
"elements": [],
"frame": 16,
"group": 0,
"id": 42,
"label_id": 5,
"occluded": false,
"outside": false,
"points": [
172.0810546875,
105.990234375,
285.97262095255974,
138.40000000000146
],
"rotation": 0.0,
"source": "manual",
"type": "rectangle",
"z_order": 0
}
],
"tags": [], "tags": [],
"tracks": [], "tracks": [],
"version": 0 "version": 0
@ -932,6 +978,51 @@
"source": "manual", "source": "manual",
"type": "rectangle", "type": "rectangle",
"z_order": 0 "z_order": 0
},
{
"attributes": [],
"elements": [],
"frame": 15,
"group": 0,
"id": 41,
"label_id": 6,
"occluded": false,
"outside": false,
"points": [
53.062929061787145,
301.6390160183091,
197.94851258581548,
763.3266590389048
],
"rotation": 0.0,
"source": "manual",
"type": "rectangle",
"z_order": 0
},
{
"attributes": [
{
"spec_id": 1,
"value": "mazda"
}
],
"elements": [],
"frame": 16,
"group": 0,
"id": 42,
"label_id": 5,
"occluded": false,
"outside": false,
"points": [
172.0810546875,
105.990234375,
285.97262095255974,
138.40000000000146
],
"rotation": 0.0,
"source": "manual",
"type": "rectangle",
"z_order": 0
} }
], ],
"tags": [], "tags": [],

@ -36,7 +36,7 @@
"pk": 1, "pk": 1,
"fields": { "fields": {
"password": "pbkdf2_sha256$260000$DevmxlmLwciP1P6sZs2Qag$U9DFtjTWx96Sk95qY6UXVcvpdQEP2LcoFBftk5D2RKY=", "password": "pbkdf2_sha256$260000$DevmxlmLwciP1P6sZs2Qag$U9DFtjTWx96Sk95qY6UXVcvpdQEP2LcoFBftk5D2RKY=",
"last_login": "2022-10-17T17:09:16.903Z", "last_login": "2022-11-03T13:56:42.744Z",
"is_superuser": true, "is_superuser": true,
"username": "admin1", "username": "admin1",
"first_name": "Admin", "first_name": "Admin",
@ -2192,7 +2192,7 @@
], ],
"bug_tracker": "", "bug_tracker": "",
"created_date": "2021-12-14T19:46:37.969Z", "created_date": "2021-12-14T19:46:37.969Z",
"updated_date": "2022-03-05T09:47:49.679Z", "updated_date": "2022-11-03T13:57:25.895Z",
"status": "annotation", "status": "annotation",
"organization": null, "organization": null,
"source_storage": null, "source_storage": null,
@ -2463,7 +2463,7 @@
], ],
"bug_tracker": "", "bug_tracker": "",
"created_date": "2022-03-05T09:33:10.420Z", "created_date": "2022-03-05T09:33:10.420Z",
"updated_date": "2022-03-05T09:47:49.667Z", "updated_date": "2022-11-03T13:57:26.007Z",
"overlap": 0, "overlap": 0,
"segment_size": 5, "segment_size": 5,
"status": "annotation", "status": "annotation",
@ -3556,10 +3556,10 @@
"fields": { "fields": {
"segment": 14, "segment": 14,
"assignee": null, "assignee": null,
"updated_date": "2022-06-22T09:18:45.296Z", "updated_date": "2022-11-03T13:57:26.346Z",
"status": "annotation", "status": "annotation",
"stage": "annotation", "stage": "annotation",
"state": "new" "state": "in progress"
} }
}, },
{ {
@ -4751,6 +4751,55 @@
"job": 17 "job": 17
} }
}, },
{
"model": "engine.jobcommit",
"pk": 74,
"fields": {
"scope": "create",
"owner": [
"business1"
],
"timestamp": "2022-11-03T13:57:26.015Z",
"data": {
"stage": "annotation",
"state": "new",
"assignee": null
},
"job": 14
}
},
{
"model": "engine.jobcommit",
"pk": 75,
"fields": {
"scope": "create",
"owner": [
"business1"
],
"timestamp": "2022-11-03T13:57:26.351Z",
"data": {
"stage": "annotation",
"state": "in progress",
"assignee": null
},
"job": 14
}
},
{
"model": "engine.jobcommit",
"pk": 76,
"fields": {
"scope": "update",
"owner": [
"admin1"
],
"timestamp": "2022-11-03T13:57:26.356Z",
"data": {
"state": "in progress"
},
"job": 14
}
},
{ {
"model": "engine.labeledimage", "model": "engine.labeledimage",
"pk": 1, "pk": 1,
@ -5079,6 +5128,42 @@
"parent": 36 "parent": 36
} }
}, },
{
"model": "engine.labeledshape",
"pk": 41,
"fields": {
"job": 14,
"label": 6,
"frame": 15,
"group": 0,
"source": "manual",
"type": "rectangle",
"occluded": false,
"outside": false,
"z_order": 0,
"points": "[53.062929061787145, 301.6390160183091, 197.94851258581548, 763.3266590389048]",
"rotation": 0.0,
"parent": null
}
},
{
"model": "engine.labeledshape",
"pk": 42,
"fields": {
"job": 14,
"label": 5,
"frame": 16,
"group": 0,
"source": "manual",
"type": "rectangle",
"occluded": false,
"outside": false,
"z_order": 0,
"points": "[172.0810546875, 105.990234375, 285.97262095255974, 138.40000000000146]",
"rotation": 0.0,
"parent": null
}
},
{ {
"model": "engine.labeledshapeattributeval", "model": "engine.labeledshapeattributeval",
"pk": 1, "pk": 1,
@ -5097,6 +5182,15 @@
"shape": 39 "shape": 39
} }
}, },
{
"model": "engine.labeledshapeattributeval",
"pk": 3,
"fields": {
"spec": 1,
"value": "mazda",
"shape": 42
}
},
{ {
"model": "engine.labeledtrack", "model": "engine.labeledtrack",
"pk": 1, "pk": 1,
@ -5920,6 +6014,85 @@
"organization": 1 "organization": 1
} }
}, },
{
"model": "webhooks.webhookdelivery",
"pk": 1,
"fields": {
"webhook": 2,
"event": "update:job",
"status_code": 200,
"redelivery": false,
"created_date": "2022-11-03T13:57:26.380Z",
"updated_date": "2022-11-03T13:57:26.908Z",
"changed_fields": "state",
"request": {
"job": {
"id": 14,
"url": "http://localhost:8080/api/jobs/14",
"mode": "annotation",
"stage": "annotation",
"state": "in progress",
"labels": [
{
"id": 6,
"name": "person",
"type": "any",
"color": "#c06060",
"sublabels": [],
"attributes": [],
"has_parent": false
},
{
"id": 5,
"name": "car",
"type": "any",
"color": "#2080c0",
"sublabels": [],
"attributes": [
{
"id": 1,
"name": "model",
"values": [
"mazda",
"volvo",
"bmw"
],
"mutable": false,
"input_type": "select",
"default_value": "mazda"
}
],
"has_parent": false
}
],
"status": "annotation",
"task_id": 9,
"assignee": null,
"dimension": "2d",
"project_id": 1,
"stop_frame": 19,
"bug_tracker": "",
"start_frame": 15,
"updated_date": "2022-11-03T13:57:26.346824Z",
"data_chunk_size": 72,
"data_compressed_chunk_type": "imageset"
},
"event": "update:job",
"sender": {
"id": 1,
"url": "http://localhost:8080/api/users/1",
"username": "admin1",
"last_name": "First",
"first_name": "Admin"
},
"webhook_id": 2,
"before_update": {
"state": "new"
}
},
"response": "<!doctype html>\n<html>\n<head>\n <title>Example Domain</title>\n\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <style type=\"text/css\">\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </style> \n</head>\n\n<body>\n<div>\n <h1>Example Domain</h1>\n <p>This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.</p>\n <p><a href=\"https://www.iana.org/domains/example\">More information...</a></p>\n</div>\n</body>\n</html>\n"
}
},
{ {
"model": "admin.logentry", "model": "admin.logentry",
"pk": 1, "pk": 1,

@ -326,11 +326,11 @@
"project_id": 1, "project_id": 1,
"stage": "annotation", "stage": "annotation",
"start_frame": 15, "start_frame": 15,
"state": "new", "state": "in progress",
"status": "annotation", "status": "annotation",
"stop_frame": 19, "stop_frame": 19,
"task_id": 9, "task_id": 9,
"updated_date": "2022-06-22T09:18:45.296000Z", "updated_date": "2022-11-03T13:57:26.346000Z",
"url": "http://localhost:8080/api/jobs/14" "url": "http://localhost:8080/api/jobs/14"
}, },
{ {

@ -508,7 +508,7 @@
"tasks": [ "tasks": [
9 9
], ],
"updated_date": "2022-03-05T09:47:49.679000Z", "updated_date": "2022-11-03T13:57:25.895000Z",
"url": "http://localhost:8080/api/projects/1" "url": "http://localhost:8080/api/projects/1"
} }
] ]

@ -550,7 +550,7 @@
"assignee": null, "assignee": null,
"id": 14, "id": 14,
"stage": "annotation", "stage": "annotation",
"state": "new", "state": "in progress",
"status": "annotation", "status": "annotation",
"url": "http://localhost:8080/api/jobs/14" "url": "http://localhost:8080/api/jobs/14"
} }
@ -564,7 +564,7 @@
"status": "annotation", "status": "annotation",
"subset": "", "subset": "",
"target_storage": null, "target_storage": null,
"updated_date": "2022-03-05T09:47:49.667000Z", "updated_date": "2022-11-03T13:57:26.007000Z",
"url": "http://localhost:8080/api/tasks/9" "url": "http://localhost:8080/api/tasks/9"
}, },
{ {

@ -310,7 +310,7 @@
"is_active": true, "is_active": true,
"is_staff": true, "is_staff": true,
"is_superuser": true, "is_superuser": true,
"last_login": "2022-10-17T17:09:16.903140Z", "last_login": "2022-11-03T13:56:42.744000Z",
"last_name": "First", "last_name": "First",
"url": "http://localhost:8080/api/users/1", "url": "http://localhost:8080/api/users/1",
"username": "admin1" "username": "admin1"

@ -91,6 +91,8 @@
], ],
"id": 2, "id": 2,
"is_active": true, "is_active": true,
"last_delivery_date": "2022-11-03T13:57:26.908000Z",
"last_status": 200,
"organization": null, "organization": null,
"owner": { "owner": {
"first_name": "Business", "first_name": "Business",

Loading…
Cancel
Save