diff --git a/darwin/importer/importer.py b/darwin/importer/importer.py index 88ad5727e..1202fe752 100644 --- a/darwin/importer/importer.py +++ b/darwin/importer/importer.py @@ -876,6 +876,22 @@ def import_annotations( # noqa: C901 slot_errors, slot_warnings, annotation_format, console ) + if annotation_format == "darwin": + dataset.client.load_feature_flags() + + # Check if the flag exists. When the flag is deprecated in the future we will always perform this check + static_instance_id_feature_flag_exists = any( + feature.name == "STATIC_INSTANCE_ID" + for feature in dataset.client.features.get(dataset.team, []) + ) + check_for_multi_instance_id_annotations = ( + static_instance_id_feature_flag_exists + and dataset.client.feature_enabled("STATIC_INSTANCE_ID") + ) or not static_instance_id_feature_flag_exists + + if check_for_multi_instance_id_annotations: + _warn_for_annotations_with_multiple_instance_ids(local_files, console) + console.print( f"{len(local_files) + len(local_files_missing_remotely)} annotation file(s) found.", style="info", @@ -1691,3 +1707,51 @@ def _display_slot_warnings_and_errors( console.print(f"- File: {file}, issues:", style="info") for warning in slot_errors[file]: console.print(f" - {warning}") + + +def _warn_for_annotations_with_multiple_instance_ids( + local_files: List[dt.AnnotationFile], console: Console +) -> None: + """ + Warns the user if any video annotations have multiple unique instance IDs. + + This function checks each video annotation in the provided list of local annotation + files for multiple instance ID values. A warning is printed to the console for each + instance of this occurrence. + + Parameters + ---------- + local_files : List[dt.AnnotationFile] + A list of local annotation files to be checked. + console : Console + The console object used to print warnings and messages. + """ + files_with_multi_instance_id_annotations = {} + files_with_video_annotations = [ + local_file for local_file in local_files if local_file.is_video + ] + for file in files_with_video_annotations: + for annotation in file.annotations: + unique_instance_ids = [] + for frame_idx in annotation.frames: # type: ignore + for subannotation in annotation.frames[frame_idx].subs: # type: ignore + if subannotation.annotation_type == "instance_id": + instance_id = subannotation.data + if instance_id not in unique_instance_ids: + unique_instance_ids.append(instance_id) + + if len(unique_instance_ids) > 1: + if file.path not in files_with_multi_instance_id_annotations: + files_with_multi_instance_id_annotations[file.path] = 1 + else: + files_with_multi_instance_id_annotations[file.path] += 1 + + if files_with_multi_instance_id_annotations: + console.print( + "The following files have annotation(s) with multiple instance ID values. Instance IDs are static, so only the first instance ID of each annotation will be imported:", + style="warning", + ) + for file in files_with_multi_instance_id_annotations: + console.print( + f"- File: {file} has {files_with_multi_instance_id_annotations[file]} annotation(s) with multiple instance IDs" + ) diff --git a/tests/darwin/importer/importer_test.py b/tests/darwin/importer/importer_test.py index 9d03218c5..cb4ca3579 100644 --- a/tests/darwin/importer/importer_test.py +++ b/tests/darwin/importer/importer_test.py @@ -30,6 +30,7 @@ _resolve_annotation_classes, _verify_slot_annotation_alignment, _import_properties, + _warn_for_annotations_with_multiple_instance_ids, ) @@ -1934,3 +1935,126 @@ def test_import_new_annotation_level_properties_with_manifest( ], granularity=PropertyGranularity.annotation, ) + + +def test_no_instance_id_warning_with_no_video_annotations(): + bounding_box_class = dt.AnnotationClass( + name="class1", annotation_type="bounding_box" + ) + local_files = [ + dt.AnnotationFile( + path=Path("file1.json"), + is_video=False, + annotations=[], + filename="file1", + annotation_classes={bounding_box_class}, + ), + dt.AnnotationFile( + path=Path("file2.json"), + is_video=False, + annotations=[], + filename="file2", + annotation_classes={bounding_box_class}, + ), + ] + console = MagicMock() + _warn_for_annotations_with_multiple_instance_ids(local_files, console) + console.print.assert_not_called() + + +def test_warning_with_multiple_files_with_multi_instance_id_annotations(): + bounding_box_class = dt.AnnotationClass( + name="class1", annotation_type="bounding_box" + ) + annotation1 = dt.VideoAnnotation( + annotation_class=bounding_box_class, + frames={ + 0: dt.Annotation( + annotation_class=dt.AnnotationClass( + name="class1", annotation_type="bounding_box" + ), + data={"x": 5, "y": 10, "w": 5, "h": 10}, + subs=[ + dt.SubAnnotation(annotation_type="instance_id", data="1"), + ], + ), + 1: dt.Annotation( + annotation_class=dt.AnnotationClass( + name="class1", annotation_type="bounding_box" + ), + data={"x": 15, "y": 20, "w": 15, "h": 20}, + subs=[ + dt.SubAnnotation(annotation_type="instance_id", data="2"), + ], + ), + 2: dt.Annotation( + annotation_class=dt.AnnotationClass( + name="class1", annotation_type="bounding_box" + ), + data={"x": 25, "y": 30, "w": 25, "h": 30}, + subs=[ + dt.SubAnnotation(annotation_type="instance_id", data="3"), + ], + ), + }, + keyframes={0: True, 1: False, 2: True}, + segments=[[0, 2]], + interpolated=False, + ) + annotation2 = dt.VideoAnnotation( + annotation_class=bounding_box_class, + frames={ + 0: dt.Annotation( + annotation_class=dt.AnnotationClass( + name="class1", annotation_type="bounding_box" + ), + data={"x": 5, "y": 10, "w": 5, "h": 10}, + subs=[ + dt.SubAnnotation(annotation_type="instance_id", data="1"), + ], + ), + 1: dt.Annotation( + annotation_class=dt.AnnotationClass( + name="class1", annotation_type="bounding_box" + ), + data={"x": 15, "y": 20, "w": 15, "h": 20}, + subs=[ + dt.SubAnnotation(annotation_type="instance_id", data="2"), + ], + ), + 2: dt.Annotation( + annotation_class=dt.AnnotationClass( + name="class1", annotation_type="bounding_box" + ), + data={"x": 25, "y": 30, "w": 25, "h": 30}, + subs=[ + dt.SubAnnotation(annotation_type="instance_id", data="3"), + ], + ), + }, + keyframes={0: True, 1: False, 2: True}, + segments=[[0, 2]], + interpolated=False, + ) + local_files = [ + dt.AnnotationFile( + path=Path("file1.json"), + is_video=True, + annotations=[annotation1], + filename="file1", + annotation_classes={bounding_box_class}, + ), + dt.AnnotationFile( + path=Path("file2.json"), + is_video=True, + annotations=[annotation2], + filename="file2", + annotation_classes={bounding_box_class}, + ), + ] + console = MagicMock() + _warn_for_annotations_with_multiple_instance_ids(local_files, console) + console.print.assert_called() + assert ( + console.print.call_count == 3 + ) # One for the warning message, two for the file details