From 408af206ebde628bd97f75a5235e441ea70714a7 Mon Sep 17 00:00:00 2001 From: Dmitriy Apollonin Date: Tue, 7 Nov 2023 16:24:29 -0700 Subject: [PATCH 1/3] add support to image bytes as a source for raster video mask import --- labelbox/data/annotation_types/video.py | 16 +++++++++++++--- labelbox/data/serialization/ndjson/objects.py | 10 ++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/labelbox/data/annotation_types/video.py b/labelbox/data/annotation_types/video.py index 715263b3f..bddcf6a02 100644 --- a/labelbox/data/annotation_types/video.py +++ b/labelbox/data/annotation_types/video.py @@ -1,7 +1,7 @@ from enum import Enum from typing import List, Optional, Tuple -from pydantic import BaseModel, validator +from pydantic import BaseModel, validator, root_validator from labelbox.data.annotation_types.annotation import ClassificationAnnotation, ObjectAnnotation from labelbox.data.annotation_types.annotation import ClassificationAnnotation, ObjectAnnotation @@ -81,14 +81,24 @@ class DICOMObjectAnnotation(VideoObjectAnnotation): keyframe (bool): Whether or not this annotation was a human generated or interpolated annotation segment_id (Optional[Int]): Index of video segment this annotation belongs to classifications (List[ClassificationAnnotation]) = [] - extra (Dict[str, Any]) + extra (Dict[str, Any]) """ group_key: GroupKey class MaskFrame(_CamelCaseMixin, BaseModel): index: int - instance_uri: str + instance_uri: Optional[str] = None + im_bytes: Optional[bytes] = None + + @root_validator() + def validate_args(cls, values): + im_bytes = values.get("im_bytes") + instance_uri = values.get("instance_uri") + + if im_bytes == instance_uri == None: + raise ValueError("One of `instance_uri`, `im_bytes` required.") + return values @validator("instance_uri") def validate_uri(cls, v): diff --git a/labelbox/data/serialization/ndjson/objects.py b/labelbox/data/serialization/ndjson/objects.py index e88850783..5bcb15e85 100644 --- a/labelbox/data/serialization/ndjson/objects.py +++ b/labelbox/data/serialization/ndjson/objects.py @@ -501,6 +501,11 @@ class NDVideoMasks(NDJsonBase, ConfidenceMixin): masks: NDVideoMasksFramesInstances def to_common(self) -> VideoMaskAnnotation: + for mask_frame in self.masks.frames: + if mask_frame.im_bytes: + mask_frame.im_bytes = base64.b64decode( + mask_frame.im_bytes.encode('utf-8')) + return VideoMaskAnnotation( frames=self.masks.frames, instances=self.masks.instances, @@ -508,6 +513,11 @@ def to_common(self) -> VideoMaskAnnotation: @classmethod def from_common(cls, annotation, data): + for mask_frame in annotation.frames: + if mask_frame.im_bytes: + mask_frame.im_bytes = base64.b64encode( + mask_frame.im_bytes).decode('utf-8') + return cls( data_row=DataRow(id=data.uid, global_key=data.global_key), masks=NDVideoMasksFramesInstances(frames=annotation.frames, From 425cbc0c546f44a630f79714d1fce85b420a4e43 Mon Sep 17 00:00:00 2001 From: Dmitriy Apollonin Date: Tue, 7 Nov 2023 17:09:32 -0700 Subject: [PATCH 2/3] fix test --- tests/data/annotation_types/test_video.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/data/annotation_types/test_video.py b/tests/data/annotation_types/test_video.py index d43c24aad..0e3cd7ec4 100644 --- a/tests/data/annotation_types/test_video.py +++ b/tests/data/annotation_types/test_video.py @@ -6,6 +6,7 @@ def test_mask_frame(): instance_uri="http://path/to/frame.png") assert mask_frame.dict(by_alias=True) == { 'index': 1, + 'imBytes': None, 'instanceURI': 'http://path/to/frame.png' } From ef545f12ec78f1deffa6213ca6c2a8b6c6561c1d Mon Sep 17 00:00:00 2001 From: Dmitriy Apollonin Date: Wed, 8 Nov 2023 13:38:01 -0700 Subject: [PATCH 3/3] fix dicom test --- Makefile | 2 +- tests/data/serialization/ndjson/test_dicom.py | 12 +++++++++--- .../integration/annotation_import/test_data_types.py | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 79c07765c..e9ffdbc96 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ build-image: - docker build -t local/labelbox-python:test . + docker build --quiet -t local/labelbox-python:test . test-local: build-image diff --git a/tests/data/serialization/ndjson/test_dicom.py b/tests/data/serialization/ndjson/test_dicom.py index eac5038c0..da47127f6 100644 --- a/tests/data/serialization/ndjson/test_dicom.py +++ b/tests/data/serialization/ndjson/test_dicom.py @@ -1,5 +1,6 @@ from copy import copy import pytest +import base64 import labelbox.types as lb_types from labelbox.data.serialization import NDJsonConverter from labelbox.data.serialization.ndjson.objects import NDDicomSegments, NDDicomSegment, NDDicomLine @@ -89,9 +90,11 @@ 'masks': { 'frames': [{ 'index': 1, + 'imBytes': None, 'instanceURI': instance_uri_1 }, { 'index': 5, + 'imBytes': None, 'instanceURI': instance_uri_5 }], 'instances': [ @@ -157,9 +160,12 @@ video_mask_label_with_global_key ] ndjsons = [ - polyline_annotation_ndjson, polyline_annotation_ndjson_with_global_key, - dicom_mask_annotation_ndjson, dicom_mask_annotation_ndjson_with_global_key, - video_mask_annotation_ndjson, video_mask_annotation_ndjson_with_global_key + polyline_annotation_ndjson, + polyline_annotation_ndjson_with_global_key, + dicom_mask_annotation_ndjson, + dicom_mask_annotation_ndjson_with_global_key, + video_mask_annotation_ndjson, + video_mask_annotation_ndjson_with_global_key, ] labels_ndjsons = list(zip(labels, ndjsons)) diff --git a/tests/integration/annotation_import/test_data_types.py b/tests/integration/annotation_import/test_data_types.py index f4ac3c82f..7e65dac96 100644 --- a/tests/integration/annotation_import/test_data_types.py +++ b/tests/integration/annotation_import/test_data_types.py @@ -65,6 +65,7 @@ def get_annotation_comparison_dicts_from_labels(labels): if 'masks' in annotation: for frame in annotation['masks']['frames']: frame.pop('instanceURI') + frame.pop('imBytes') for instance in annotation['masks']['instances']: instance.pop('colorRGB') return labels_ndjson