Skip to content

Commit 278b711

Browse files
authored
Merge pull request #1005 from Labelbox/imuhammad/AL-5109-raster-segmentation-annotation-kind
[AL-5109][AL-5110] Add Video and Raster Segmentation Annotation Kinds
2 parents 9be6a30 + a916c10 commit 278b711

File tree

13 files changed

+404
-179
lines changed

13 files changed

+404
-179
lines changed

labelbox/data/annotation_types/__init__.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
from .geometry import Geometry
77

88
from .annotation import ClassificationAnnotation
9-
from .annotation import VideoClassificationAnnotation
109
from .annotation import ObjectAnnotation
11-
from .annotation import VideoObjectAnnotation
12-
from .annotation import DICOMObjectAnnotation
13-
from .annotation import GroupKey
10+
from .video import VideoClassificationAnnotation
11+
from .video import VideoObjectAnnotation
12+
from .video import DICOMObjectAnnotation
13+
from .video import GroupKey
14+
from .video import MaskFrame
15+
from .video import MaskInstance
16+
from .video import VideoMaskAnnotation
17+
from .video import DICOMMaskAnnotation
1418

1519
from .ner import ConversationEntity
1620
from .ner import DocumentEntity
Lines changed: 2 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import abc
2-
from enum import Enum
3-
from typing import Any, Dict, List, Optional, Union
2+
from typing import Any, Dict, List, Union
43

5-
from labelbox.data.mixins import ConfidenceNotSupportedMixin, ConfidenceMixin
4+
from labelbox.data.mixins import ConfidenceMixin
65

