From 7ba292a190cab9865e47902a19d59ef66b4625b5 Mon Sep 17 00:00:00 2001 From: Pedro Date: Wed, 29 Dec 2021 15:31:13 +0100 Subject: [PATCH 1/8] Added types and docs --- darwin/datatypes.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/darwin/datatypes.py b/darwin/datatypes.py index 9bee1130a..af824f25a 100644 --- a/darwin/datatypes.py +++ b/darwin/datatypes.py @@ -602,7 +602,20 @@ def make_instance_id(value: int) -> SubAnnotation: return SubAnnotation("instance_id", value) -def make_attributes(attributes: Any) -> SubAnnotation: +def make_attributes(attributes: List[str]) -> SubAnnotation: + """ + Creates and returns an attributes sub-annotation. + + Parameters + ---------- + value: List[str] + A list of attributes. Example: ``["orange", "big"]``. + + Returns + ------- + SubAnnotation + An attributes ``SubAnnotation``. + """ return SubAnnotation("attributes", attributes) From 7652689581003055207328dca4028d2c499b1442 Mon Sep 17 00:00:00 2001 From: Pedro Date: Wed, 29 Dec 2021 15:31:35 +0100 Subject: [PATCH 2/8] adding support for attribute imports --- darwin/importer/formats/superannotate.py | 48 ++++++++++++-- .../formats/import_superannotate_test.py | 63 +++++++++++++++++++ 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/darwin/importer/formats/superannotate.py b/darwin/importer/formats/superannotate.py index d817bdebe..af2472a14 100644 --- a/darwin/importer/formats/superannotate.py +++ b/darwin/importer/formats/superannotate.py @@ -12,6 +12,8 @@ AnnotationFile, CuboidData, Point, + SubAnnotation, + make_attributes, make_bounding_box, make_cuboid, make_ellipse, @@ -170,8 +172,14 @@ def _to_bbox_annotation(bbox: Dict[str, Any], classes: List[Dict[str, Any]]) -> h: float = abs(cast(float, points.get("y1")) - cast(float, points.get("y2"))) class_id: int = cast(int, bbox.get("classId")) - name = _find_class_name(class_id, classes) - return make_bounding_box(name, x, y, w, h) + instance_class: Dict[str, Any] = _find_class(class_id, classes) + name: str = str(instance_class.get("name")) + attributes: Optional[SubAnnotation] = _get_attributes(bbox, instance_class) + subannotations: Optional[List[SubAnnotation]] = None + if attributes: + subannotations = [attributes] + + return make_bounding_box(name, x, y, w, h, subannotations) def _to_ellipse_annotation(ellipse: Dict[str, Any], classes: List[Dict[str, Any]]) -> Annotation: @@ -230,7 +238,7 @@ def _to_line_annotation(line: Dict[str, Any], classes: List[Dict[str, Any]]) -> return make_line(name, points) -def _find_class_name(class_id: int, classes: List[Dict[str, Any]]) -> str: +def _find_class(class_id: int, classes: List[Dict[str, Any]]) -> Dict[str, Any]: obj: Optional[Dict[str, Any]] = next((class_obj for class_obj in classes if class_obj.get("id") == class_id), None) if obj is None: @@ -238,7 +246,39 @@ def _find_class_name(class_id: int, classes: List[Dict[str, Any]]) -> str: f"No class with id '{class_id}' was found in {classes}.\nCannot continue import, pleaase check your 'classes.json' file." ) - return str(obj.get("name")) + return obj + + +def _get_attributes(instance: Dict[str, Any], instance_class: Dict[str, Any]) -> Optional[SubAnnotation]: + attribute_info: List[Dict[str, int]] = cast(List[Dict[str, int]], instance.get("attributes")) + groups: List[Dict[str, Any]] = cast(List[Dict[str, Any]], instance_class.get("attribute_groups")) + all_attributes: List[str] = [] + + for info in attribute_info: + info_group_id: int = cast(int, info.get("groupId")) + attribute_id: int = cast(int, info.get("id")) + + for group in groups: + group_id: int = cast(int, group.get("id")) + + if info_group_id == group_id: + group_attributes: List[Dict[str, Union[str, int]]] = cast( + List[Dict[str, Union[str, int]]], group.get("attributes") + ) + attribute: Optional[Dict[str, Union[str, int]]] = next( + (attribute for attribute in group_attributes if attribute.get("id") == attribute_id), None + ) + + if attribute is None: + raise ValueError(f"No attribute data found for {info}.") + + final_attribute = f"{str(group.get('name'))}-{str(attribute.get('name'))}" + all_attributes.append(final_attribute) + + if all_attributes == []: + return None + + return make_attributes(all_attributes) def _get_class(annotation: Annotation) -> AnnotationClass: diff --git a/tests/darwin/importer/formats/import_superannotate_test.py b/tests/darwin/importer/formats/import_superannotate_test.py index c3e54e289..4d65e2a5d 100644 --- a/tests/darwin/importer/formats/import_superannotate_test.py +++ b/tests/darwin/importer/formats/import_superannotate_test.py @@ -620,6 +620,69 @@ def it_imports_bbox_vectors(annotations_file_path: Path, classes_file_path: Path annotation_class = bbox_annotation.annotation_class assert_annotation_class(annotation_class, "Person", "bounding_box") + def it_imports_attributes(annotations_file_path: Path, classes_file_path: Path): + + annotations_json: str = """ + { + "instances": [ + { + "type": "bbox", + "classId": 1, + "points": {"x1": 1642.9, "x2": 1920, "y1": 516.5, "y2": 734}, + "attributes": [{"id": 2, "groupId": 1}, {"id": 3, "groupId": 2}] + } + ], + "metadata": {"name": "demo-image-0.jpg"} + } + """ + classes_json: str = """ + [ + { + "attribute_groups": [ + { + "id": 1, + "name": "Sex", + "attributes": [ + {"id": 1, "name": "Male"}, + {"id": 2, "name": "Female"} + ] + }, + { + "id": 2, + "name": "Emotion", + "attributes": [ + {"id": 3, "name": "Smiling"}, + {"id": 4, "name": "Sadness"}, + {"id": 5, "name": "Crying"} + ] + } + ], + "id": 1, + "name": "Person" + } + ] + """ + + annotations_file_path.write_text(annotations_json) + classes_file_path.write_text(classes_json) + + annotation_file: Optional[AnnotationFile] = parse_path(annotations_file_path) + assert annotation_file is not None + assert annotation_file.path == annotations_file_path + assert annotation_file.filename == "demo-image-0.jpg" + assert annotation_file.annotation_classes + assert annotation_file.remote_path == "/" + + assert annotation_file.annotations + + bbox_annotation: Annotation = cast(Annotation, annotation_file.annotations.pop()) + assert_bbox(bbox_annotation, 1642.9, 516.5, 217.5, 277.1) + + annotation_class = bbox_annotation.annotation_class + assert_annotation_class(annotation_class, "Person", "bounding_box") + + assert_subannotations(bbox_annotation.subs, [SubAnnotation("attributes", ["Sex-Female", "Emotion-Smiling"])]) + def assert_cuboid(annotation: Annotation, cuboid: CuboidData) -> None: cuboid_back: Dict[str, float] = cast(Dict[str, float], cuboid.get("back")) From 8c9ca04ded47a0a729d8b831886641120e50cf78 Mon Sep 17 00:00:00 2001 From: Pedro Date: Wed, 29 Dec 2021 16:42:48 +0100 Subject: [PATCH 3/8] Fixed tests and added check in schema --- darwin/importer/formats/superannotate.py | 2 +- .../importer/formats/superannotate_schemas.py | 82 ++++++++++--- .../formats/import_superannotate_test.py | 110 +++++++++++++++--- 3 files changed, 159 insertions(+), 35 deletions(-) diff --git a/darwin/importer/formats/superannotate.py b/darwin/importer/formats/superannotate.py index b06505960..b57f30f3d 100644 --- a/darwin/importer/formats/superannotate.py +++ b/darwin/importer/formats/superannotate.py @@ -277,7 +277,7 @@ def _get_attributes(instance: Dict[str, Any], instance_class: Dict[str, Any]) -> if attribute is None: raise ValueError(f"No attribute data found for {info}.") - final_attribute = f"{str(group.get('name'))}-{str(attribute.get('name'))}" + final_attribute = f"{str(group.get('name'))}:{str(attribute.get('name'))}" all_attributes.append(final_attribute) if all_attributes == []: diff --git a/darwin/importer/formats/superannotate_schemas.py b/darwin/importer/formats/superannotate_schemas.py index 45c61af00..bb60e67c5 100644 --- a/darwin/importer/formats/superannotate_schemas.py +++ b/darwin/importer/formats/superannotate_schemas.py @@ -1,11 +1,33 @@ +attributes = { + "type": "array", + "items": { + "type": "object", + "properties": {"id": {"type": "integer"}, "groupId": {"type": "integer"}}, + "required": ["id", "groupId"], + }, +} + bbox = { "$id": "https://darwin.v7labs.com/schemas/supperannotate/bounding_box", "description": "Schema of a Bounding Box", "title": "Bounding Box", - "default": {"type": "bbox", "points": {"x1": 1223.1, "x2": 1420.2, "y1": 607.3, "y2": 1440,}, "classId": 1,}, - "examples": [{"type": "bbox", "points": {"x1": 587.5, "x2": 1420.2, "y1": 607.3, "y2": 1440,}, "classId": 1,}], + "default": { + "type": "bbox", + "points": {"x1": 1223.1, "x2": 1420.2, "y1": 607.3, "y2": 1440,}, + "classId": 1, + "attributes": [], + }, + "examples": [ + { + "type": "bbox", + "points": {"x1": 587.5, "x2": 1420.2, "y1": 607.3, "y2": 1440,}, + "classId": 1, + "attributes": [{"id": 1, "groupId": 2}], + } + ], "type": "object", "properties": { + "attributes": attributes, "classId": {"type": "integer"}, "type": {"enum": ["bbox"]}, "points": { @@ -19,7 +41,7 @@ "required": ["x1", "x2", "y1", "y2"], }, }, - "required": ["points", "type", "classId"], + "required": ["points", "type", "classId", "attributes"], } polygon = { @@ -28,16 +50,17 @@ "title": "Polygon", "default": {"type": "polygon", "points": [1, 2, 3, 4], "classId": 1}, "examples": [ - {"type": "polygon", "points": [1, 2, 3, 4], "classId": 1}, - {"type": "polygon", "points": [], "classId": 1}, + {"type": "polygon", "points": [1, 2, 3, 4], "classId": 1, "attributes": [{"id": 1, "groupId": 2}]}, + {"type": "polygon", "points": [], "classId": 1, "attributes": []}, ], "type": "object", "properties": { + "attributes": attributes, "classId": {"type": "integer"}, "points": {"type": "array", "items": {"type": "number"}}, "type": {"enum": ["polygon"]}, }, - "required": ["points", "type", "classId"], + "required": ["points", "type", "classId", "attributes"], } polyline = { @@ -46,16 +69,17 @@ "title": "Polyline", "default": {"type": "polyline", "points": [1, 2, 3, 4], "classId": 1}, "examples": [ - {"type": "polyline", "points": [1, 2, 3, 4], "classId": 1}, - {"type": "polyline", "points": [], "classId": 1}, + {"type": "polyline", "points": [1, 2, 3, 4], "classId": 1, "attributes": [{"id": 1, "groupId": 2}]}, + {"type": "polyline", "points": [], "classId": 1, "attributes": []}, ], "type": "object", "properties": { + "attributes": attributes, "classId": {"type": "integer"}, "points": {"type": "array", "items": {"type": "number"}}, "type": {"enum": ["polyline"]}, }, - "required": ["points", "type", "classId"], + "required": ["points", "type", "classId", "attributes"], } cuboid = { @@ -71,6 +95,7 @@ "r2": {"x": 1603.4, "y": 1440}, }, "classId": 1, + "attributes": [{"id": 1, "groupId": 2}], }, "examples": [ { @@ -82,10 +107,12 @@ "r2": {"x": 1603.4, "y": 1440}, }, "classId": 1, + "attributes": [], } ], "type": "object", "properties": { + "attributes": attributes, "classId": {"type": "integer"}, "type": {"enum": ["cuboid"]}, "points": { @@ -115,19 +142,38 @@ "required": ["f1", "f2", "r1", "r2"], }, }, - "required": ["points", "type", "classId"], + "required": ["points", "type", "classId", "attributes"], } ellipse = { "$id": "https://darwin.v7labs.com/schemas/supperannotate/ellipse", "description": "Schema of an Ellipse", "title": "Ellipse", - "default": {"type": "ellipse", "cx": 377.46, "cy": 806.18, "rx": 316.36, "ry": 134.18, "angle": 0, "classId": 1}, + "default": { + "type": "ellipse", + "cx": 377.46, + "cy": 806.18, + "rx": 316.36, + "ry": 134.18, + "angle": 0, + "classId": 1, + "attributes": [], + }, "examples": [ - {"type": "ellipse", "cx": 377.46, "cy": 806.18, "rx": 316.36, "ry": 134.18, "angle": 14.66, "classId": 1} + { + "type": "ellipse", + "cx": 377.46, + "cy": 806.18, + "rx": 316.36, + "ry": 134.18, + "angle": 14.66, + "classId": 1, + "attributes": [{"id": 1, "groupId": 2}], + } ], "type": "object", "properties": { + "attributes": attributes, "classId": {"type": "integer"}, "cx": {"type": "number"}, "cy": {"type": "number"}, @@ -136,23 +182,27 @@ "angle": {"type": "number"}, "type": {"enum": ["ellipse"]}, }, - "required": ["cx", "cy", "rx", "ry", "angle", "type", "classId"], + "required": ["cx", "cy", "rx", "ry", "angle", "type", "classId", "attributes"], } point = { "$id": "https://darwin.v7labs.com/schemas/supperannotate/point", "description": "Schema of a Point", "title": "Point", - "default": {"type": "point", "x": 1.2, "y": 2.5, "classId": 1}, - "examples": [{"type": "point", "x": 1.2, "y": 2.5, "classId": 1}, {"type": "point", "x": 0, "y": 1, "classId": 2}], + "default": {"type": "point", "x": 1.2, "y": 2.5, "classId": 1, "attributes": []}, + "examples": [ + {"type": "point", "x": 1.2, "y": 2.5, "classId": 1, "attributes": []}, + {"type": "point", "x": 0, "y": 1, "classId": 2, "attributes": [{"id": 1, "groupId": 2}]}, + ], "type": "object", "properties": { + "attributes": attributes, "classId": {"type": "integer"}, "x": {"type": "number"}, "y": {"type": "number"}, "type": {"enum": ["point"]}, }, - "required": ["x", "y", "type", "classId"], + "required": ["x", "y", "type", "classId", "attributes"], } diff --git a/tests/darwin/importer/formats/import_superannotate_test.py b/tests/darwin/importer/formats/import_superannotate_test.py index 0863dda8b..2413689d0 100644 --- a/tests/darwin/importer/formats/import_superannotate_test.py +++ b/tests/darwin/importer/formats/import_superannotate_test.py @@ -195,16 +195,9 @@ def it_imports_point_vectors(annotations_file_path: Path, classes_file_path: Pat annotations_json: str = """ { "instances": [ - { - "type": "point", - "x": 1.93, - "y": 0.233, - "classId": 1 - } + {"type": "point", "x": 1.93, "y": 0.233, "classId": 1, "attributes": []} ], - "metadata": { - "name": "demo-image-0.jpg" - } + "metadata": {"name": "demo-image-0.jpg"} } """ classes_json: str = """ @@ -269,7 +262,8 @@ def it_imports_ellipse_vectors(annotations_file_path: Path, classes_file_path: P "cy": 475.8, "rx": 205.4, "ry": 275.7, - "angle": 0 + "angle": 0, + "attributes": [] } ], "metadata": { @@ -346,6 +340,7 @@ def it_imports_cuboid_vectors(annotations_file_path: Path, classes_file_path: Pa { "instances": [ { + "attributes": [], "type": "cuboid", "classId": 1, "points": { @@ -432,6 +427,7 @@ def it_imports_polygon_vectors(annotations_file_path: Path, classes_file_path: P { "instances": [ { + "attributes": [], "type": "polygon", "classId": 1, "points": [ @@ -504,6 +500,7 @@ def it_imports_polyline_vectors(annotations_file_path: Path, classes_file_path: { "instances": [ { + "attributes": [], "type": "polyline", "classId": 1, "points": [ @@ -583,12 +580,8 @@ def it_imports_bbox_vectors(annotations_file_path: Path, classes_file_path: Path { "type": "bbox", "classId": 1, - "points": { - "x1": 1642.9, - "x2": 1920, - "y1": 516.5, - "y2": 734 - } + "points": {"x1": 1642.9, "x2": 1920, "y1": 516.5, "y2": 734}, + "attributes": [] } ], "metadata": { @@ -620,6 +613,87 @@ def it_imports_bbox_vectors(annotations_file_path: Path, classes_file_path: Path annotation_class = bbox_annotation.annotation_class assert_annotation_class(annotation_class, "Person-bbox", "bounding_box") + def it_raises_if_an_attributes_is_missing(annotations_file_path: Path, classes_file_path: Path): + + annotations_json: str = """ + { + "instances": [ + { + "type": "bbox", + "classId": 1, + "points": {"x1": 1642.9, "x2": 1920, "y1": 516.5, "y2": 734} + } + ], + "metadata": {"name": "demo-image-0.jpg"} + } + """ + classes_json: str = """ + [ + { + "attribute_groups": [ + { + "id": 1, + "name": "Sex", + "attributes": [ + {"id": 1, "name": "Male"} + ] + } + ], + "id": 1, + "name": "Person" + } + ] + """ + + annotations_file_path.write_text(annotations_json) + classes_file_path.write_text(classes_json) + + with pytest.raises(ValidationError) as error: + parse_path(annotations_file_path) + + assert "'bbox' is not one of ['point']" in str(error.value) + + def it_raises_if_an_attribute_from_a_group_is_missing(annotations_file_path: Path, classes_file_path: Path): + + annotations_json: str = """ + { + "instances": [ + { + "type": "bbox", + "classId": 1, + "points": {"x1": 1642.9, "x2": 1920, "y1": 516.5, "y2": 734}, + "attributes": [{"id": 2, "groupId": 1}] + } + ], + "metadata": {"name": "demo-image-0.jpg"} + } + """ + classes_json: str = """ + [ + { + "attribute_groups": [ + { + "id": 1, + "name": "Sex", + "attributes": [ + {"id": 1, "name": "Male"} + ] + } + ], + "id": 1, + "name": "Person" + } + ] + """ + + annotations_file_path.write_text(annotations_json) + classes_file_path.write_text(classes_json) + + with pytest.raises(ValueError) as error: + parse_path(annotations_file_path) + + assert "No attribute data found for {'id': 2, 'groupId': 1}." in str(error.value) + def it_imports_attributes(annotations_file_path: Path, classes_file_path: Path): annotations_json: str = """ @@ -679,9 +753,9 @@ def it_imports_attributes(annotations_file_path: Path, classes_file_path: Path): assert_bbox(bbox_annotation, 1642.9, 516.5, 217.5, 277.1) annotation_class = bbox_annotation.annotation_class - assert_annotation_class(annotation_class, "Person", "bounding_box") + assert_annotation_class(annotation_class, "Person-bbox", "bounding_box") - assert_subannotations(bbox_annotation.subs, [SubAnnotation("attributes", ["Sex-Female", "Emotion-Smiling"])]) + assert_subannotations(bbox_annotation.subs, [SubAnnotation("attributes", ["Sex:Female", "Emotion:Smiling"])]) def assert_cuboid(annotation: Annotation, cuboid: CuboidData) -> None: From d762072c8d5937c4aec148c8f5954e686e362785 Mon Sep 17 00:00:00 2001 From: Pedro Date: Wed, 29 Dec 2021 18:36:41 +0100 Subject: [PATCH 4/8] added attribute import to other types --- darwin/importer/formats/superannotate.py | 32 ++++++++++++++++++++---- darwin/importer/importer.py | 4 +++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/darwin/importer/formats/superannotate.py b/darwin/importer/formats/superannotate.py index b57f30f3d..7612754a6 100644 --- a/darwin/importer/formats/superannotate.py +++ b/darwin/importer/formats/superannotate.py @@ -162,7 +162,11 @@ def _to_keypoint_annotation(point: Dict[str, Any], classes: List[Dict[str, Any]] instance_class: Dict[str, Any] = _find_class(class_id, classes) name: str = str(instance_class.get("name")) - return make_keypoint(f"{name}-point", x, y) + attributes: Optional[SubAnnotation] = _get_attributes(point, instance_class) + subannotations: Optional[List[SubAnnotation]] = None + if attributes: + subannotations = [attributes] + return make_keypoint(f"{name}-point", x, y, subannotations) def _to_bbox_annotation(bbox: Dict[str, Any], classes: List[Dict[str, Any]]) -> Annotation: @@ -192,7 +196,12 @@ def _to_ellipse_annotation(ellipse: Dict[str, Any], classes: List[Dict[str, Any] instance_class: Dict[str, Any] = _find_class(class_id, classes) name: str = str(instance_class.get("name")) - return make_ellipse(f"{name}-ellipse", ellipse_data) + attributes: Optional[SubAnnotation] = _get_attributes(ellipse, instance_class) + subannotations: Optional[List[SubAnnotation]] = None + if attributes: + subannotations = [attributes] + + return make_ellipse(f"{name}-ellipse", ellipse_data, subannotations) def _to_cuboid_annotation(cuboid: Dict[str, Any], classes: List[Dict[str, Any]]) -> Annotation: @@ -220,7 +229,12 @@ def _to_cuboid_annotation(cuboid: Dict[str, Any], classes: List[Dict[str, Any]]) instance_class: Dict[str, Any] = _find_class(class_id, classes) name: str = str(instance_class.get("name")) - return make_cuboid(f"{name}-cuboid", cuboid_data) + attributes: Optional[SubAnnotation] = _get_attributes(cuboid, instance_class) + subannotations: Optional[List[SubAnnotation]] = None + if attributes: + subannotations = [attributes] + + return make_cuboid(f"{name}-cuboid", cuboid_data, subannotations) def _to_polygon_annotation(polygon: Dict[str, Any], classes: List[Dict[str, Any]]) -> Annotation: @@ -230,7 +244,11 @@ def _to_polygon_annotation(polygon: Dict[str, Any], classes: List[Dict[str, Any] name: str = str(instance_class.get("name")) points: List[Point] = _map_to_list(_tuple_to_point, _group_to_list(data, 2, 0)) - return make_polygon(f"{name}-polygon", points) + attributes: Optional[SubAnnotation] = _get_attributes(polygon, instance_class) + subannotations: Optional[List[SubAnnotation]] = None + if attributes: + subannotations = [attributes] + return make_polygon(f"{name}-polygon", points, None, subannotations) def _to_line_annotation(line: Dict[str, Any], classes: List[Dict[str, Any]]) -> Annotation: @@ -239,8 +257,12 @@ def _to_line_annotation(line: Dict[str, Any], classes: List[Dict[str, Any]]) -> instance_class: Dict[str, Any] = _find_class(class_id, classes) name: str = str(instance_class.get("name")) points: List[Point] = _map_to_list(_tuple_to_point, _group_to_list(data, 2, 0)) + attributes: Optional[SubAnnotation] = _get_attributes(line, instance_class) + subannotations: Optional[List[SubAnnotation]] = None + if attributes: + subannotations = [attributes] - return make_line(f"{name}-polyline", points) + return make_line(f"{name}-polyline", points, subannotations) def _find_class(class_id: int, classes: List[Dict[str, Any]]) -> Dict[str, Any]: diff --git a/darwin/importer/importer.py b/darwin/importer/importer.py index 422bba8fb..76797c499 100644 --- a/darwin/importer/importer.py +++ b/darwin/importer/importer.py @@ -284,6 +284,10 @@ def _handle_subs( data["instance_id"] = {"value": sub.data} else: data[sub.annotation_type] = sub.data + + difference = list(set(sub.data) - set(data["attributes"])) + print(f"The following attributes were not imported: {difference}") + return data From c037ce8e766256879d9eaa1e9b911327dcbada8c Mon Sep 17 00:00:00 2001 From: Pedro Date: Wed, 29 Dec 2021 19:17:39 +0100 Subject: [PATCH 5/8] improving attribute not imported message --- darwin/importer/importer.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/darwin/importer/importer.py b/darwin/importer/importer.py index 76797c499..cc0a57f8b 100644 --- a/darwin/importer/importer.py +++ b/darwin/importer/importer.py @@ -273,21 +273,19 @@ def _handle_subs( if sub.annotation_type == "text": data["text"] = {"text": sub.data} elif sub.annotation_type == "attributes": - data["attributes"] = { - "attributes": [ - attributes[annotation_class_id][attr] - for attr in sub.data - if annotation_class_id in attributes and attr in attributes[annotation_class_id] - ] - } + attributes_with_key = [] + for attr in sub.data: + if annotation_class_id in attributes and attr in attributes[annotation_class_id]: + attributes_with_key.append(attributes[annotation_class_id][attr]) + else: + print(f"The attribute '{attr}' for class '{annotation.annotation_class.name}' was not imported.") + + data["attributes"] = {"attributes": attributes_with_key} elif sub.annotation_type == "instance_id": data["instance_id"] = {"value": sub.data} else: data[sub.annotation_type] = sub.data - difference = list(set(sub.data) - set(data["attributes"])) - print(f"The following attributes were not imported: {difference}") - return data From 8455b1b00c8ba8f37a0e1f32fab84fcefa4ebe9c Mon Sep 17 00:00:00 2001 From: Pedro Date: Wed, 29 Dec 2021 19:30:21 +0100 Subject: [PATCH 6/8] Added Typing to file --- darwin/importer/formats/superannotate.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/darwin/importer/formats/superannotate.py b/darwin/importer/formats/superannotate.py index 7612754a6..e0af806a2 100644 --- a/darwin/importer/formats/superannotate.py +++ b/darwin/importer/formats/superannotate.py @@ -26,6 +26,8 @@ superannotate_export, ) +AttributeGroup = Dict[str, Union[str, int]] + def parse_path(path: Path) -> Optional[AnnotationFile]: """ @@ -289,17 +291,15 @@ def _get_attributes(instance: Dict[str, Any], instance_class: Dict[str, Any]) -> group_id: int = cast(int, group.get("id")) if info_group_id == group_id: - group_attributes: List[Dict[str, Union[str, int]]] = cast( - List[Dict[str, Union[str, int]]], group.get("attributes") - ) - attribute: Optional[Dict[str, Union[str, int]]] = next( + group_attributes: List[AttributeGroup] = cast(List[AttributeGroup], group.get("attributes")) + attribute: Optional[AttributeGroup] = next( (attribute for attribute in group_attributes if attribute.get("id") == attribute_id), None ) if attribute is None: raise ValueError(f"No attribute data found for {info}.") - final_attribute = f"{str(group.get('name'))}:{str(attribute.get('name'))}" + final_attribute: str = f"{str(group.get('name'))}:{str(attribute.get('name'))}" all_attributes.append(final_attribute) if all_attributes == []: From ecd5885c2206b4aa5863e606e7a542a7e273b44a Mon Sep 17 00:00:00 2001 From: Pedro Date: Thu, 30 Dec 2021 12:06:57 +0100 Subject: [PATCH 7/8] small readability refactor --- darwin/importer/formats/superannotate.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/darwin/importer/formats/superannotate.py b/darwin/importer/formats/superannotate.py index e0af806a2..cc87b073e 100644 --- a/darwin/importer/formats/superannotate.py +++ b/darwin/importer/formats/superannotate.py @@ -290,17 +290,19 @@ def _get_attributes(instance: Dict[str, Any], instance_class: Dict[str, Any]) -> for group in groups: group_id: int = cast(int, group.get("id")) - if info_group_id == group_id: - group_attributes: List[AttributeGroup] = cast(List[AttributeGroup], group.get("attributes")) - attribute: Optional[AttributeGroup] = next( - (attribute for attribute in group_attributes if attribute.get("id") == attribute_id), None - ) + if info_group_id != group_id: + continue - if attribute is None: - raise ValueError(f"No attribute data found for {info}.") + group_attributes: List[AttributeGroup] = cast(List[AttributeGroup], group.get("attributes")) + attribute: Optional[AttributeGroup] = next( + (attribute for attribute in group_attributes if attribute.get("id") == attribute_id), None + ) - final_attribute: str = f"{str(group.get('name'))}:{str(attribute.get('name'))}" - all_attributes.append(final_attribute) + if attribute is None: + raise ValueError(f"No attribute data found for {info}.") + + final_attribute: str = f"{str(group.get('name'))}:{str(attribute.get('name'))}" + all_attributes.append(final_attribute) if all_attributes == []: return None From 98f90be2cc8e3fdddace803a5cdfc741ab7adaf9 Mon Sep 17 00:00:00 2001 From: Pedro Date: Thu, 30 Dec 2021 12:10:24 +0100 Subject: [PATCH 8/8] linter fixes --- tests/darwin/importer/formats/import_pascalvoc_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/darwin/importer/formats/import_pascalvoc_test.py b/tests/darwin/importer/formats/import_pascalvoc_test.py index 397618bfa..1b352876e 100644 --- a/tests/darwin/importer/formats/import_pascalvoc_test.py +++ b/tests/darwin/importer/formats/import_pascalvoc_test.py @@ -1,6 +1,8 @@ from pathlib import Path +from typing import cast import pytest +from darwin.datatypes import Annotation from darwin.importer.formats.pascal_voc import parse_path @@ -128,7 +130,7 @@ def it_returns_annotation_file_with_correct_annotations_otherwise(annotation_pat assert class_.name == "Class" assert class_.annotation_type == "bounding_box" - annotation = annotation_file.annotations.pop() + annotation: Annotation = cast(Annotation, annotation_file.annotations.pop()) assert annotation.annotation_class == class_ assert annotation.data == {"x": 10, "y": 10, "w": 0, "h": 0} assert annotation.subs == [] @@ -150,7 +152,7 @@ def it_returns_annotation_file_with_correct_annotations_with_float_values(annota assert class_.name == "Class" assert class_.annotation_type == "bounding_box" - annotation = annotation_file.annotations.pop() + annotation: Annotation = cast(Annotation, annotation_file.annotations.pop()) assert annotation.annotation_class == class_ assert annotation.data == {"x": 10, "y": 10, "w": 0, "h": 0} assert annotation.subs == []