From df1219eb4c80245e91255de9803d8625d43838f6 Mon Sep 17 00:00:00 2001 From: John Wilkie <124276291+JBWilkie@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:17:21 +0000 Subject: [PATCH] Fixed unhandled annotation types in 1.0 conversion (#773) --- darwin/exporter/formats/darwin_1_0.py | 3 ++ darwin/importer/importer.py | 38 ++++++++++----- .../formats/export_darwin_1_0_test.py | 47 +++++++++++++++++++ 3 files changed, 75 insertions(+), 13 deletions(-) diff --git a/darwin/exporter/formats/darwin_1_0.py b/darwin/exporter/formats/darwin_1_0.py index 4adc6b3ad..5283bc0fd 100644 --- a/darwin/exporter/formats/darwin_1_0.py +++ b/darwin/exporter/formats/darwin_1_0.py @@ -205,6 +205,9 @@ def _build_legacy_annotation_data( # Poygons and complex polygons usually have attached bounding_box annotations v1_data["bounding_box"] = data["bounding_box"] + if not v1_data: + v1_data[annotation_class.annotation_type] = data + return v1_data diff --git a/darwin/importer/importer.py b/darwin/importer/importer.py index 133c32ead..00f3bd862 100644 --- a/darwin/importer/importer.py +++ b/darwin/importer/importer.py @@ -1032,11 +1032,11 @@ def _get_overwrite_value(append: bool) -> str: def _parse_empty_masks( - annotation: dt.Annotation, - raster_layer: dt.Annotation, - raster_layer_dense_rle_ids: Optional[Set[str]] = None, - raster_layer_dense_rle_ids_frames: Optional[Dict[int, Set[str]]] = None, - ): + annotation: dt.Annotation, + raster_layer: dt.Annotation, + raster_layer_dense_rle_ids: Optional[Set[str]] = None, + raster_layer_dense_rle_ids_frames: Optional[Dict[int, Set[str]]] = None, +): """ Check if the mask is empty (i.e. masks that do not have a corresponding raster layer) if so, skip import of the mask. This function is used for both dt.Annotation and dt.VideoAnnotation objects. @@ -1051,13 +1051,17 @@ def _parse_empty_masks( tuple[Optional[Set[str]], Optional[Dict[int, Set[str]]]]: raster_layer_dense_rle_ids, raster_layer_dense_rle_ids_frames """ # For dt.VideoAnnotation, create dense_rle ids for each frame. - if raster_layer_dense_rle_ids_frames is None and isinstance(annotation, dt.VideoAnnotation): + if raster_layer_dense_rle_ids_frames is None and isinstance( + annotation, dt.VideoAnnotation + ): assert isinstance(raster_layer, dt.VideoAnnotation) # build a dict of frame_index: set of dense_rle_ids (for each frame in VideoAnnotation object) raster_layer_dense_rle_ids_frames = {} for frame_index, _rl in raster_layer.frames.items(): - raster_layer_dense_rle_ids_frames[frame_index] = set(_rl.data["dense_rle"][::2]) + raster_layer_dense_rle_ids_frames[frame_index] = set( + _rl.data["dense_rle"][::2] + ) # check every frame # - if the 'annotation_class_id' is in raster_layer's mask_annotation_ids_mapping dict @@ -1066,22 +1070,26 @@ def _parse_empty_masks( for frame_index, _annotation in annotation.frames.items(): _annotation_id = _annotation.id if ( - frame_index in raster_layer_dense_rle_ids_frames and - raster_layer.frames[frame_index].data["mask_annotation_ids_mapping"][_annotation_id] + frame_index in raster_layer_dense_rle_ids_frames + and raster_layer.frames[frame_index].data[ + "mask_annotation_ids_mapping" + ][_annotation_id] not in raster_layer_dense_rle_ids_frames[frame_index] ): # skip import of the mask, and remove it from mask_annotation_ids_mapping logger.warning( f"Skipping import of mask annotation '{_annotation.annotation_class.name}' as it does not have a corresponding raster layer" ) - del raster_layer.frames[frame_index]["mask_annotation_ids_mapping"][_annotation_id] + del raster_layer.frames[frame_index]["mask_annotation_ids_mapping"][ + _annotation_id + ] return raster_layer_dense_rle_ids, raster_layer_dense_rle_ids_frames # For dt.Annotation, create dense_rle ids. elif raster_layer_dense_rle_ids is None and isinstance(annotation, dt.Annotation): assert isinstance(raster_layer, dt.Annotation) - # build a set of dense_rle_ids (for the Annotation object) + # build a set of dense_rle_ids (for the Annotation object) raster_layer_dense_rle_ids = set(raster_layer.data["dense_rle"][::2]) # check the annotation (i.e. mask) @@ -1102,6 +1110,7 @@ def _parse_empty_masks( return raster_layer_dense_rle_ids, raster_layer_dense_rle_ids_frames + def _import_annotations( client: "Client", # TODO: This is unused, should it be? id: Union[str, int], @@ -1165,11 +1174,14 @@ def _import_annotations( None, ) if raster_layer: - raster_layer_dense_rle_ids, raster_layer_dense_rle_ids_frames = _parse_empty_masks( + ( + raster_layer_dense_rle_ids, + raster_layer_dense_rle_ids_frames, + ) = _parse_empty_masks( annotation, raster_layer, raster_layer_dense_rle_ids, - raster_layer_dense_rle_ids_frames + raster_layer_dense_rle_ids_frames, ) actors: List[dt.DictFreeForm] = [] diff --git a/tests/darwin/exporter/formats/export_darwin_1_0_test.py b/tests/darwin/exporter/formats/export_darwin_1_0_test.py index 306908d84..aa1e39bf6 100644 --- a/tests/darwin/exporter/formats/export_darwin_1_0_test.py +++ b/tests/darwin/exporter/formats/export_darwin_1_0_test.py @@ -532,3 +532,50 @@ def test_tags(self): "annotations": [{"tag": {}, "name": "tag_test", "slot_names": []}], "dataset": "None", } + + def test_other_annotation_types(self): + line_path = [ + {"x": 230.06, "y": 174.04}, + {"x": 226.39, "y": 170.36}, + {"x": 224.61, "y": 166.81}, + ] + + annotation_class = dt.AnnotationClass(annotation_type="line", name="line_class") + + annotation = dt.Annotation( + annotation_class=annotation_class, + data={"path": line_path}, + subs=[], + ) + + annotation_file = dt.AnnotationFile( + path=Path("test.json"), + filename="test.json", + annotation_classes=[annotation_class], + annotations=[annotation], + image_height=1080, + image_width=1920, + image_url="https://darwin.v7labs.com/image.jpg", + ) + + assert _build_json(annotation_file) == { + "image": { + "seq": None, + "width": 1920, + "height": 1080, + "filename": "test.json", + "original_filename": "test.json", + "url": "https://darwin.v7labs.com/image.jpg", + "thumbnail_url": None, + "path": None, + "workview_url": None, + }, + "annotations": [ + { + "line": {"path": line_path}, + "name": "line_class", + "slot_names": [], + } + ], + "dataset": "None", + }