76
from .classification import Checklist, Dropdown, Radio, Text
87
from .feature import FeatureSchema
@@ -54,85 +53,3 @@ class ObjectAnnotation(BaseAnnotation, ConfidenceMixin):
5453
"""
5554
value: Union[TextEntity, ConversationEntity, DocumentEntity, Geometry]
5655
classifications: List[ClassificationAnnotation] = []
57-
58-
59-
class VideoObjectAnnotation(ObjectAnnotation, ConfidenceNotSupportedMixin):
60-
"""Video object annotation
61-
62-
>>> VideoObjectAnnotation(
63-
>>> keyframe=True,
64-
>>> frame=10,
65-
>>> value=Rectangle(
66-
>>> start=Point(x=0, y=0),
67-
>>> end=Point(x=1, y=1)
68-
>>> ),
69-
>>> feature_schema_id="my-feature-schema-id"
70-
>>> )
71-
72-
Args:
73-
name (Optional[str])
74-
feature_schema_id (Optional[Cuid])
75-
value (Geometry)
76-
frame (Int): The frame index that this annotation corresponds to
77-
keyframe (bool): Whether or not this annotation was a human generated or interpolated annotation
78-
segment_id (Optional[Int]): Index of video segment this annotation belongs to
79-
classifications (List[ClassificationAnnotation]) = []
80-
extra (Dict[str, Any])
81-
"""
82-
83-
frame: int
84-
keyframe: bool
85-
segment_index: Optional[int] = None
86-
87-
88-
class VideoClassificationAnnotation(ClassificationAnnotation):
89-
"""Video classification
90-
91-
Args:
92-
name (Optional[str])
93-
feature_schema_id (Optional[Cuid])
94-
value (Union[Text, Checklist, Radio, Dropdown])
95-
frame (int): The frame index that this annotation corresponds to
96-
segment_id (Optional[Int]): Index of video segment this annotation belongs to
97-
extra (Dict[str, Any])
98-
"""
99-
frame: int
100-
segment_index: Optional[int] = None
101-
102-
103-
class GroupKey(Enum):
104-
"""Group key for DICOM annotations
105-
"""
106-
AXIAL = "axial"
107-
SAGITTAL = "sagittal"
108-
CORONAL = "coronal"
109-
110-
111-
class DICOMObjectAnnotation(VideoObjectAnnotation):
112-
"""DICOM object annotation
113-
114-
>>> DICOMObjectAnnotation(
115-
>>> name="dicom_polyline",
116-
>>> frame=2,
117-
>>> value=lb_types.Line(points = [
118-
>>> lb_types.Point(x=680, y=100),
119-
>>> lb_types.Point(x=100, y=190),
120-
>>> lb_types.Point(x=190, y=220)
121-
>>> ]),
122-
>>> segment_index=0,
123-
>>> keyframe=True,
124-
>>> group_key=GroupKey.AXIAL
125-
>>> )
126-
127-
Args:
128-
name (Optional[str])
129-
feature_schema_id (Optional[Cuid])
130-
value (Geometry)
131-
group_key (GroupKey)
132-
frame (Int): The frame index that this annotation corresponds to
133-
keyframe (bool): Whether or not this annotation was a human generated or interpolated annotation
134-
segment_id (Optional[Int]): Index of video segment this annotation belongs to
135-
classifications (List[ClassificationAnnotation]) = []
136-
extra (Dict[str, Any])
137-
"""
138-
group_key: GroupKey

labelbox/data/annotation_types/label.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
import labelbox
88
from labelbox.data.annotation_types.data.tiled_image import TiledImageData
99
from labelbox.schema import ontology
10-
from .annotation import (ClassificationAnnotation, ObjectAnnotation,
11-
VideoClassificationAnnotation, VideoObjectAnnotation,
12-
DICOMObjectAnnotation)
10+
from .annotation import (ClassificationAnnotation, ObjectAnnotation)
1311
from .classification import ClassificationAnswer
1412
from .data import AudioData, ConversationData, DicomData, DocumentData, HTMLData, ImageData, MaskData, TextData, VideoData
1513
from .geometry import Mask
1614
from .metrics import ScalarMetric, ConfusionMatrixMetric
1715
from .types import Cuid
16+
from .video import VideoClassificationAnnotation
17+
from .video import VideoObjectAnnotation, VideoMaskAnnotation
1818
from ..ontology import get_feature_schema_lookup
1919

2020
DataType = Union[VideoData, ImageData, TextData, TiledImageData, AudioData,
@@ -43,7 +43,8 @@ class Label(BaseModel):
4343
uid: Optional[Cuid] = None
4444
data: DataType
4545
annotations: List[Union[ClassificationAnnotation, ObjectAnnotation,
46-
ScalarMetric, ConfusionMatrixMetric]] = []
46+
VideoMaskAnnotation, ScalarMetric,
47+
ConfusionMatrixMetric]] = []
4748
extra: Dict[str, Any] = {}
4849

4950
def object_annotations(self) -> List[ObjectAnnotation]:
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from enum import Enum
2+
3+
from pydantic import BaseModel, validator
4+
from labelbox.data.annotation_types.annotation import BaseAnnotation, ClassificationAnnotation, ObjectAnnotation
5+
6+
from typing import List, Optional, Tuple
7+
from labelbox.data.mixins import ConfidenceNotSupportedMixin
8+
9+
from labelbox.utils import _CamelCaseMixin, is_valid_uri
10+
11+
12+
class VideoClassificationAnnotation(ClassificationAnnotation):
13+
"""Video classification
14+
Args:
15+
name (Optional[str])
16+
feature_schema_id (Optional[Cuid])
17+
value (Union[Text, Checklist, Radio, Dropdown])
18+
frame (int): The frame index that this annotation corresponds to
19+
segment_id (Optional[Int]): Index of video segment this annotation belongs to
20+
extra (Dict[str, Any])
21+
"""
22+
frame: int
23+
segment_index: Optional[int] = None
24+
25+
26+
class VideoObjectAnnotation(ObjectAnnotation, ConfidenceNotSupportedMixin):
27+
"""Video object annotation
28+
>>> VideoObjectAnnotation(
29+
>>> keyframe=True,
30+
>>> frame=10,
31+
>>> value=Rectangle(
32+
>>> start=Point(x=0, y=0),
33+
>>> end=Point(x=1, y=1)
34+
>>> ),
35+
>>> feature_schema_id="my-feature-schema-id"
36+
>>> )
37+
Args:
38+
name (Optional[str])
39+
feature_schema_id (Optional[Cuid])
40+
value (Geometry)
41+
frame (Int): The frame index that this annotation corresponds to
42+
keyframe (bool): Whether or not this annotation was a human generated or interpolated annotation
43+
segment_id (Optional[Int]): Index of video segment this annotation belongs to
44+
classifications (List[ClassificationAnnotation]) = []
45+
extra (Dict[str, Any])
46+
"""
47+
frame: int
48+
keyframe: bool
49+
segment_index: Optional[int] = None
50+
51+
52+
class GroupKey(Enum):
53+
"""Group key for DICOM annotations
54+
"""
55+
AXIAL = "axial"
56+
SAGITTAL = "sagittal"
57+
CORONAL = "coronal"
58+
59+
60+
class DICOMObjectAnnotation(VideoObjectAnnotation):
61+
"""DICOM object annotation
62+
>>> DICOMObjectAnnotation(
63+
>>> name="dicom_polyline",
64+
>>> frame=2,
65+
>>> value=lb_types.Line(points = [
66+
>>> lb_types.Point(x=680, y=100),
67+
>>> lb_types.Point(x=100, y=190),
68+
>>> lb_types.Point(x=190, y=220)
69+
>>> ]),
70+
>>> segment_index=0,
71+
>>> keyframe=True,
72+
>>> Group_key=GroupKey.AXIAL
73+
>>> )
74+
Args:
75+
name (Optional[str])
76+
feature_schema_id (Optional[Cuid])
77+
value (Geometry)
78+
group_key (GroupKey)
79+
frame (Int): The frame index that this annotation corresponds to
80+
keyframe (bool): Whether or not this annotation was a human generated or interpolated annotation
81+
segment_id (Optional[Int]): Index of video segment this annotation belongs to
82+
classifications (List[ClassificationAnnotation]) = []
83+
extra (Dict[str, Any])
84+
"""
85+
group_key: GroupKey
86+
87+
88+
class MaskFrame(_CamelCaseMixin, BaseModel):
89+
index: int
90+
instance_uri: str
91+
92+
@validator("instance_uri")
93+
def validate_uri(cls, v):
94+
if not is_valid_uri(v):
95+
raise ValueError(f"{v} is not a valid uri")
96+
return v
97+
98+
99+
class MaskInstance(_CamelCaseMixin, BaseModel):
100+
color_rgb: Tuple[int, int, int]
101+
name: str
102+
103+
104+
class VideoMaskAnnotation(BaseAnnotation):
105+
"""DICOM video annotation
106+
>>> DICOMVideoAnnotation(
107+
>>> name="dicom_mask",
108+
>>> frames=[
109+
>>> MaskFrame(index=1, instance_uri='https://storage.labelbox.com/cjhfn5y6s0pk507024nz1ocys%2F1d60856c-59b7-3060-2754-83f7e93e0d01-1?Expires=1666901963361&KeyName=labelbox-assets-key-3&Signature=t-2s2DB4YjFuWEFak0wxYqfBfZA'),
110+
>>> MaskFrame(index=5, instance_uri='https://storage.labelbox.com/cjhfn5y6s0pk507024nz1ocys1%2F1d60856c-59b7-3060-2754-83f7e93e0d01-1?Expires=1666901963361&KeyName=labelbox-assets-key-3&Signature=t-2s2DB4YjFuWEFak0wxYqfBfZA'),
111+
>>> ],
112+
>>> instances=[
113+
>>> MaskInstance(color_rgb=(0, 0, 255), name="mask1"),
114+
>>> MaskInstance(color_rgb=(0, 255, 0), name="mask2"),
115+
>>> MaskInstance(color_rgb=(255, 0, 0), name="mask3")
116+
>>> ]
117+
>>> )
118+
"""
119+
frames: List[MaskFrame]
120+
instances: List[MaskInstance]
121+
122+
123+
class DICOMMaskAnnotation(VideoMaskAnnotation):
124+
"""DICOM mask annotation
125+
>>> DICOMMaskAnnotation(
126+
>>> name="dicom_mask",
127+
>>> group_key=GroupKey.AXIAL,
128+
>>> frames=[
129+
>>> MaskFrame(index=1, instance_uri='https://storage.labelbox.com/cjhfn5y6s0pk507024nz1ocys%2F1d60856c-59b7-3060-2754-83f7e93e0d01-1?Expires=1666901963361&KeyName=labelbox-assets-key-3&Signature=t-2s2DB4YjFuWEFak0wxYqfBfZA'),
130+
>>> MaskFrame(index=5, instance_uri='https://storage.labelbox.com/cjhfn5y6s0pk507024nz1ocys1%2F1d60856c-59b7-3060-2754-83f7e93e0d01-1?Expires=1666901963361&KeyName=labelbox-assets-key-3&Signature=t-2s2DB4YjFuWEFak0wxYqfBfZA'),
131+
>>> ],
132+
>>> instances=[
133+
>>> MaskInstance(color_rgb=(0, 0, 255), name="mask1"),
134+
>>> MaskInstance(color_rgb=(0, 255, 0), name="mask2"),
135+
>>> MaskInstance(color_rgb=(255, 0, 0), name="mask3")
136+
>>> ]
137+
>>> )
138+
"""
139+
group_key: GroupKey

labelbox/data/serialization/labelbox_v1/label.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
from pydantic import BaseModel, Field
66

77
from ...annotation_types.annotation import (ClassificationAnnotation,
8-
ObjectAnnotation,
9-
VideoClassificationAnnotation,
10-
VideoObjectAnnotation)
8+
ObjectAnnotation)
9+
from ...annotation_types.video import VideoClassificationAnnotation, VideoObjectAnnotation
1110
from ...annotation_types.data import ImageData, TextData, VideoData
1211
from ...annotation_types.label import Label
1312
from .classification import LBV1Classifications

labelbox/data/serialization/ndjson/classification.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from labelbox.data.mixins import ConfidenceMixin
55

66
from labelbox.utils import camel_case
7-
from ...annotation_types.annotation import ClassificationAnnotation, VideoClassificationAnnotation
7+
from ...annotation_types.annotation import ClassificationAnnotation
8+
from ...annotation_types.video import VideoClassificationAnnotation
89
from ...annotation_types.classification.classification import ClassificationAnswer, Dropdown, Text, Checklist, Radio
910
from ...annotation_types.types import Cuid
1011
from ...annotation_types.data import TextData, VideoData, ImageData

labelbox/data/serialization/ndjson/label.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
from pydantic import BaseModel
88

9-
from ...annotation_types.annotation import ClassificationAnnotation, ObjectAnnotation, VideoClassificationAnnotation, VideoObjectAnnotation, DICOMObjectAnnotation
9+
from ...annotation_types.annotation import ClassificationAnnotation, ObjectAnnotation
10+
from ...annotation_types.video import DICOMObjectAnnotation, VideoClassificationAnnotation
11+
from ...annotation_types.video import VideoObjectAnnotation, VideoMaskAnnotation
1012
from ...annotation_types.collection import LabelCollection, LabelGenerator
1113
from ...annotation_types.data import DicomData, ImageData, TextData, VideoData
1214
from ...annotation_types.label import Label
@@ -16,14 +18,15 @@
1618

1719
from .metric import NDScalarMetric, NDMetricAnnotation, NDConfusionMatrixMetric
1820
from .classification import NDChecklistSubclass, NDClassification, NDClassificationType, NDRadioSubclass
19-
from .objects import NDObject, NDObjectType, NDSegments, NDDicomSegments
21+
from .objects import NDObject, NDObjectType, NDSegments, NDDicomSegments, NDVideoMasks, NDDicomMasks
2022
from .base import DataRow
2123

2224

2325
class NDLabel(BaseModel):
2426
annotations: List[Union[NDObjectType, NDClassificationType,
2527
NDConfusionMatrixMetric, NDScalarMetric,
26-
NDDicomSegments, NDSegments]]
28+
NDDicomSegments, NDSegments, NDDicomMasks,
29+
NDVideoMasks]]
2730

2831
def to_common(self) -> LabelGenerator:
2932
grouped_annotations = defaultdict(list)
@@ -60,6 +63,10 @@ def _generate_annotations(
6063
annots.extend(
6164
NDSegments.to_common(annotation, annotation.name,
6265
annotation.schema_id))
66+
elif isinstance(annotation, NDDicomMasks):
67+
annots.append(NDDicomMasks.to_common(annotation))
68+
elif isinstance(annotation, NDVideoMasks):
69+
annots.append(NDVideoMasks.to_common(annotation))
6370
elif isinstance(annotation, NDObjectType.__args__):
6471
annots.append(NDObject.to_common(annotation))
6572
elif isinstance(annotation, NDClassificationType.__args__):
@@ -152,6 +159,8 @@ def _create_video_annotations(
152159
(VideoClassificationAnnotation, VideoObjectAnnotation)):
153160
video_annotations[annot.feature_schema_id or
154161
annot.name].append(annot)
162+
elif isinstance(annot, VideoMaskAnnotation):
163+
yield NDObject.from_common(annotation=annot, data=label.data)
155164

156165
for annotation_group in video_annotations.values():
157166
segment_frame_ranges = cls._get_segment_frame_ranges(
@@ -183,7 +192,8 @@ def _create_non_video_annotations(cls, label: Label):
183192
non_video_annotations = [
184193
annot for annot in label.annotations
185194
if not isinstance(annot, (VideoClassificationAnnotation,
186-
VideoObjectAnnotation))
195+
VideoObjectAnnotation,
196+
VideoMaskAnnotation))
187197
]
188198
for annotation in non_video_annotations:
189199
if isinstance(annotation, ClassificationAnnotation):

0 commit comments

Comments
 (0)