Skip to content

Fix loss of rotation in CVAT format #5407

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 5, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ non-ascii paths while adding files from "Connected file share" (issue #4428)
- Fixed FBRS serverless function runtime error on images with alpha channel (<https://github.com/opencv/cvat/pull/5384>)
- Attaching manifest with custom name (<https://github.com/opencv/cvat/pull/5377>)
- Uploading non-zip annotaion files (<https://github.com/opencv/cvat/pull/5386>)
- Loss of rotation in CVAT format (<https://github.com/opencv/cvat/pull/5407>)
- A permission problem with interactive model launches for workers in orgs (<https://github.com/opencv/cvat/issues/4996>)
- Fix chart not being upgradable (<https://github.com/opencv/cvat/pull/5371>)
- Broken helm chart - if using custom release name (<https://github.com/opencv/cvat/pull/5403>)
Expand Down
3 changes: 3 additions & 0 deletions cvat/apps/dataset_manager/formats/cvat.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ def _parse(cls, path):
shape['type'] = el.tag
shape['occluded'] = (el.attrib.get('occluded') == '1')
shape['z_order'] = int(el.attrib.get('z_order', 0))
shape['rotation'] = float(el.attrib.get('rotation', 0))

if el.tag == 'box':
shape['points'] = list(map(float, [
Expand Down Expand Up @@ -440,6 +441,8 @@ def _parse_shape_ann(cls, ann, categories):
attributes['keyframe'] = ann['keyframe']
if 'track_id' in ann:
attributes['track_id'] = ann['track_id']
if 'rotation' in ann:
attributes['rotation'] = ann['rotation']

group = ann.get('group')

Expand Down
36 changes: 36 additions & 0 deletions tests/python/rest_api/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# SPDX-License-Identifier: MIT

import io
import json
import xml.etree.ElementTree as ET
import zipfile
from copy import deepcopy
Expand Down Expand Up @@ -402,6 +403,14 @@ def _test_import_project(self, username, project_id, format_name, data):
if response.status == HTTPStatus.CREATED:
break

def _test_get_annotations_from_task(self, username, task_id):
with make_api_client(username) as api_client:
(_, response) = api_client.tasks_api.retrieve_annotations(task_id)
assert response.status == HTTPStatus.OK

response_data = json.loads(response.data)
return response_data

def test_can_import_dataset_in_org(self, admin_user):
project_id = 4

Expand Down Expand Up @@ -546,6 +555,33 @@ def test_exported_project_dataset_structure(
content = zip_file.read(anno_file_name)
check_func(content, values_to_be_checked)

def test_can_import_export_annotations_with_rotation(self):
# https://github.com/opencv/cvat/issues/4378
username = "admin1"
project_id = 4

response = self._test_export_project(username, project_id, "CVAT for images 1.1")

tmp_file = io.BytesIO(response.data)
tmp_file.name = "dataset.zip"

import_data = {
"dataset_file": tmp_file,
}

self._test_import_project(username, project_id, "CVAT 1.1", import_data)

response = get_method(username, f"/projects/{project_id}/tasks")
assert response.status_code == HTTPStatus.OK
tasks = response.json()["results"]

response_data = self._test_get_annotations_from_task(username, tasks[0]["id"])
task1_rotation = response_data["shapes"][0]["rotation"]
response_data = self._test_get_annotations_from_task(username, tasks[1]["id"])
task2_rotation = response_data["shapes"][0]["rotation"]

assert task1_rotation == task2_rotation
Copy link
Contributor

@nmanovic nmanovic Dec 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the check help us to prevent a regression?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this test fails without a fix.



@pytest.mark.usefixtures("restore_db_per_function")
class TestPatchProjectLabel:
Expand Down
20 changes: 10 additions & 10 deletions tests/python/shared/assets/annotations.json
Original file line number Diff line number Diff line change
Expand Up @@ -376,12 +376,12 @@
"occluded": false,
"outside": false,
"points": [
106.36066411239153,
85.1515964240134,
240.08352490421748,
241.26526181354166
106.361328125,
85.150390625,
240.083984375,
241.263671875
],
"rotation": 0.0,
"rotation": 45.9,
"source": "manual",
"type": "rectangle",
"z_order": 0
Expand Down Expand Up @@ -1121,12 +1121,12 @@
"occluded": false,
"outside": false,
"points": [
106.36066411239153,
85.1515964240134,
240.08352490421748,
241.26526181354166
106.361328125,
85.150390625,
240.083984375,
241.263671875
],
"rotation": 0.0,
"rotation": 45.9,
"source": "manual",
"type": "rectangle",
"z_order": 0
Expand Down
Binary file modified tests/python/shared/assets/cvat_db/cvat_data.tar.bz2
Binary file not shown.
41 changes: 33 additions & 8 deletions tests/python/shared/assets/cvat_db/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"pk": 1,
"fields": {
"password": "pbkdf2_sha256$260000$DevmxlmLwciP1P6sZs2Qag$U9DFtjTWx96Sk95qY6UXVcvpdQEP2LcoFBftk5D2RKY=",
"last_login": "2022-12-01T12:52:15.631Z",
"last_login": "2022-12-05T07:46:24.795Z",
"is_superuser": true,
"username": "admin1",
"first_name": "Admin",
Expand Down Expand Up @@ -455,6 +455,14 @@
"user_permissions": []
}
},
{
"model": "sessions.session",
"pk": "3p7i7jibc6g827i39r8e0ce5i9f1orzp",
"fields": {
"session_data": ".eJxVjDEOAiEQAP9CbQiwgGBp7xsIC6ycGkiOu8r4d0NyhbYzk3mzEPethn2UNSyZXZhkp1-GMT1LmyI_Yrt3nnrb1gX5TPhhB7_1XF7Xo_0b1Djq3AoglJTJACKS9zGB0eAdopNkz6TIWq2LLQ611gkEgjFJCTIChFPs8wX0UDee:1p26BE:nXJF4Fl6fGAvhi1ZZfLr4WJSgvc53poHLXXAM5V6NdU",
"expire_date": "2022-12-19T07:46:24.799Z"
}
},
{
"model": "sessions.session",
"pk": "5x9v6r58e4l9if78anupog0ittsq2w3j",
Expand Down Expand Up @@ -625,12 +633,12 @@
},
{
"model": "authtoken.token",
"pk": "4f057576712c65d30847e77456aea605a9df5965",
"pk": "2dcf8d2ff5032c3cf7d276549af3a50edb4f11c8",
"fields": {
"user": [
"admin1"
],
"created": "2022-09-28T12:20:48.631Z"
"created": "2022-12-05T07:46:24.792Z"
}
},
{
Expand Down Expand Up @@ -2279,7 +2287,7 @@
"assignee": null,
"bug_tracker": "",
"created_date": "2022-06-08T08:32:45.521Z",
"updated_date": "2022-10-17T17:09:36.526Z",
"updated_date": "2022-12-05T07:47:01.518Z",
"status": "annotation",
"organization": 2,
"source_storage": null,
Expand Down Expand Up @@ -2587,7 +2595,7 @@
"assignee": null,
"bug_tracker": "",
"created_date": "2022-06-08T08:33:06.505Z",
"updated_date": "2022-10-17T17:09:36.570Z",
"updated_date": "2022-12-05T07:47:01.633Z",
"overlap": 0,
"segment_size": 5,
"status": "annotation",
Expand Down Expand Up @@ -3671,7 +3679,7 @@
"fields": {
"segment": 17,
"assignee": null,
"updated_date": "2022-10-17T17:09:36.571Z",
"updated_date": "2022-12-05T07:47:01.633Z",
"status": "annotation",
"stage": "annotation",
"state": "in progress"
Expand Down Expand Up @@ -4991,6 +4999,23 @@
"job": 19
}
},
{
"model": "engine.jobcommit",
"pk": 81,
"fields": {
"scope": "create",
"owner": [
"admin1"
],
"timestamp": "2022-12-05T07:47:01.635Z",
"data": {
"stage": "annotation",
"state": "in progress",
"assignee": null
},
"job": 17
}
},
{
"model": "engine.labeledimage",
"pk": 1,
Expand Down Expand Up @@ -5206,8 +5231,8 @@
"occluded": false,
"outside": false,
"z_order": 0,
"points": "[106.36066411239153, 85.1515964240134, 240.08352490421748, 241.26526181354166]",
"rotation": 0.0,
"points": "[106.361328125, 85.150390625, 240.083984375, 241.263671875]",
"rotation": 45.9,
"parent": null
}
},
Expand Down
2 changes: 1 addition & 1 deletion tests/python/shared/assets/jobs.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@
"status": "annotation",
"stop_frame": 4,
"task_id": 13,
"updated_date": "2022-10-17T17:09:36.571000Z",
"updated_date": "2022-12-05T07:47:01.633000Z",
"url": "http://localhost:8080/api/jobs/17"
},
{
Expand Down
2 changes: 1 addition & 1 deletion tests/python/shared/assets/projects.json
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@
"tasks": [
13
],
"updated_date": "2022-10-17T17:09:36.526000Z",
"updated_date": "2022-12-05T07:47:01.518000Z",
"url": "http://localhost:8080/api/projects/4"
},
{
Expand Down
2 changes: 1 addition & 1 deletion tests/python/shared/assets/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@
"status": "annotation",
"subset": "",
"target_storage": null,
"updated_date": "2022-10-17T17:09:36.570000Z",
"updated_date": "2022-12-05T07:47:01.633000Z",
"url": "http://localhost:8080/api/tasks/13"
},
{
Expand Down
2 changes: 1 addition & 1 deletion tests/python/shared/assets/users.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@
"is_active": true,
"is_staff": true,
"is_superuser": true,
"last_login": "2022-12-01T12:52:15.631000Z",
"last_login": "2022-12-05T07:46:24.795659Z",
"last_name": "First",
"url": "http://localhost:8080/api/users/1",
"username": "admin1"
Expand Down