From a7e71d55eb50e705de3f68744611c0f6765fbb31 Mon Sep 17 00:00:00 2001 From: Aleksandar Ivic Date: Thu, 24 Apr 2025 16:45:48 +0200 Subject: [PATCH 01/17] [DAR-6239] Create file for each slot --- darwin/exporter/exporter.py | 29 +++++++++++++++++------------ darwin/exporter/formats/nifti.py | 14 +++++++------- darwin/utils/utils.py | 23 ++++++++++++++++------- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/darwin/exporter/exporter.py b/darwin/exporter/exporter.py index fd9631dd7..0116c5a2c 100644 --- a/darwin/exporter/exporter.py +++ b/darwin/exporter/exporter.py @@ -6,6 +6,7 @@ get_annotation_files_from_dir, parse_darwin_json, split_video_annotation, + load_data_from_file ) @@ -38,17 +39,20 @@ def darwin_to_dt_gen( for f in files: if f.suffix != ".json": continue - data = parse_darwin_json(f, count) - if data: - if data.is_video and split_sequences: - for d in split_video_annotation(data): - d.seq = count - count += 1 - yield d - else: - yield data - count += 1 - + raw_data, version = load_data_from_file(f) + item = raw_data["item"] + slots = len(item.get("slots", [])) # Determine the number of slots + for slot_index in range(slots): # Iterate over each slot + data = parse_darwin_json(f, count, slot_index) + if data: + if data.is_video and split_sequences: + for d in split_video_annotation(data): + d.seq = count + count += 1 + yield d + else: + yield data + count += 1 def export_annotations( exporter: ExportParser, @@ -69,8 +73,9 @@ def export_annotations( Where the parsed files will be placed after the operation is complete. """ print("Converting annotations...") + annotation_files = darwin_to_dt_gen(file_paths, split_sequences=split_sequences) exporter( - darwin_to_dt_gen(file_paths, split_sequences=split_sequences), + annotation_files, Path(output_directory), ) print(f"Converted annotations saved at {output_directory}") diff --git a/darwin/exporter/formats/nifti.py b/darwin/exporter/formats/nifti.py index c6c4e5168..fe06ae449 100644 --- a/darwin/exporter/formats/nifti.py +++ b/darwin/exporter/formats/nifti.py @@ -119,13 +119,13 @@ def export( populate_output_volumes_from_polygons( polygon_annotations, slot_map, output_volumes, legacy=legacy ) - write_output_volume_to_disk( - output_volumes, - image_id=image_id, - output_dir=output_dir, - legacy=legacy, - filename=video_annotation.filename, - ) + write_output_volume_to_disk( + output_volumes, + image_id=image_id, + output_dir=output_dir, + legacy=legacy, + filename=video_annotation.filename, + ) # Need to map raster layers to SeriesInstanceUIDs if mask_present: mask_id_to_classname = { diff --git a/darwin/utils/utils.py b/darwin/utils/utils.py index c098bfda1..9ab7d08e2 100644 --- a/darwin/utils/utils.py +++ b/darwin/utils/utils.py @@ -447,7 +447,7 @@ def load_data_from_file(path: Path) -> Tuple[dict, dt.AnnotationFileVersion]: def parse_darwin_json( - path: Path, count: Optional[int] = None + path: Path, count: Optional[int] = None, slot_index: Optional[int] = None ) -> Optional[dt.AnnotationFile]: """ Parses the given JSON file in v7's darwin proprietary format. Works for images, split frame @@ -479,7 +479,7 @@ def parse_darwin_json( if "annotations" not in data: return None - return _parse_darwin_v2(path, data) + return _parse_darwin_v2(path, data, slot_index) def stream_darwin_json(path: Path) -> PersistentStreamingJSONObject: @@ -560,7 +560,7 @@ def is_stream_list_empty(json_list: PersistentStreamingJSONList) -> bool: return False -def _parse_darwin_v2(path: Path, data: Dict[str, Any]) -> dt.AnnotationFile: +def _parse_darwin_v2(path: Path, data: Dict[str, Any], slot_index: Optional[int]) -> dt.AnnotationFile: item = data["item"] item_source = item.get("source_info", {}) slots: List[dt.Slot] = list( @@ -597,17 +597,26 @@ def _parse_darwin_v2(path: Path, data: Dict[str, Any]) -> dt.AnnotationFile: item_properties=data.get("properties", []), ) else: - slot = slots[0] + if slot_index is not None: + slot = slots[slot_index] + else: + slot = slots[0] + slot_annotations = [ + annotation for annotation in annotations + if hasattr(annotation, "slot_names") and annotation.slot_names and annotation.slot_names[0] == slot.name + ] + slot_annotation_classes = {annotation.annotation_class for annotation in slot_annotations} + annotation_file = dt.AnnotationFile( version=_parse_version(data), path=path, - filename=item["name"], + filename=item["name"] + "-" + slot.name, item_id=item.get("source_info", {}).get("item_id", None), dataset_name=item.get("source_info", {}) .get("dataset", {}) .get("name", None), - annotation_classes=annotation_classes, - annotations=annotations, + annotation_classes=slot_annotation_classes, + annotations=slot_annotations, is_video=slot.frame_urls is not None or slot.frame_manifest is not None, image_width=slot.width, image_height=slot.height, From a02f2c033e8f796b4abb675249b0a9495a3c3d25 Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Mon, 28 Apr 2025 17:11:35 +0200 Subject: [PATCH 02/17] Make per-slot export logic backwards compatible --- darwin/exporter/exporter.py | 6 ++++-- darwin/exporter/formats/nifti.py | 14 +++++++------- darwin/utils/utils.py | 30 +++++++++++++++++++----------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/darwin/exporter/exporter.py b/darwin/exporter/exporter.py index 0116c5a2c..c95d424f4 100644 --- a/darwin/exporter/exporter.py +++ b/darwin/exporter/exporter.py @@ -39,10 +39,12 @@ def darwin_to_dt_gen( for f in files: if f.suffix != ".json": continue + raw_data, version = load_data_from_file(f) item = raw_data["item"] - slots = len(item.get("slots", [])) # Determine the number of slots - for slot_index in range(slots): # Iterate over each slot + slot_count = len(item.get("slots", [])) + + for slot_index in range(slot_count): data = parse_darwin_json(f, count, slot_index) if data: if data.is_video and split_sequences: diff --git a/darwin/exporter/formats/nifti.py b/darwin/exporter/formats/nifti.py index fe06ae449..c6c4e5168 100644 --- a/darwin/exporter/formats/nifti.py +++ b/darwin/exporter/formats/nifti.py @@ -119,13 +119,13 @@ def export( populate_output_volumes_from_polygons( polygon_annotations, slot_map, output_volumes, legacy=legacy ) - write_output_volume_to_disk( - output_volumes, - image_id=image_id, - output_dir=output_dir, - legacy=legacy, - filename=video_annotation.filename, - ) + write_output_volume_to_disk( + output_volumes, + image_id=image_id, + output_dir=output_dir, + legacy=legacy, + filename=video_annotation.filename, + ) # Need to map raster layers to SeriesInstanceUIDs if mask_present: mask_id_to_classname = { diff --git a/darwin/utils/utils.py b/darwin/utils/utils.py index 9ab7d08e2..f0157b5df 100644 --- a/darwin/utils/utils.py +++ b/darwin/utils/utils.py @@ -459,6 +459,8 @@ def parse_darwin_json( Path to the file to parse. count : Optional[int] Optional count parameter. Used only if the 's image sequence is None. + slot_index: Optional[int] + Optional index of the slot to be parsed as a separate AnnotationFile. Returns ------- @@ -597,26 +599,32 @@ def _parse_darwin_v2(path: Path, data: Dict[str, Any], slot_index: Optional[int] item_properties=data.get("properties", []), ) else: - if slot_index is not None: - slot = slots[slot_index] - else: + filename = item["name"] + + if slot_index is None: slot = slots[0] - slot_annotations = [ - annotation for annotation in annotations - if hasattr(annotation, "slot_names") and annotation.slot_names and annotation.slot_names[0] == slot.name - ] - slot_annotation_classes = {annotation.annotation_class for annotation in slot_annotations} + else: + slot = slots[slot_index] + annotations = [ + annotation for annotation in annotations + if hasattr(annotation, "slot_names") and annotation.slot_names and annotation.slot_names[0] == slot.name + ] + annotation_classes = {annotation.annotation_class for annotation in annotations} + + if len(slots) > 1: + filename = item["name"] + "-" + slot.name + slots = [slot] annotation_file = dt.AnnotationFile( version=_parse_version(data), path=path, - filename=item["name"] + "-" + slot.name, + filename=filename, item_id=item.get("source_info", {}).get("item_id", None), dataset_name=item.get("source_info", {}) .get("dataset", {}) .get("name", None), - annotation_classes=slot_annotation_classes, - annotations=slot_annotations, + annotation_classes=annotation_classes, + annotations=annotations, is_video=slot.frame_urls is not None or slot.frame_manifest is not None, image_width=slot.width, image_height=slot.height, From 76dc399fb9aaa1a1914a64f24cb87f6be505f8b7 Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Tue, 29 Apr 2025 11:13:24 +0200 Subject: [PATCH 03/17] Do not modify AnnotationFile if there is only one slot --- darwin/utils/utils.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/darwin/utils/utils.py b/darwin/utils/utils.py index f0157b5df..de4352427 100644 --- a/darwin/utils/utils.py +++ b/darwin/utils/utils.py @@ -601,20 +601,18 @@ def _parse_darwin_v2(path: Path, data: Dict[str, Any], slot_index: Optional[int] else: filename = item["name"] - if slot_index is None: + if slot_index is None or len(slots) == 1: slot = slots[0] else: slot = slots[slot_index] + slots = [slot] + filename = item["name"] + "-" + slot.name annotations = [ annotation for annotation in annotations if hasattr(annotation, "slot_names") and annotation.slot_names and annotation.slot_names[0] == slot.name ] annotation_classes = {annotation.annotation_class for annotation in annotations} - if len(slots) > 1: - filename = item["name"] + "-" + slot.name - slots = [slot] - annotation_file = dt.AnnotationFile( version=_parse_version(data), path=path, From dd3df9cd7d2c41f66e5c431d960974121977a6e3 Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Tue, 29 Apr 2025 14:14:36 +0200 Subject: [PATCH 04/17] black formatting --- darwin/exporter/exporter.py | 3 ++- darwin/utils/utils.py | 15 +++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/darwin/exporter/exporter.py b/darwin/exporter/exporter.py index c95d424f4..d5a0c9447 100644 --- a/darwin/exporter/exporter.py +++ b/darwin/exporter/exporter.py @@ -6,7 +6,7 @@ get_annotation_files_from_dir, parse_darwin_json, split_video_annotation, - load_data_from_file + load_data_from_file, ) @@ -56,6 +56,7 @@ def darwin_to_dt_gen( yield data count += 1 + def export_annotations( exporter: ExportParser, file_paths: List[PathLike], diff --git a/darwin/utils/utils.py b/darwin/utils/utils.py index de4352427..8624e028a 100644 --- a/darwin/utils/utils.py +++ b/darwin/utils/utils.py @@ -562,7 +562,9 @@ def is_stream_list_empty(json_list: PersistentStreamingJSONList) -> bool: return False -def _parse_darwin_v2(path: Path, data: Dict[str, Any], slot_index: Optional[int]) -> dt.AnnotationFile: +def _parse_darwin_v2( + path: Path, data: Dict[str, Any], slot_index: Optional[int] +) -> dt.AnnotationFile: item = data["item"] item_source = item.get("source_info", {}) slots: List[dt.Slot] = list( @@ -608,10 +610,15 @@ def _parse_darwin_v2(path: Path, data: Dict[str, Any], slot_index: Optional[int] slots = [slot] filename = item["name"] + "-" + slot.name annotations = [ - annotation for annotation in annotations - if hasattr(annotation, "slot_names") and annotation.slot_names and annotation.slot_names[0] == slot.name + annotation + for annotation in annotations + if hasattr(annotation, "slot_names") + and annotation.slot_names + and annotation.slot_names[0] == slot.name ] - annotation_classes = {annotation.annotation_class for annotation in annotations} + annotation_classes = { + annotation.annotation_class for annotation in annotations + } annotation_file = dt.AnnotationFile( version=_parse_version(data), From 9c3c35575cd8c17903f09ad30137ee6632f2e0e7 Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Tue, 29 Apr 2025 15:46:45 +0200 Subject: [PATCH 05/17] Add a multislot nifti convert e2e test --- e2e_tests/cli/test_convert.py | 5 + .../nifti-multislot/from/2044737.fat.nii.json | 2998 +++++++++++++++++ ...2044737.fat.nii.gz-0_Reference_sBAT.nii.gz | Bin 0 -> 9720 bytes ...4737.fat.nii.gz-0_test_mask_basic_m.nii.gz | Bin 0 -> 6727 bytes ...2044737.fat.nii.gz-1_Reference_sBAT.nii.gz | Bin 0 -> 9720 bytes ...4737.fat.nii.gz-1_test_mask_basic_m.nii.gz | Bin 0 -> 6727 bytes 6 files changed, 3003 insertions(+) create mode 100644 e2e_tests/data/convert/nifti-multislot/from/2044737.fat.nii.json create mode 100644 e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-0_Reference_sBAT.nii.gz create mode 100644 e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-0_test_mask_basic_m.nii.gz create mode 100644 e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-1_Reference_sBAT.nii.gz create mode 100644 e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-1_test_mask_basic_m.nii.gz diff --git a/e2e_tests/cli/test_convert.py b/e2e_tests/cli/test_convert.py index 634f28951..863b6c0cc 100644 --- a/e2e_tests/cli/test_convert.py +++ b/e2e_tests/cli/test_convert.py @@ -65,6 +65,11 @@ def compare_directories(self, path: Path, expected_path: Path) -> None: data_path / "nifti-no-legacy-scaling/from", data_path / "nifti-no-legacy-scaling/to", ), + ( + "nifti", + data_path / "nifti-multislot/from", + data_path / "nifti-multislot/to", + ), ( "instance_mask", data_path / "instance_mask/from", diff --git a/e2e_tests/data/convert/nifti-multislot/from/2044737.fat.nii.json b/e2e_tests/data/convert/nifti-multislot/from/2044737.fat.nii.json new file mode 100644 index 000000000..6fbe889cb --- /dev/null +++ b/e2e_tests/data/convert/nifti-multislot/from/2044737.fat.nii.json @@ -0,0 +1,2998 @@ +{ + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "2044737.fat.nii.gz", + "path": "/", + "source_info": { + "item_id": "0192eee1-7767-3bcc-1b02-7bd3a435b59d", + "dataset": { + "name": "MED_2D_VIEWER_OFF", + "slug": "med_2d_viewer_off", + "dataset_management_url": "https://darwin.v7labs.com/datasets/1354682/dataset-management" + }, + "team": { + "name": "V7 John", + "slug": "v7-john" + }, + "workview_url": "https://darwin.v7labs.com/workview?dataset=1354682&item=0192eee1-7767-3bcc-1b02-7bd3a435b59d" + }, + "slots": [ + { + "type": "dicom", + "slot_name": "0", + "width": 224, + "height": 174, + "fps": null, + "thumbnail_url": "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/thumbnail", + "source_files": [ + { + "file_name": "2044737.fat.nii.gz", + "url": "https://darwin.v7labs.com/api/v2/teams/v7-john/uploads/e760518f-563b-467a-be4d-e85eee725e45" + } + ], + "frame_count": 17, + "frame_urls": [ + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/0", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/1", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/2", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/3", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/4", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/5", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/6", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/7", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/8", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/9", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/10", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/11", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/12", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/13", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/14", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/15", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/16" + ], + "metadata": { + "shape": [ + 1, + 224, + 174, + 17 + ], + "SeriesInstanceUID": "1.2.826.0.1.3680043.8.498.89599582585125995121795967413768680340", + "affine": "[[2.232142925262451, 0.0, 0.0, -247.7678723335266], [0.0, 2.232142925262451, 0.0, -191.96429443359375], [0.0, 0.0, 3.0, -21.0], [0.0, 0.0, 0.0, 1.0]]", + "colorspace": "RG16", + "original_affine": [ + [ + "-2.232142925262451", + "-0.0", + "0.0", + "250.0" + ], + [ + "-0.0", + "2.232142925262451", + "-0.0", + "-191.96429443359375" + ], + [ + "0.0", + "0.0", + "3.0", + "-21.0" + ], + [ + "0.0", + "0.0", + "0.0", + "1.0" + ] + ], + "pixdim": "(2.232143, 2.232143, 3.0)", + "plane_map": { + "0": "AXIAL" + } + } + }, + { + "type": "dicom", + "slot_name": "1", + "width": 224, + "height": 174, + "fps": null, + "thumbnail_url": "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/thumbnail", + "source_files": [ + { + "file_name": "2044737.fat.nii.gz", + "url": "https://darwin.v7labs.com/api/v2/teams/v7-john/uploads/e760518f-563b-467a-be4d-e85eee725e45" + } + ], + "frame_count": 17, + "frame_urls": [ + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/0", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/1", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/2", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/3", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/4", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/5", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/6", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/7", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/8", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/9", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/10", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/11", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/12", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/13", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/14", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/15", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/8c754a67-b65a-4aad-aff1-2fdcb36a1669/sections/16" + ], + "metadata": { + "shape": [ + 1, + 224, + 174, + 17 + ], + "SeriesInstanceUID": "1.2.826.0.1.3680043.8.498.89599582585125995121795967413768680340", + "affine": "[[2.232142925262451, 0.0, 0.0, -247.7678723335266], [0.0, 2.232142925262451, 0.0, -191.96429443359375], [0.0, 0.0, 3.0, -21.0], [0.0, 0.0, 0.0, 1.0]]", + "colorspace": "RG16", + "original_affine": [ + [ + "-2.232142925262451", + "-0.0", + "0.0", + "250.0" + ], + [ + "-0.0", + "2.232142925262451", + "-0.0", + "-191.96429443359375" + ], + [ + "0.0", + "0.0", + "3.0", + "-21.0" + ], + [ + "0.0", + "0.0", + "0.0", + "1.0" + ] + ], + "pixdim": "(2.232143, 2.232143, 3.0)", + "plane_map": { + "0": "AXIAL" + } + } + } + ] + }, + "annotations": [ + { + "frames": { + "4": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": true, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "5": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "6": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "7": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "8": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "9": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "10": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "11": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "12": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "13": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "14": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "15": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "16": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": true, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + } + }, + "global_sub_types": {}, + "id": "c33371d2-e165-4808-bf16-2f1040dd3cb2", + "interpolate_algorithm": "linear-1.1", + "interpolated": true, + "name": "Reference_sBAT", + "properties": [], + "ranges": [ + [ + 4, + 17 + ] + ], + "slot_names": [ + "0" + ] + }, + { + "frames": { + "4": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": true, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "5": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "6": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "7": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "8": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "9": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "10": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "11": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "12": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "13": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "14": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "15": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "16": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": true, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + } + }, + "global_sub_types": {}, + "id": "4664e9b3-f1c3-4bb4-a1a3-366a5441c1d7", + "interpolate_algorithm": "linear-1.1", + "interpolated": true, + "name": "Reference_sBAT", + "properties": [], + "ranges": [ + [ + 4, + 17 + ] + ], + "slot_names": [ + "0" + ] + }, + { + "frames": { + "9": { + "keyframe": true, + "mask": {} + }, + "10": { + "keyframe": true, + "mask": {} + }, + "11": { + "keyframe": true, + "mask": {} + }, + "12": { + "keyframe": true, + "mask": {} + } + }, + "id": "e950fe0c-4811-4590-a28a-40cb7ea96864", + "interpolate_algorithm": "linear-1.1", + "name": "test_mask_basic", + "only_keyframes": true, + "properties": [], + "ranges": [ + [ + 9, + 13 + ] + ], + "slot_names": [ + "0" + ] + }, + { + "frames": { + "9": { + "keyframe": true, + "raster_layer": { + "dense_rle": [ + 0, + 18868, + 1, + 6, + 0, + 216, + 1, + 9, + 0, + 215, + 1, + 9, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 213, + 1, + 11, + 0, + 212, + 1, + 12, + 0, + 212, + 1, + 12, + 0, + 211, + 1, + 13, + 0, + 211, + 1, + 12, + 0, + 212, + 1, + 11, + 0, + 213, + 1, + 11, + 0, + 213, + 1, + 10, + 0, + 215, + 1, + 9, + 0, + 215, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 16747 + ], + "mask_annotation_ids_mapping": { + "e950fe0c-4811-4590-a28a-40cb7ea96864": 1 + }, + "total_pixels": 38976 + } + }, + "10": { + "keyframe": true, + "raster_layer": { + "dense_rle": [ + 0, + 17786, + 1, + 5, + 0, + 217, + 1, + 9, + 0, + 63, + 1, + 4, + 0, + 148, + 1, + 9, + 0, + 61, + 1, + 8, + 0, + 145, + 1, + 11, + 0, + 60, + 1, + 8, + 0, + 145, + 1, + 11, + 0, + 59, + 1, + 10, + 0, + 144, + 1, + 11, + 0, + 59, + 1, + 10, + 0, + 144, + 1, + 11, + 0, + 59, + 1, + 10, + 0, + 145, + 1, + 9, + 0, + 60, + 1, + 10, + 0, + 145, + 1, + 9, + 0, + 61, + 1, + 8, + 0, + 148, + 1, + 5, + 0, + 63, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 560, + 1, + 4, + 0, + 218, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 215, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 215, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 4390, + 1, + 4, + 0, + 218, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 215, + 1, + 10, + 0, + 213, + 1, + 11, + 0, + 213, + 1, + 11, + 0, + 213, + 1, + 11, + 0, + 213, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 215, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 9439 + ], + "mask_annotation_ids_mapping": { + "e950fe0c-4811-4590-a28a-40cb7ea96864": 1 + }, + "total_pixels": 38976 + } + }, + "11": { + "keyframe": true, + "raster_layer": { + "dense_rle": [ + 0, + 17387, + 1, + 4, + 0, + 193, + 1, + 4, + 0, + 21, + 1, + 8, + 0, + 188, + 1, + 9, + 0, + 19, + 1, + 8, + 0, + 186, + 1, + 11, + 0, + 18, + 1, + 10, + 0, + 185, + 1, + 12, + 0, + 17, + 1, + 10, + 0, + 184, + 1, + 13, + 0, + 17, + 1, + 10, + 0, + 184, + 1, + 13, + 0, + 17, + 1, + 10, + 0, + 184, + 1, + 13, + 0, + 18, + 1, + 8, + 0, + 185, + 1, + 12, + 0, + 19, + 1, + 8, + 0, + 131, + 1, + 4, + 0, + 51, + 1, + 11, + 0, + 21, + 1, + 4, + 0, + 131, + 1, + 8, + 0, + 49, + 1, + 9, + 0, + 158, + 1, + 8, + 0, + 51, + 1, + 4, + 0, + 160, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 215, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 17640 + ], + "mask_annotation_ids_mapping": { + "e950fe0c-4811-4590-a28a-40cb7ea96864": 1 + }, + "total_pixels": 38976 + } + }, + "12": { + "keyframe": true, + "raster_layer": { + "dense_rle": [ + 0, + 17547, + 1, + 4, + 0, + 218, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 60, + 1, + 5, + 0, + 150, + 1, + 10, + 0, + 57, + 1, + 9, + 0, + 148, + 1, + 10, + 0, + 57, + 1, + 9, + 0, + 148, + 1, + 10, + 0, + 56, + 1, + 11, + 0, + 147, + 1, + 10, + 0, + 56, + 1, + 11, + 0, + 148, + 1, + 8, + 0, + 57, + 1, + 11, + 0, + 148, + 1, + 8, + 0, + 57, + 1, + 11, + 0, + 150, + 1, + 4, + 0, + 60, + 1, + 9, + 0, + 215, + 1, + 9, + 0, + 217, + 1, + 5, + 0, + 2076, + 1, + 4, + 0, + 218, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 215, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 215, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 6463, + 1, + 4, + 0, + 218, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 215, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 215, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 6315 + ], + "mask_annotation_ids_mapping": { + "e950fe0c-4811-4590-a28a-40cb7ea96864": 1 + }, + "total_pixels": 38976 + } + } + }, + "id": "505f072e-94e5-46d8-89ef-f36663d82c0e", + "name": "__raster_layer__", + "only_keyframes": true, + "properties": [], + "ranges": [ + [ + 0, + 17 + ] + ], + "slot_names": [ + "0" + ] + }, + { + "frames": { + "4": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": true, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "5": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "6": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "7": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "8": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "9": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "10": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "11": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "12": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "13": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "14": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "15": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + }, + "16": { + "bounding_box": { + "h": 29.754599999999996, + "w": 44.632000000000005, + "x": 70.6022, + "y": 99.6134 + }, + "keyframe": true, + "polygon": { + "paths": [ + [ + { + "x": 94.5353, + "y": 99.6134 + }, + { + "x": 70.6022, + "y": 120.9591 + }, + { + "x": 115.2342, + "y": 129.368 + } + ] + ] + } + } + }, + "global_sub_types": {}, + "id": "c33371d2-e165-4808-bf16-2f1040dd3cb2", + "interpolate_algorithm": "linear-1.1", + "interpolated": true, + "name": "Reference_sBAT", + "properties": [], + "ranges": [ + [ + 4, + 17 + ] + ], + "slot_names": [ + "1" + ] + }, + { + "frames": { + "4": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": true, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "5": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "6": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "7": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "8": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "9": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "10": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "11": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "12": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "13": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "14": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "15": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": false, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + }, + "16": { + "bounding_box": { + "h": 16.170999999999992, + "w": 42.044700000000006, + "x": 128.8178, + "y": 97.3494 + }, + "keyframe": true, + "polygon": { + "paths": [ + [ + { + "x": 167.9517, + "y": 97.3494 + }, + { + "x": 128.8178, + "y": 99.9368 + }, + { + "x": 151.7807, + "y": 113.5204 + }, + { + "x": 170.8625, + "y": 97.6729 + } + ] + ] + } + } + }, + "global_sub_types": {}, + "id": "4664e9b3-f1c3-4bb4-a1a3-366a5441c1d7", + "interpolate_algorithm": "linear-1.1", + "interpolated": true, + "name": "Reference_sBAT", + "properties": [], + "ranges": [ + [ + 4, + 17 + ] + ], + "slot_names": [ + "1" + ] + }, + { + "frames": { + "9": { + "keyframe": true, + "mask": {} + }, + "10": { + "keyframe": true, + "mask": {} + }, + "11": { + "keyframe": true, + "mask": {} + }, + "12": { + "keyframe": true, + "mask": {} + } + }, + "id": "e950fe0c-4811-4590-a28a-40cb7ea96864", + "interpolate_algorithm": "linear-1.1", + "name": "test_mask_basic", + "only_keyframes": true, + "properties": [], + "ranges": [ + [ + 9, + 13 + ] + ], + "slot_names": [ + "1" + ] + }, + { + "frames": { + "9": { + "keyframe": true, + "raster_layer": { + "dense_rle": [ + 0, + 18868, + 1, + 6, + 0, + 216, + 1, + 9, + 0, + 215, + 1, + 9, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 213, + 1, + 11, + 0, + 212, + 1, + 12, + 0, + 212, + 1, + 12, + 0, + 211, + 1, + 13, + 0, + 211, + 1, + 12, + 0, + 212, + 1, + 11, + 0, + 213, + 1, + 11, + 0, + 213, + 1, + 10, + 0, + 215, + 1, + 9, + 0, + 215, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 16747 + ], + "mask_annotation_ids_mapping": { + "e950fe0c-4811-4590-a28a-40cb7ea96864": 1 + }, + "total_pixels": 38976 + } + }, + "10": { + "keyframe": true, + "raster_layer": { + "dense_rle": [ + 0, + 17786, + 1, + 5, + 0, + 217, + 1, + 9, + 0, + 63, + 1, + 4, + 0, + 148, + 1, + 9, + 0, + 61, + 1, + 8, + 0, + 145, + 1, + 11, + 0, + 60, + 1, + 8, + 0, + 145, + 1, + 11, + 0, + 59, + 1, + 10, + 0, + 144, + 1, + 11, + 0, + 59, + 1, + 10, + 0, + 144, + 1, + 11, + 0, + 59, + 1, + 10, + 0, + 145, + 1, + 9, + 0, + 60, + 1, + 10, + 0, + 145, + 1, + 9, + 0, + 61, + 1, + 8, + 0, + 148, + 1, + 5, + 0, + 63, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 560, + 1, + 4, + 0, + 218, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 215, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 215, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 4390, + 1, + 4, + 0, + 218, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 215, + 1, + 10, + 0, + 213, + 1, + 11, + 0, + 213, + 1, + 11, + 0, + 213, + 1, + 11, + 0, + 213, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 215, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 9439 + ], + "mask_annotation_ids_mapping": { + "e950fe0c-4811-4590-a28a-40cb7ea96864": 1 + }, + "total_pixels": 38976 + } + }, + "11": { + "keyframe": true, + "raster_layer": { + "dense_rle": [ + 0, + 17387, + 1, + 4, + 0, + 193, + 1, + 4, + 0, + 21, + 1, + 8, + 0, + 188, + 1, + 9, + 0, + 19, + 1, + 8, + 0, + 186, + 1, + 11, + 0, + 18, + 1, + 10, + 0, + 185, + 1, + 12, + 0, + 17, + 1, + 10, + 0, + 184, + 1, + 13, + 0, + 17, + 1, + 10, + 0, + 184, + 1, + 13, + 0, + 17, + 1, + 10, + 0, + 184, + 1, + 13, + 0, + 18, + 1, + 8, + 0, + 185, + 1, + 12, + 0, + 19, + 1, + 8, + 0, + 131, + 1, + 4, + 0, + 51, + 1, + 11, + 0, + 21, + 1, + 4, + 0, + 131, + 1, + 8, + 0, + 49, + 1, + 9, + 0, + 158, + 1, + 8, + 0, + 51, + 1, + 4, + 0, + 160, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 215, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 17640 + ], + "mask_annotation_ids_mapping": { + "e950fe0c-4811-4590-a28a-40cb7ea96864": 1 + }, + "total_pixels": 38976 + } + }, + "12": { + "keyframe": true, + "raster_layer": { + "dense_rle": [ + 0, + 17547, + 1, + 4, + 0, + 218, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 60, + 1, + 5, + 0, + 150, + 1, + 10, + 0, + 57, + 1, + 9, + 0, + 148, + 1, + 10, + 0, + 57, + 1, + 9, + 0, + 148, + 1, + 10, + 0, + 56, + 1, + 11, + 0, + 147, + 1, + 10, + 0, + 56, + 1, + 11, + 0, + 148, + 1, + 8, + 0, + 57, + 1, + 11, + 0, + 148, + 1, + 8, + 0, + 57, + 1, + 11, + 0, + 150, + 1, + 4, + 0, + 60, + 1, + 9, + 0, + 215, + 1, + 9, + 0, + 217, + 1, + 5, + 0, + 2076, + 1, + 4, + 0, + 218, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 215, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 215, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 6463, + 1, + 4, + 0, + 218, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 215, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 214, + 1, + 10, + 0, + 215, + 1, + 8, + 0, + 216, + 1, + 8, + 0, + 218, + 1, + 4, + 0, + 6315 + ], + "mask_annotation_ids_mapping": { + "e950fe0c-4811-4590-a28a-40cb7ea96864": 1 + }, + "total_pixels": 38976 + } + } + }, + "id": "505f072e-94e5-46d8-89ef-f36663d82c0e", + "name": "__raster_layer__", + "only_keyframes": true, + "properties": [], + "ranges": [ + [ + 0, + 17 + ] + ], + "slot_names": [ + "1" + ] + } + ], + "properties": [] +} diff --git a/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-0_Reference_sBAT.nii.gz b/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-0_Reference_sBAT.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..af3c51339da949e7fb8fccc7ef996f41dc7dfb6d GIT binary patch literal 9720 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 LQ+!^21z+|L0MXy_ literal 0 HcmV?d00001 diff --git a/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-1_Reference_sBAT.nii.gz b/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-1_Reference_sBAT.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..af3c51339da949e7fb8fccc7ef996f41dc7dfb6d GIT binary patch literal 9720 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 LQ+!^21z+|L0MXy_ literal 0 HcmV?d00001 From 94cb4bb24b2c39bad0b151c206cb432312bd4d70 Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Tue, 29 Apr 2025 18:14:28 +0200 Subject: [PATCH 06/17] Add an import e2e test for multislot item with dicom slots --- e2e_tests/cli/test_import.py | 11 + .../multi_slotted_dicom_item.json | 196 ++++++++++++++++++ e2e_tests/helpers.py | 1 + e2e_tests/objects.py | 52 ++++- 4 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 e2e_tests/data/import/multi_slotted_annotations_with_dicom_slots/multi_slotted_dicom_item.json diff --git a/e2e_tests/cli/test_import.py b/e2e_tests/cli/test_import.py index bb6554bff..ab902f87f 100644 --- a/e2e_tests/cli/test_import.py +++ b/e2e_tests/cli/test_import.py @@ -512,6 +512,17 @@ def test_import_annotations_to_multi_slotted_item_with_slots_defined( ) +def test_import_annotations_to_multi_slotted_item_with_dicom_slots( + local_dataset: E2EDataset, config_values: ConfigValues +) -> None: + run_import_test( + local_dataset, + config_values, + item_type="multi_slotted_dicom", + annotations_subdir="multi_slotted_annotations_with_dicom_slots", + ) + + def test_import_annotations_to_multi_channel_item_without_slots_defined( local_dataset: E2EDataset, config_values: ConfigValues ) -> None: diff --git a/e2e_tests/data/import/multi_slotted_annotations_with_dicom_slots/multi_slotted_dicom_item.json b/e2e_tests/data/import/multi_slotted_annotations_with_dicom_slots/multi_slotted_dicom_item.json new file mode 100644 index 000000000..c454410bd --- /dev/null +++ b/e2e_tests/data/import/multi_slotted_annotations_with_dicom_slots/multi_slotted_dicom_item.json @@ -0,0 +1,196 @@ +{ + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "multi_slotted_dicom_item", + "path": "/", + "source_info": { + "item_id": "01923065-fe73-590a-119b-59cf1cd9b0ea", + "dataset": { + "name": "test_dataset_c6d7f523-c837-41ce-9267-c9441258d831", + "slug": "test_dataset_c6d7f523-c837-41ce-9267-c9441258d831", + "dataset_management_url": "https://staging.v7labs.com/datasets/919888/dataset-management" + }, + "team": { + "name": "E2E Testing", + "slug": "e2e-testing" + }, + "workview_url": "https://staging.v7labs.com/workview?dataset=919888&item=01923065-fe73-590a-119b-59cf1cd9b0ea" + }, + "slots": [ + { + "slot_name": "0", + "type": "dicom", + "storage_key": "darwin-py/dicoms/file_1.dcm", + "storage_thumbnail_key": "darwin-py/images/image_3_thumbnail.jpg", + "file_name": "file_1.dcm", + "source_files": [ + { + "file_name": "file_1.dcm", + "storage_key": "darwin-py/dicoms/file_1.dcm", + "url": "https://staging.v7labs.com/api/v2/teams/e2e-testing/uploads/144dc80d-6bec-4885-b5cb-a174f18000e2" + } + ], + "sections": [], + "metadata": { + "handler": null, + "shape": [ + 1, + 512, + 512, + 0 + ], + "SeriesInstanceUID": "1.3.12.2.1107.5.2.41.169564.2020042813301141366601341", + "affine": "[[0.08913741368344952, -0.002451136967734148, 1.1977545891985513, 95.846341859537], [-1.4218570160804405, -0.3123601272972694, 0.07121401006667598, 205.87200175651], [0.31163018838647816, -1.4244863529748326, -0.017676742040419257, 120.40900817655], [0.0, 0.0, 0.0, 1.0]]", + "colorspace": "RG16", + "original_affine": [ + [ + "0.08913741368344952", + "-0.002451136967734148", + "1.1977545891985513", + "95.846341859537" + ], + [ + "-1.4218570160804405", + "-0.3123601272972694", + "0.07121401006667598", + "205.87200175651" + ], + [ + "0.31163018838647816", + "-1.4244863529748326", + "-0.017676742040419257", + "120.40900817655" + ], + [ + "0.0", + "0.0", + "0.0", + "1.0" + ] + ], + "pixdim": "(1.4583334, 1.4583334, 1.1999999)", + "plane_map": { + "0": "AXIAL" + } + } + }, + { + "slot_name": "1", + "type": "dicom", + "storage_key": "darwin-py/dicoms/file_2.dcm", + "storage_thumbnail_key": "darwin-py/images/image_3_thumbnail.jpg", + "file_name": "file_2.dcm", + "source_files": [ + { + "file_name": "file_2.dcm", + "storage_key": "darwin-py/dicoms/file_2.dcm", + "url": "https://staging.v7labs.com/api/v2/teams/e2e-testing/uploads/144dc80d-6bec-4885-b5cb-a174f18000e2" + } + ], + "sections": [], + "metadata": { + "handler": null, + "shape": [ + 1, + 512, + 512, + 0 + ], + "SeriesInstanceUID": "1.3.12.2.1107.5.2.41.169564.2020042813301141366601341", + "affine": "[[0.08913741368344952, -0.002451136967734148, 1.1977545891985513, 95.846341859537], [-1.4218570160804405, -0.3123601272972694, 0.07121401006667598, 205.87200175651], [0.31163018838647816, -1.4244863529748326, -0.017676742040419257, 120.40900817655], [0.0, 0.0, 0.0, 1.0]]", + "colorspace": "RG16", + "original_affine": [ + [ + "0.08913741368344952", + "-0.002451136967734148", + "1.1977545891985513", + "95.846341859537" + ], + [ + "-1.4218570160804405", + "-0.3123601272972694", + "0.07121401006667598", + "205.87200175651" + ], + [ + "0.31163018838647816", + "-1.4244863529748326", + "-0.017676742040419257", + "120.40900817655" + ], + [ + "0.0", + "0.0", + "0.0", + "1.0" + ] + ], + "pixdim": "(1.4583334, 1.4583334, 1.1999999)", + "plane_map": { + "0": "AXIAL" + } + } + } + ] + }, + "annotations": [ + { + "frames": { + "0": { + "keyframe": true, + "mask": {} + } + }, + "id": "e950fe0c-4811-4590-a28a-40cb7ea96864", + "interpolate_algorithm": "linear-1.1", + "name": "test_mask_basic", + "only_keyframes": true, + "properties": [], + "ranges": [ + [ + 0, + 1 + ] + ], + "slot_names": [ + "0" + ] + }, + { + "frames": { + "0": { + "keyframe": true, + "raster_layer": { + "dense_rle": [ + 0, + 5, + 1, + 5, + 0, + 5 + ], + "mask_annotation_ids_mapping": { + "e950fe0c-4811-4590-a28a-40cb7ea96864": 1 + }, + "total_pixels": 15 + } + } + }, + "id": "505f072e-94e5-46d8-89ef-f36663d82c0e", + "name": "__raster_layer__", + "only_keyframes": true, + "properties": [], + "ranges": [ + [ + 0, + 1 + ] + ], + "slot_names": [ + "0" + ] + } + ], + "properties": [] +} diff --git a/e2e_tests/helpers.py b/e2e_tests/helpers.py index ea68fef21..ee42d5550 100644 --- a/e2e_tests/helpers.py +++ b/e2e_tests/helpers.py @@ -229,6 +229,7 @@ def export_release( "Authorization": f"ApiKey {api_key}", } response = requests.post(create_export_url, json=payload, headers=headers) + response.raise_for_status() list_export_url = ( f"{base_url}/api/v2/teams/{team_slug}/datasets/{dataset_slug}/exports" ) diff --git a/e2e_tests/objects.py b/e2e_tests/objects.py index d0639be89..ab405af60 100644 --- a/e2e_tests/objects.py +++ b/e2e_tests/objects.py @@ -1,11 +1,12 @@ +import json from collections import namedtuple from dataclasses import dataclass -from typing import List, Literal, Optional, Tuple, Dict +from typing import Dict, List, Literal, Optional, Tuple from uuid import UUID -from darwin.datatypes import JSONType import requests -import json + +from darwin.datatypes import JSONType ConfigValues = namedtuple("ConfigValues", ["server", "api_key", "team_slug"]) @@ -421,6 +422,51 @@ def get_read_only_registration_payload( ], } ], + "multi_slotted_dicom": [ + { + "path": path or "/", + "layout": { + "slots": ["0", "1"], + "type": "horizontal", + "version": 1, + }, + "slots": [ + { + "type": "dicom", + "file_name": "file_1.dcm", + "storage_key": "darwin-py/dicoms/file_1.dcm", + "storage_thumbnail_key": "darwin-py/images/image_3_thumbnail.jpg", + "sections": [ + { + "section_index": 1, + "height": 1080, + "width": 1920, + "storage_hq_key": "darwin-py/videos/hq_frames/image_1_hq.jpg", + "storage_lq_key": "darwin-py/videos/hq_frames/image_1_lq.jpg", + }, + ], + "slot_name": "0", + }, + { + "type": "dicom", + "file_name": "file_2.dcm", + "storage_key": "darwin-py/dicoms/file_2.dcm", + "storage_thumbnail_key": "darwin-py/images/image_3_thumbnail.jpg", + "sections": [ + { + "section_index": 1, + "height": 1080, + "width": 1920, + "storage_hq_key": "darwin-py/videos/hq_frames/image_1_hq.jpg", + "storage_lq_key": "darwin-py/videos/hq_frames/image_1_lq.jpg", + }, + ], + "slot_name": "1", + }, + ], + "name": "multi_slotted_dicom_item", + }, + ], } return { "items": items[item_type], # type: ignore From 0e1b3e57a9c55c6bc5cb1638f4908787bd845f2b Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Tue, 29 Apr 2025 18:21:48 +0200 Subject: [PATCH 07/17] Revert change to exporter.py --- darwin/exporter/exporter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/darwin/exporter/exporter.py b/darwin/exporter/exporter.py index d5a0c9447..75209401b 100644 --- a/darwin/exporter/exporter.py +++ b/darwin/exporter/exporter.py @@ -76,9 +76,8 @@ def export_annotations( Where the parsed files will be placed after the operation is complete. """ print("Converting annotations...") - annotation_files = darwin_to_dt_gen(file_paths, split_sequences=split_sequences) exporter( - annotation_files, + darwin_to_dt_gen(file_paths, split_sequences=split_sequences), Path(output_directory), ) print(f"Converted annotations saved at {output_directory}") From 1780e013257e0a56e5b2b5b648ca83af962fc247 Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Fri, 2 May 2025 08:53:48 +0200 Subject: [PATCH 08/17] Revert adding raise_for_status --- e2e_tests/helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e_tests/helpers.py b/e2e_tests/helpers.py index ee42d5550..ea68fef21 100644 --- a/e2e_tests/helpers.py +++ b/e2e_tests/helpers.py @@ -229,7 +229,6 @@ def export_release( "Authorization": f"ApiKey {api_key}", } response = requests.post(create_export_url, json=payload, headers=headers) - response.raise_for_status() list_export_url = ( f"{base_url}/api/v2/teams/{team_slug}/datasets/{dataset_slug}/exports" ) From 6c930521e434e09b8fc32f64b3507eb0a2d865b3 Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Fri, 2 May 2025 14:07:40 +0200 Subject: [PATCH 09/17] Unify export logic of convert and dataset convert commands --- darwin/cli_functions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/darwin/cli_functions.py b/darwin/cli_functions.py index 74d77e550..61c34d6a8 100644 --- a/darwin/cli_functions.py +++ b/darwin/cli_functions.py @@ -1243,7 +1243,9 @@ def dataset_convert( output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) - export_annotations(parser, [annotations_path], output_dir) + export_annotations( + parser, [annotations_path], output_dir, split_sequences=(format != "nifti") + ) except ExporterNotFoundError: _error( f"Unsupported export format: {format}, currently supported: {export_formats}" From bcfeae8542f9a1a48b030ff1766c3bae46e36abe Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Fri, 2 May 2025 16:53:19 +0200 Subject: [PATCH 10/17] Save export nifti volumes to item_name/slot_name/file_name dir structure --- darwin/exporter/formats/nifti.py | 22 ++++++++++++++++-- darwin/utils/utils.py | 7 +++--- .../0}/2044737.fat_Reference_sBAT.nii.gz | Bin .../0}/2044737.fat_test_mask_basic_m.nii.gz | Bin .../0}/2044737.fat_Reference_sBAT.nii.gz | Bin .../0}/2044737.fat_test_mask_basic_m.nii.gz | Bin .../1/2044737.fat_Reference_sBAT.nii.gz} | Bin .../1/2044737.fat_test_mask_basic_m.nii.gz} | Bin .../0/2044737.fat_Reference_sBAT.nii.gz} | Bin .../0/2044737.fat_test_mask_basic_m.nii.gz} | Bin .../exporter/formats/export_nifti_test.py | 20 ++++++++++++---- 11 files changed, 38 insertions(+), 11 deletions(-) rename e2e_tests/data/convert/nifti-legacy-scaling/to/{ => 2044737.fat.nii/0}/2044737.fat_Reference_sBAT.nii.gz (100%) rename e2e_tests/data/convert/nifti-legacy-scaling/to/{ => 2044737.fat.nii/0}/2044737.fat_test_mask_basic_m.nii.gz (100%) rename e2e_tests/data/convert/{nifti-no-legacy-scaling/to => nifti-multislot/to/2044737.fat.nii/0}/2044737.fat_Reference_sBAT.nii.gz (100%) rename e2e_tests/data/convert/{nifti-no-legacy-scaling/to => nifti-multislot/to/2044737.fat.nii/0}/2044737.fat_test_mask_basic_m.nii.gz (100%) rename e2e_tests/data/convert/nifti-multislot/to/{2044737.fat.nii.gz-0_Reference_sBAT.nii.gz => 2044737.fat.nii/1/2044737.fat_Reference_sBAT.nii.gz} (100%) rename e2e_tests/data/convert/nifti-multislot/to/{2044737.fat.nii.gz-0_test_mask_basic_m.nii.gz => 2044737.fat.nii/1/2044737.fat_test_mask_basic_m.nii.gz} (100%) rename e2e_tests/data/convert/{nifti-multislot/to/2044737.fat.nii.gz-1_Reference_sBAT.nii.gz => nifti-no-legacy-scaling/to/2044737.fat.nii/0/2044737.fat_Reference_sBAT.nii.gz} (100%) rename e2e_tests/data/convert/{nifti-multislot/to/2044737.fat.nii.gz-1_test_mask_basic_m.nii.gz => nifti-no-legacy-scaling/to/2044737.fat.nii/0/2044737.fat_test_mask_basic_m.nii.gz} (100%) diff --git a/darwin/exporter/formats/nifti.py b/darwin/exporter/formats/nifti.py index c6c4e5168..86928943b 100644 --- a/darwin/exporter/formats/nifti.py +++ b/darwin/exporter/formats/nifti.py @@ -124,6 +124,8 @@ def export( image_id=image_id, output_dir=output_dir, legacy=legacy, + item_name=video_annotation.path.stem, + slot_name=slot_name, filename=video_annotation.filename, ) # Need to map raster layers to SeriesInstanceUIDs @@ -159,6 +161,8 @@ def export( image_id=image_id, output_dir=output_dir, legacy=legacy, + item_name=video_annotation.path.stem, + slot_name=slot_name, filename=video_annotation.filename, ) @@ -501,7 +505,9 @@ def write_output_volume_to_disk( output_volumes: Dict, image_id: str, output_dir: Union[str, Path], + item_name: str, legacy: bool = False, + slot_name: str = "0", filename: str = None, ) -> None: """Writes the given output volumes to disk. @@ -519,6 +525,8 @@ def write_output_volume_to_disk( If ``False``, the exporter will use the new calculation by dividing with pixdims. filename: str The filename of the dataset item + slot_name: str + Name of the item slot the annotation volume belongs to. Returns ------- @@ -543,9 +551,19 @@ def unnest_dict_to_list(d: Dict) -> List: ) img = _get_reoriented_nifti_image(img, volume, legacy, filename) if volume.from_raster_layer: - output_path = Path(output_dir) / f"{image_id}_{volume.class_name}_m.nii.gz" + output_path = ( + Path(output_dir) + / item_name + / slot_name + / f"{Path(Path(filename).stem).stem}_{volume.class_name}_m.nii.gz" + ) else: - output_path = Path(output_dir) / f"{image_id}_{volume.class_name}.nii.gz" + output_path = ( + Path(output_dir) + / item_name + / slot_name + / f"{Path(Path(filename).stem).stem}_{volume.class_name}.nii.gz" + ) if not output_path.parent.exists(): output_path.parent.mkdir(parents=True) nib.save(img=img, filename=output_path) diff --git a/darwin/utils/utils.py b/darwin/utils/utils.py index 8624e028a..c32133aab 100644 --- a/darwin/utils/utils.py +++ b/darwin/utils/utils.py @@ -566,6 +566,7 @@ def _parse_darwin_v2( path: Path, data: Dict[str, Any], slot_index: Optional[int] ) -> dt.AnnotationFile: item = data["item"] + filename = item["name"] item_source = item.get("source_info", {}) slots: List[dt.Slot] = list( filter(None, map(_parse_darwin_slot, item.get("slots", []))) @@ -581,7 +582,7 @@ def _parse_darwin_v2( annotation_file = dt.AnnotationFile( version=_parse_version(data), path=path, - filename=item["name"], + filename=filename, item_id=item.get("source_info", {}).get("item_id", None), dataset_name=item.get("source_info", {}) .get("dataset", {}) @@ -601,14 +602,12 @@ def _parse_darwin_v2( item_properties=data.get("properties", []), ) else: - filename = item["name"] - if slot_index is None or len(slots) == 1: slot = slots[0] else: slot = slots[slot_index] slots = [slot] - filename = item["name"] + "-" + slot.name + filename = slot.source_files[0].file_name annotations = [ annotation for annotation in annotations diff --git a/e2e_tests/data/convert/nifti-legacy-scaling/to/2044737.fat_Reference_sBAT.nii.gz b/e2e_tests/data/convert/nifti-legacy-scaling/to/2044737.fat.nii/0/2044737.fat_Reference_sBAT.nii.gz similarity index 100% rename from e2e_tests/data/convert/nifti-legacy-scaling/to/2044737.fat_Reference_sBAT.nii.gz rename to e2e_tests/data/convert/nifti-legacy-scaling/to/2044737.fat.nii/0/2044737.fat_Reference_sBAT.nii.gz diff --git a/e2e_tests/data/convert/nifti-legacy-scaling/to/2044737.fat_test_mask_basic_m.nii.gz b/e2e_tests/data/convert/nifti-legacy-scaling/to/2044737.fat.nii/0/2044737.fat_test_mask_basic_m.nii.gz similarity index 100% rename from e2e_tests/data/convert/nifti-legacy-scaling/to/2044737.fat_test_mask_basic_m.nii.gz rename to e2e_tests/data/convert/nifti-legacy-scaling/to/2044737.fat.nii/0/2044737.fat_test_mask_basic_m.nii.gz diff --git a/e2e_tests/data/convert/nifti-no-legacy-scaling/to/2044737.fat_Reference_sBAT.nii.gz b/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii/0/2044737.fat_Reference_sBAT.nii.gz similarity index 100% rename from e2e_tests/data/convert/nifti-no-legacy-scaling/to/2044737.fat_Reference_sBAT.nii.gz rename to e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii/0/2044737.fat_Reference_sBAT.nii.gz diff --git a/e2e_tests/data/convert/nifti-no-legacy-scaling/to/2044737.fat_test_mask_basic_m.nii.gz b/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii/0/2044737.fat_test_mask_basic_m.nii.gz similarity index 100% rename from e2e_tests/data/convert/nifti-no-legacy-scaling/to/2044737.fat_test_mask_basic_m.nii.gz rename to e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii/0/2044737.fat_test_mask_basic_m.nii.gz diff --git a/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-0_Reference_sBAT.nii.gz b/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii/1/2044737.fat_Reference_sBAT.nii.gz similarity index 100% rename from e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-0_Reference_sBAT.nii.gz rename to e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii/1/2044737.fat_Reference_sBAT.nii.gz diff --git a/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-0_test_mask_basic_m.nii.gz b/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii/1/2044737.fat_test_mask_basic_m.nii.gz similarity index 100% rename from e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-0_test_mask_basic_m.nii.gz rename to e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii/1/2044737.fat_test_mask_basic_m.nii.gz diff --git a/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-1_Reference_sBAT.nii.gz b/e2e_tests/data/convert/nifti-no-legacy-scaling/to/2044737.fat.nii/0/2044737.fat_Reference_sBAT.nii.gz similarity index 100% rename from e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-1_Reference_sBAT.nii.gz rename to e2e_tests/data/convert/nifti-no-legacy-scaling/to/2044737.fat.nii/0/2044737.fat_Reference_sBAT.nii.gz diff --git a/e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-1_test_mask_basic_m.nii.gz b/e2e_tests/data/convert/nifti-no-legacy-scaling/to/2044737.fat.nii/0/2044737.fat_test_mask_basic_m.nii.gz similarity index 100% rename from e2e_tests/data/convert/nifti-multislot/to/2044737.fat.nii.gz-1_test_mask_basic_m.nii.gz rename to e2e_tests/data/convert/nifti-no-legacy-scaling/to/2044737.fat.nii/0/2044737.fat_test_mask_basic_m.nii.gz diff --git a/tests/darwin/exporter/formats/export_nifti_test.py b/tests/darwin/exporter/formats/export_nifti_test.py index 5641b883a..62a2b567a 100644 --- a/tests/darwin/exporter/formats/export_nifti_test.py +++ b/tests/darwin/exporter/formats/export_nifti_test.py @@ -149,15 +149,25 @@ def test_export_creates_file_for_polygons_and_masks( / "nifti/releases/latest/annotations" ) video_annotation_files = { - "mask_only.json": ["hippocampus_multislot_3_test_hippo_LOIN_m.nii.gz"], + "mask_only.json": [ + Path("mask_only/0/hippocampus_multislot_3_test_hippo_LOIN_m.nii.gz") + ], "polygon_only.json": [ - "hippocampus_multislot_3_test_hippo_create_class_1.nii.gz" + Path( + "polygon_only/0/hippocampus_multislot_3_test_hippo_create_class_1.nii.gz" + ), ], "polygon_and_mask.json": [ - "hippocampus_multislot_3_test_hippo_create_class_1.nii.gz", - "hippocampus_multislot_3_test_hippo_LOIN_m.nii.gz", + Path( + "polygon_and_mask/0/hippocampus_multislot_3_test_hippo_create_class_1.nii.gz" + ), + Path( + "polygon_and_mask/0/hippocampus_multislot_3_test_hippo_LOIN_m.nii.gz" + ), + ], + "empty.json": [ + Path("empty/0/hippocampus_multislot_3_test_hippo_.nii.gz") ], - "empty.json": ["hippocampus_multislot_3_test_hippo_.nii.gz"], } for video_annotation_file in video_annotation_files: video_annotation_filepaths = [annotations_dir / video_annotation_file] From 20292a0f7b531415b8bdae01a8acae479183449d Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Fri, 2 May 2025 17:24:13 +0200 Subject: [PATCH 11/17] Update docstring, add comments --- darwin/exporter/formats/nifti.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/darwin/exporter/formats/nifti.py b/darwin/exporter/formats/nifti.py index 86928943b..bba1e834c 100644 --- a/darwin/exporter/formats/nifti.py +++ b/darwin/exporter/formats/nifti.py @@ -507,7 +507,7 @@ def write_output_volume_to_disk( output_dir: Union[str, Path], item_name: str, legacy: bool = False, - slot_name: str = "0", + slot_name: str = "0", # default slot name is "0" filename: str = None, ) -> None: """Writes the given output volumes to disk. @@ -523,10 +523,12 @@ def write_output_volume_to_disk( legacy : bool, default=False If ``True``, the exporter will use the legacy calculation. If ``False``, the exporter will use the new calculation by dividing with pixdims. - filename: str - The filename of the dataset item - slot_name: str - Name of the item slot the annotation volume belongs to. + item_name : str + Name of the dataset item. + slot_name : str + Name of the dataset item slot the volume belongs to. + filename : str + Name of the file occupying the dataset item slot. Returns ------- @@ -550,19 +552,22 @@ def unnest_dict_to_list(d: Dict) -> List: affine=volume.affine, ) img = _get_reoriented_nifti_image(img, volume, legacy, filename) + filename_stem = Path( + Path(filename).stem + ).stem # We take stem twice to handle ".nii.gz" suffixes if volume.from_raster_layer: output_path = ( Path(output_dir) / item_name / slot_name - / f"{Path(Path(filename).stem).stem}_{volume.class_name}_m.nii.gz" + / f"{filename_stem}_{volume.class_name}_m.nii.gz" ) else: output_path = ( Path(output_dir) / item_name / slot_name - / f"{Path(Path(filename).stem).stem}_{volume.class_name}.nii.gz" + / f"{filename_stem}_{volume.class_name}.nii.gz" ) if not output_path.parent.exists(): output_path.parent.mkdir(parents=True) From 71b1c6e12b6d543dc1a6a674d256e09dd7438abc Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Fri, 2 May 2025 18:52:41 +0200 Subject: [PATCH 12/17] Update e2e import test to import annotations to both dicom slots --- .../multi_slotted_dicom_item.json | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/e2e_tests/data/import/multi_slotted_annotations_with_dicom_slots/multi_slotted_dicom_item.json b/e2e_tests/data/import/multi_slotted_annotations_with_dicom_slots/multi_slotted_dicom_item.json index c454410bd..171c2b2e9 100644 --- a/e2e_tests/data/import/multi_slotted_annotations_with_dicom_slots/multi_slotted_dicom_item.json +++ b/e2e_tests/data/import/multi_slotted_annotations_with_dicom_slots/multi_slotted_dicom_item.json @@ -38,7 +38,7 @@ 1, 512, 512, - 0 + 1 ], "SeriesInstanceUID": "1.3.12.2.1107.5.2.41.169564.2020042813301141366601341", "affine": "[[0.08913741368344952, -0.002451136967734148, 1.1977545891985513, 95.846341859537], [-1.4218570160804405, -0.3123601272972694, 0.07121401006667598, 205.87200175651], [0.31163018838647816, -1.4244863529748326, -0.017676742040419257, 120.40900817655], [0.0, 0.0, 0.0, 1.0]]", @@ -95,7 +95,7 @@ 1, 512, 512, - 0 + 1 ], "SeriesInstanceUID": "1.3.12.2.1107.5.2.41.169564.2020042813301141366601341", "affine": "[[0.08913741368344952, -0.002451136967734148, 1.1977545891985513, 95.846341859537], [-1.4218570160804405, -0.3123601272972694, 0.07121401006667598, 205.87200175651], [0.31163018838647816, -1.4244863529748326, -0.017676742040419257, 120.40900817655], [0.0, 0.0, 0.0, 1.0]]", @@ -190,6 +190,62 @@ "slot_names": [ "0" ] + }, + { + "frames": { + "0": { + "keyframe": true, + "mask": {} + } + }, + "id": "e950fe0c-4811-4590-a28a-40cb7ea96865", + "interpolate_algorithm": "linear-1.1", + "name": "test_mask_basic", + "only_keyframes": true, + "properties": [], + "ranges": [ + [ + 0, + 1 + ] + ], + "slot_names": [ + "1" + ] + }, + { + "frames": { + "0": { + "keyframe": true, + "raster_layer": { + "dense_rle": [ + 0, + 5, + 1, + 5, + 0, + 5 + ], + "mask_annotation_ids_mapping": { + "e950fe0c-4811-4590-a28a-40cb7ea96865": 1 + }, + "total_pixels": 15 + } + } + }, + "id": "505f072e-94e5-46d8-89ef-f36663d82c0f", + "name": "__raster_layer__", + "only_keyframes": true, + "properties": [], + "ranges": [ + [ + 0, + 1 + ] + ], + "slot_names": [ + "1" + ] } ], "properties": [] From 963a95344943ee06843154508bcef42433113964 Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Mon, 5 May 2025 10:16:52 +0200 Subject: [PATCH 13/17] Leave dataset convert command update for a separate ticket and PR --- darwin/cli_functions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/darwin/cli_functions.py b/darwin/cli_functions.py index 61c34d6a8..74d77e550 100644 --- a/darwin/cli_functions.py +++ b/darwin/cli_functions.py @@ -1243,9 +1243,7 @@ def dataset_convert( output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) - export_annotations( - parser, [annotations_path], output_dir, split_sequences=(format != "nifti") - ) + export_annotations(parser, [annotations_path], output_dir) except ExporterNotFoundError: _error( f"Unsupported export format: {format}, currently supported: {export_formats}" From 33ecacb0898ee492cdbb364d70e2189d85dc3abc Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Mon, 5 May 2025 10:27:19 +0200 Subject: [PATCH 14/17] Add a util to get annotations in slot --- darwin/utils/utils.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/darwin/utils/utils.py b/darwin/utils/utils.py index c32133aab..4ab180ac3 100644 --- a/darwin/utils/utils.py +++ b/darwin/utils/utils.py @@ -13,6 +13,7 @@ Iterator, List, Optional, + Sequence, Set, Tuple, Union, @@ -608,13 +609,7 @@ def _parse_darwin_v2( slot = slots[slot_index] slots = [slot] filename = slot.source_files[0].file_name - annotations = [ - annotation - for annotation in annotations - if hasattr(annotation, "slot_names") - and annotation.slot_names - and annotation.slot_names[0] == slot.name - ] + annotations = get_annotations_in_slot(slot.name, annotations) annotation_classes = { annotation.annotation_class for annotation in annotations } @@ -648,6 +643,18 @@ def _parse_darwin_v2( return annotation_file +def get_annotations_in_slot( + slot_name: str, annotations: Sequence[Union[dt.Annotation, dt.VideoAnnotation]] +) -> List[Union[dt.Annotation, dt.VideoAnnotation]]: + return [ + annotation + for annotation in annotations + if hasattr(annotation, "slot_names") + and annotation.slot_names + and annotation.slot_names[0] == slot_name + ] + + def _parse_darwin_slot(data: Dict[str, Any]) -> dt.Slot: source_files_data = data.get("source_files", []) source_files = [ From 96de3d875d2084a67475f1354e166f2308d5111d Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Mon, 5 May 2025 14:49:06 +0200 Subject: [PATCH 15/17] Move slot iteration logic into the nifti exporter --- darwin/exporter/exporter.py | 27 ++--- darwin/exporter/formats/nifti.py | 201 +++++++++++++++---------------- darwin/utils/utils.py | 27 +---- 3 files changed, 116 insertions(+), 139 deletions(-) diff --git a/darwin/exporter/exporter.py b/darwin/exporter/exporter.py index 75209401b..fd9631dd7 100644 --- a/darwin/exporter/exporter.py +++ b/darwin/exporter/exporter.py @@ -6,7 +6,6 @@ get_annotation_files_from_dir, parse_darwin_json, split_video_annotation, - load_data_from_file, ) @@ -39,22 +38,16 @@ def darwin_to_dt_gen( for f in files: if f.suffix != ".json": continue - - raw_data, version = load_data_from_file(f) - item = raw_data["item"] - slot_count = len(item.get("slots", [])) - - for slot_index in range(slot_count): - data = parse_darwin_json(f, count, slot_index) - if data: - if data.is_video and split_sequences: - for d in split_video_annotation(data): - d.seq = count - count += 1 - yield d - else: - yield data - count += 1 + data = parse_darwin_json(f, count) + if data: + if data.is_video and split_sequences: + for d in split_video_annotation(data): + d.seq = count + count += 1 + yield d + else: + yield data + count += 1 def export_annotations( diff --git a/darwin/exporter/formats/nifti.py b/darwin/exporter/formats/nifti.py index bba1e834c..19290e75a 100644 --- a/darwin/exporter/formats/nifti.py +++ b/darwin/exporter/formats/nifti.py @@ -9,6 +9,8 @@ from rich.console import Console from rich.theme import Theme +from darwin.utils.utils import get_annotations_in_slot + def _console_theme() -> Theme: return Theme( @@ -78,97 +80,105 @@ def export( """ video_annotations = list(annotation_files) for video_annotation in video_annotations: - slot_name = video_annotation.slots[0].name - try: - medical_metadata = video_annotation.slots[0].metadata - legacy = not medical_metadata.get("handler") == "MONAI" # type: ignore - plane_map = medical_metadata.get("plane_map", {slot_name: "AXIAL"}) - primary_plane = plane_map.get(slot_name, "AXIAL") - except (KeyError, AttributeError): - legacy = True - primary_plane = "AXIAL" - + slot_map = {slot.name: slot for slot in video_annotation.slots} image_id = check_for_error_and_return_imageid(video_annotation, output_dir) if not isinstance(image_id, str): continue - polygon_class_names = [ - ann.annotation_class.name - for ann in video_annotation.annotations - if ann.annotation_class.annotation_type == "polygon" - ] - # Check if there are any rasters in the annotation, these are created with a _m suffix - # in addition to those created from polygons. - annotation_types = [ - a.annotation_class.annotation_type for a in video_annotation.annotations - ] - mask_present = "raster_layer" in annotation_types and "mask" in annotation_types - output_volumes = build_output_volumes( - video_annotation, - class_names_to_export=polygon_class_names, - from_raster_layer=False, - mask_present=mask_present, - primary_plane=primary_plane, - ) - slot_map = {slot.name: slot for slot in video_annotation.slots} - polygon_annotations = [ - ann - for ann in video_annotation.annotations - if ann.annotation_class.annotation_type == "polygon" - ] - if polygon_annotations: - populate_output_volumes_from_polygons( - polygon_annotations, slot_map, output_volumes, legacy=legacy + + for slot in video_annotation.slots: + slot_name = slot.name + slot_annotations = get_annotations_in_slot( + slot.name, video_annotation.annotations ) - write_output_volume_to_disk( - output_volumes, - image_id=image_id, - output_dir=output_dir, - legacy=legacy, - item_name=video_annotation.path.stem, - slot_name=slot_name, - filename=video_annotation.filename, - ) - # Need to map raster layers to SeriesInstanceUIDs - if mask_present: - mask_id_to_classname = { - ann.id: ann.annotation_class.name - for ann in video_annotation.annotations - if ann.annotation_class.annotation_type == "mask" - } - raster_output_volumes = build_output_volumes( - video_annotation, - class_names_to_export=list(mask_id_to_classname.values()), - from_raster_layer=True, + + try: + medical_metadata = slot.metadata + legacy = not medical_metadata.get("handler") == "MONAI" # type: ignore + plane_map = medical_metadata.get("plane_map", {slot_name: "AXIAL"}) + primary_plane = plane_map.get(slot_name, "AXIAL") + except (KeyError, AttributeError): + legacy = True + primary_plane = "AXIAL" + + polygon_class_names = [ + ann.annotation_class.name + for ann in slot_annotations + if ann.annotation_class.annotation_type == "polygon" + ] + # Check if there are any rasters in the annotation, these are created with a _m suffix + # in addition to those created from polygons. + annotation_types = [ + a.annotation_class.annotation_type for a in slot_annotations + ] + mask_present = ( + "raster_layer" in annotation_types and "mask" in annotation_types + ) + output_volumes = build_output_volumes( + slot, + class_names_to_export=polygon_class_names, + from_raster_layer=False, + mask_present=mask_present, primary_plane=primary_plane, ) - - # This assumes only one raster_layer annotation. If we allow multiple raster layers per annotation file we need to change this. - raster_layer_annotation = [ + polygon_annotations = [ ann - for ann in video_annotation.annotations - if ann.annotation_class.annotation_type == "raster_layer" - ][0] - if raster_layer_annotation: - populate_output_volumes_from_raster_layer( - annotation=raster_layer_annotation, - mask_id_to_classname=mask_id_to_classname, - slot_map=slot_map, - output_volumes=raster_output_volumes, - primary_plane=primary_plane, + for ann in slot_annotations + if ann.annotation_class.annotation_type == "polygon" + ] + if polygon_annotations: + populate_output_volumes_from_polygons( + polygon_annotations, slot_map, output_volumes, legacy=legacy ) write_output_volume_to_disk( - raster_output_volumes, + output_volumes, image_id=image_id, output_dir=output_dir, legacy=legacy, item_name=video_annotation.path.stem, - slot_name=slot_name, - filename=video_annotation.filename, + slot_name=slot.name, + filename=slot.source_files[0].file_name, ) + # Need to map raster layers to SeriesInstanceUIDs + if mask_present: + mask_id_to_classname = { + ann.id: ann.annotation_class.name + for ann in slot_annotations + if ann.annotation_class.annotation_type == "mask" + } + raster_output_volumes = build_output_volumes( + slot, + class_names_to_export=list(mask_id_to_classname.values()), + from_raster_layer=True, + primary_plane=primary_plane, + ) + + # This assumes only one raster_layer annotation. If we allow multiple raster layers per annotation file we need to change this. + raster_layer_annotation = [ + ann + for ann in slot_annotations + if ann.annotation_class.annotation_type == "raster_layer" + ][0] + if raster_layer_annotation: + populate_output_volumes_from_raster_layer( + annotation=raster_layer_annotation, + mask_id_to_classname=mask_id_to_classname, + slot_map=slot_map, + output_volumes=raster_output_volumes, + primary_plane=primary_plane, + ) + write_output_volume_to_disk( + raster_output_volumes, + image_id=image_id, + output_dir=output_dir, + legacy=legacy, + item_name=video_annotation.path.stem, + slot_name=slot.name, + filename=slot.source_files[0].file_name, + ) def build_output_volumes( - video_annotation: dt.AnnotationFile, + slot: dt.Slot, from_raster_layer: bool = False, class_names_to_export: List[str] = None, mask_present: Optional[bool] = False, @@ -198,21 +208,20 @@ def build_output_volumes( # Builds a map of class to integer, if its a polygon we use the class name as is # for the mask annotations we append a suffix _m to ensure backwards compatibility - output_volumes = {} - for slot in video_annotation.slots: - slot_metadata = slot.metadata - assert slot_metadata is not None - series_instance_uid = slot_metadata.get( - "SeriesInstanceUID", "SeriesIntanceUIDNotProvided" - ) - # Builds output volumes per class - volume_dims, pixdims, affine, original_affine = process_metadata(slot.metadata) - if not mask_present and not class_names_to_export: - class_names_to_export = [ - "" - ] # If there are no annotations to export, we still need to create an empty volume - - output_volumes[series_instance_uid] = { + slot_metadata = slot.metadata + assert slot_metadata is not None + series_instance_uid = slot_metadata.get( + "SeriesInstanceUID", "SeriesIntanceUIDNotProvided" + ) + # Builds output volumes per class + volume_dims, pixdims, affine, original_affine = process_metadata(slot.metadata) + if not mask_present and not class_names_to_export: + class_names_to_export = [ + "" + ] # If there are no annotations to export, we still need to create an empty volume + + return { + series_instance_uid: { class_name: Volume( pixel_array=np.zeros(volume_dims), affine=affine, @@ -225,8 +234,8 @@ def build_output_volumes( primary_plane=primary_plane, ) for class_name in class_names_to_export - } - return output_volumes + }, + } def check_for_error_and_return_imageid( @@ -272,17 +281,7 @@ def check_for_error_and_return_imageid( else: image_id = str(filename) - if video_annotation is None: - return create_error_message_json( - "video_annotation not found", output_dir, image_id - ) - if video_annotation is None: - return create_error_message_json( - "video_annotation not found", output_dir, image_id - ) - for slot in video_annotation.slots: - # Pick the first slot to take the metadata from. We assume that all slots have the same metadata. metadata = slot.metadata if metadata is None: return create_error_message_json( diff --git a/darwin/utils/utils.py b/darwin/utils/utils.py index 4ab180ac3..7d0549635 100644 --- a/darwin/utils/utils.py +++ b/darwin/utils/utils.py @@ -448,7 +448,7 @@ def load_data_from_file(path: Path) -> Tuple[dict, dt.AnnotationFileVersion]: def parse_darwin_json( - path: Path, count: Optional[int] = None, slot_index: Optional[int] = None + path: Path, count: Optional[int] = None ) -> Optional[dt.AnnotationFile]: """ Parses the given JSON file in v7's darwin proprietary format. Works for images, split frame @@ -460,8 +460,6 @@ def parse_darwin_json( Path to the file to parse. count : Optional[int] Optional count parameter. Used only if the 's image sequence is None. - slot_index: Optional[int] - Optional index of the slot to be parsed as a separate AnnotationFile. Returns ------- @@ -482,7 +480,7 @@ def parse_darwin_json( if "annotations" not in data: return None - return _parse_darwin_v2(path, data, slot_index) + return _parse_darwin_v2(path, data) def stream_darwin_json(path: Path) -> PersistentStreamingJSONObject: @@ -563,11 +561,8 @@ def is_stream_list_empty(json_list: PersistentStreamingJSONList) -> bool: return False -def _parse_darwin_v2( - path: Path, data: Dict[str, Any], slot_index: Optional[int] -) -> dt.AnnotationFile: +def _parse_darwin_v2(path: Path, data: Dict[str, Any]) -> dt.AnnotationFile: item = data["item"] - filename = item["name"] item_source = item.get("source_info", {}) slots: List[dt.Slot] = list( filter(None, map(_parse_darwin_slot, item.get("slots", []))) @@ -583,7 +578,7 @@ def _parse_darwin_v2( annotation_file = dt.AnnotationFile( version=_parse_version(data), path=path, - filename=filename, + filename=item["name"], item_id=item.get("source_info", {}).get("item_id", None), dataset_name=item.get("source_info", {}) .get("dataset", {}) @@ -603,21 +598,11 @@ def _parse_darwin_v2( item_properties=data.get("properties", []), ) else: - if slot_index is None or len(slots) == 1: - slot = slots[0] - else: - slot = slots[slot_index] - slots = [slot] - filename = slot.source_files[0].file_name - annotations = get_annotations_in_slot(slot.name, annotations) - annotation_classes = { - annotation.annotation_class for annotation in annotations - } - + slot = slots[0] annotation_file = dt.AnnotationFile( version=_parse_version(data), path=path, - filename=filename, + filename=item["name"], item_id=item.get("source_info", {}).get("item_id", None), dataset_name=item.get("source_info", {}) .get("dataset", {}) From 89aa658bf61575f8d24e0b9bbd9901f5085514d2 Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Mon, 5 May 2025 16:46:16 +0200 Subject: [PATCH 16/17] Fix primary_plane setting after merge --- darwin/exporter/formats/nifti.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/darwin/exporter/formats/nifti.py b/darwin/exporter/formats/nifti.py index 39d260ddf..03486dc72 100644 --- a/darwin/exporter/formats/nifti.py +++ b/darwin/exporter/formats/nifti.py @@ -88,14 +88,16 @@ def export( for slot in video_annotation.slots: slot_name = slot.name slot_annotations = get_annotations_in_slot( - slot.name, video_annotation.annotations + slot_name, video_annotation.annotations ) try: medical_metadata = slot.metadata legacy = not medical_metadata.get("handler") == "MONAI" # type: ignore plane_map = medical_metadata.get("plane_map", {slot_name: "AXIAL"}) - primary_plane = plane_map.get(slot_name, "AXIAL") + primary_plane = medical_metadata.get( + "primary_plane", plane_map.get(slot_name, "AXIAL") + ) except (KeyError, AttributeError): legacy = True primary_plane = "AXIAL" @@ -214,7 +216,7 @@ def build_output_volumes( "SeriesInstanceUID", "SeriesIntanceUIDNotProvided" ) # Builds output volumes per class - volume_dims, pixdims, affine, original_affine = process_metadata(slot.metadata) + volume_dims, pixdims, affine, original_affine = process_metadata(slot_metadata) if not mask_present and not class_names_to_export: class_names_to_export = [ "" From 24bef8c6b210a0d1361fb50c443147eb8dc29a74 Mon Sep 17 00:00:00 2001 From: Valentin Vikhorev Date: Tue, 6 May 2025 09:26:06 +0200 Subject: [PATCH 17/17] Rename video_annotation to annotation_file in nifti exporter --- darwin/exporter/formats/nifti.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/darwin/exporter/formats/nifti.py b/darwin/exporter/formats/nifti.py index 03486dc72..396f49743 100644 --- a/darwin/exporter/formats/nifti.py +++ b/darwin/exporter/formats/nifti.py @@ -78,17 +78,16 @@ def export( sends output volumes, image_id and output_dir to the write_output_volume_to_disk function """ - video_annotations = list(annotation_files) - for video_annotation in video_annotations: - slot_map = {slot.name: slot for slot in video_annotation.slots} - image_id = check_for_error_and_return_imageid(video_annotation, output_dir) + for annotation_file in annotation_files: + slot_map = {slot.name: slot for slot in annotation_file.slots} + image_id = check_for_error_and_return_imageid(annotation_file, output_dir) if not isinstance(image_id, str): continue - for slot in video_annotation.slots: + for slot in annotation_file.slots: slot_name = slot.name slot_annotations = get_annotations_in_slot( - slot_name, video_annotation.annotations + slot_name, annotation_file.annotations ) try: @@ -136,7 +135,7 @@ def export( image_id=image_id, output_dir=output_dir, legacy=legacy, - item_name=video_annotation.path.stem, + item_name=annotation_file.path.stem, slot_name=slot.name, filename=slot.source_files[0].file_name, ) @@ -173,7 +172,7 @@ def export( image_id=image_id, output_dir=output_dir, legacy=legacy, - item_name=video_annotation.path.stem, + item_name=annotation_file.path.stem, slot_name=slot.name, filename=slot.source_files[0].file_name, ) @@ -191,7 +190,7 @@ def build_output_volumes( Parameters ---------- - video_annotation : dt.AnnotationFile + annotation_file : dt.AnnotationFile The ``AnnotationFile``\\s to be exported. from_raster_layer : bool Whether the output volumes are being built from raster layers or not @@ -241,14 +240,14 @@ def build_output_volumes( def check_for_error_and_return_imageid( - video_annotation: dt.AnnotationFile, output_dir: Path + annotation_file: dt.AnnotationFile, output_dir: Path ) -> Union[str, bool]: - """Given the video_annotation file and the output directory, checks for a range of errors and + """Given the annotation_file file and the output directory, checks for a range of errors and returns messages accordingly. Parameters ---------- - video_annotation : dt.AnnotationFile + annotation_file : dt.AnnotationFile The ``AnnotationFile``\\s to be exported. output_dir : Path The folder where the new instance mask files will be. @@ -259,7 +258,7 @@ def check_for_error_and_return_imageid( Returns the image_id if no errors are found, otherwise returns False """ # Check if all item slots have the correct file-extension - for slot in video_annotation.slots: + for slot in annotation_file.slots: for source_file in slot.source_files: filename = Path(source_file.file_name) if not ( @@ -273,7 +272,7 @@ def check_for_error_and_return_imageid( str(filename), ) - filename = Path(video_annotation.filename) + filename = Path(annotation_file.filename) if filename.name.lower().endswith(".nii.gz"): image_id = re.sub(r"(?i)\.nii\.gz$", "", str(filename)) elif filename.name.lower().endswith(".nii"): @@ -283,7 +282,7 @@ def check_for_error_and_return_imageid( else: image_id = str(filename) - for slot in video_annotation.slots: + for slot in annotation_file.slots: metadata = slot.metadata if metadata is None: return create_error_message_json(