Skip to content
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

Fix import annotations for frames with dots in name #3091

Merged
merged 10 commits into from
Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cvat/apps/dataset_manager/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,9 @@ def db_task(self):
def _get_filename(path):
return osp.splitext(path)[0]

def match_frame(self, path, root_hint=None):
path = self._get_filename(path)
def match_frame(self, path, root_hint=None, path_has_ext=True):
if path_has_ext:
path = self._get_filename(path)
match = self._frame_mapping.get(path)
if not match and root_hint and not path.startswith(root_hint):
path = osp.join(root_hint, path)
Expand Down Expand Up @@ -611,7 +612,7 @@ def match_dm_item(item, task_data, root_hint=None):
if frame_number is None and item.has_image:
frame_number = task_data.match_frame(item.id + item.image.ext, root_hint)
if frame_number is None:
frame_number = task_data.match_frame(item.id, root_hint)
frame_number = task_data.match_frame(item.id, root_hint, path_has_ext=False)
if frame_number is None:
frame_number = cast(item.attributes.get('frame', item.id), int)
if frame_number is None and is_video:
Expand Down
5 changes: 3 additions & 2 deletions cvat/apps/dataset_manager/formats/cvat.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,9 @@ def load(file_object, annotations):
elif el.tag == 'image':
image_is_opened = True
frame_id = annotations.abs_frame_id(match_dm_item(
DatasetItem(id=el.attrib['name'],
attributes={'frame': el.attrib['id']}
DatasetItem(id=osp.splitext(el.attrib['name'])[0],
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved
attributes={'frame': el.attrib['id']},
image=el.attrib['name']
),
task_data=annotations
))
Expand Down
325 changes: 325 additions & 0 deletions cvat/apps/dataset_manager/tests/test_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ def test_frames_outside_are_not_generated(self):
self.assertTrue(frame.frame in range(6, 10))
self.assertEqual(i + 1, 4)


class FrameMatchingTest(_DbTestBase):
def _generate_task_images(self, paths): # pylint: disable=no-self-use
f = BytesIO()
Expand Down Expand Up @@ -586,3 +587,327 @@ def test_dataset_root(self):

root = find_dataset_root(dataset, task_data)
self.assertEqual(expected, root)

class TaskAnnotationsImportTest(_DbTestBase):
def _generate_custom_annotations(self, annotations, task):
self._put_api_v1_task_id_annotations(task["id"], annotations)
return annotations

def _generate_task_images(self, count, name="image"):
images = {
"client_files[%d]" % i: generate_image_file("image_%d.jpg" % i)
for i in range(count)
}
images["image_quality"] = 75
return images

def _generate_task(self, images, annotation_format, **overrides):
labels = []
if annotation_format in ["ICDAR Recognition 1.0",
"ICDAR Localization 1.0"]:
labels = [{
"name": "icdar",
"attributes": [{
"name": "text",
"mutable": False,
"input_type": "text",
"values": ["word1", "word2"]
}]
}]
elif annotation_format == "ICDAR Segmentation 1.0":
labels = [{
"name": "icdar",
"attributes": [
{
"name": "text",
"mutable": False,
"input_type": "text",
"values": ["word_1", "word_2", "word_3"]
},
{
"name": "index",
"mutable": False,
"input_type": "number",
"values": ["0", "1", "2"]
},
{
"name": "color",
"mutable": False,
"input_type": "text",
"values": ["100 110 240", "10 15 20", "120 128 64"]
},
{
"name": "center",
"mutable": False,
"input_type": "text",
"values": ["1 2", "2 4", "10 45"]
},
]
}]
elif annotation_format == "Market-1501 1.0":
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"]
},
{
"name": "person_id",
"mutable": False,
"input_type": "number",
"values": ["1", "2", "3"]
},
]
}]
else:
labels = [
{
"name": "car",
"attributes": [
{
"name": "model",
"mutable": False,
"input_type": "select",
"default_value": "mazda",
"values": ["bmw", "mazda", "renault"]
},
{
"name": "parked",
"mutable": True,
"input_type": "checkbox",
"default_value": False
}
]
},
{"name": "person"}
]

task = {
"name": "my task #1",
"overlap": 0,
"segment_size": 100,
"labels": labels
}
task.update(overrides)
return self._create_task(task, images)

def _generate_annotations(self, task, annotation_format):
shapes = []
tracks = []
tags = []

if annotation_format in ["ICDAR Recognition 1.0",
"ICDAR Localization 1.0"]:
shapes = [{
"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]
},
],
"points": [1.0, 2.1, 10.6, 53.22],
"type": "rectangle",
"occluded": False,
}]
elif annotation_format == "Market-1501 1.0":
tags = [{
"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]
}
],
}]
elif annotation_format == "ICDAR Segmentation 1.0":
shapes = [{
"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]["values"][0]
},
{
"spec_id": task["labels"][0]["attributes"][2]["id"],
"value": task["labels"][0]["attributes"][2]["values"][1]
},
{
"spec_id": task["labels"][0]["attributes"][3]["id"],
"value": task["labels"][0]["attributes"][3]["values"][2]
}
],
"points": [1.0, 2.1, 10.6, 53.22],
"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,
"label_id": task["labels"][1]["id"],
"group": 0,
"source": "manual",
"attributes": [],
"points": [2.0, 2.1, 40, 50.7],
"type": "rectangle",
"occluded": False,
}

rectangle_shape_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,
}

track_wo_attrs = {
"frame": 0,
"label_id": task["labels"][1]["id"],
"group": 0,
"source": "manual",
"attributes": [],
"shapes": [
{
"frame": 0,
"attributes": [],
"points": [1.0, 2.1, 100, 300.222],
"type": "polygon",
"occluded": False,
"outside": False
}
]
}

tag_wo_attrs = {
"frame": 0,
"label_id": task["labels"][0]["id"],
"group": None,
"attributes": []
}

tag_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"]
}
],
}

if annotation_format == "VGGFace2 1.0":
shapes = rectangle_shape_wo_attrs
elif annotation_format == "CVAT 1.1":
shapes = [rectangle_shape_wo_attrs,
rectangle_shape_with_attrs]
tags = [tag_with_attrs, tag_wo_attrs]
elif annotation_format == "MOTS PNG 1.0":
tracks = [track_wo_attrs]
else:
shapes = [rectangle_shape_wo_attrs,
rectangle_shape_with_attrs]
tags = tag_wo_attrs
tracks = track_wo_attrs

annotations = {
"version": 0,
"tags": tags,
"shapes": shapes,
"tracks": tracks
}

return self._generate_custom_annotations(annotations, task)

def _test_can_import_annotations(self, task, import_format):
with tempfile.TemporaryDirectory() as temp_dir:
file_path = osp.join(temp_dir, import_format)

export_format = import_format
if import_format == "CVAT 1.1":
export_format = "CVAT for images 1.1"

dm.task.export_task(task["id"], file_path, export_format)
expected_ann = TaskAnnotation(task["id"])
expected_ann.init_from_db()

dm.task.import_task_annotations(task["id"],
file_path, import_format)
actual_ann = TaskAnnotation(task["id"])
actual_ann.init_from_db()

self.assertEqual(len(expected_ann.data), len(actual_ann.data))

def test_can_import_annotations_for_image_with_dots_in_filename(self):
for f in dm.views.get_import_formats():
format_name = f.DISPLAY_NAME

images = self._generate_task_images(3, "img0.0.0")
task = self._generate_task(images, format_name)
self._generate_annotations(task, format_name)

with self.subTest(format=format_name):
if not f.ENABLED:
self.skipTest("Format is disabled")

self._test_can_import_annotations(task, format_name)