diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index a8d2fcb98da3..b800e18ca3cf 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -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) @@ -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: diff --git a/cvat/apps/dataset_manager/formats/cvat.py b/cvat/apps/dataset_manager/formats/cvat.py index 02025afc750a..786a5025e7c0 100644 --- a/cvat/apps/dataset_manager/formats/cvat.py +++ b/cvat/apps/dataset_manager/formats/cvat.py @@ -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], + attributes={'frame': el.attrib['id']}, + image=el.attrib['name'] ), task_data=annotations )) diff --git a/cvat/apps/dataset_manager/tests/test_formats.py b/cvat/apps/dataset_manager/tests/test_formats.py index f4589feedc05..2a7c40cdad6e 100644 --- a/cvat/apps/dataset_manager/tests/test_formats.py +++ b/cvat/apps/dataset_manager/tests/test_formats.py @@ -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() @@ -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) \ No newline at end of file