Skip to content

Commit 543245d

Browse files
author
Matt Sokoloff
committed
support keyframes in videos
1 parent b0723e7 commit 543245d

File tree

2 files changed

+47
-38
lines changed

2 files changed

+47
-38
lines changed

labelbox/data/annotation_types/classification/classification.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Dict, List
1+
from typing import Any, Dict, List, Union
22

33
try:
44
from typing import Literal
@@ -28,15 +28,24 @@ class ClassificationAnswer(FeatureSchema):
2828
extra: Dict[str, Any] = {}
2929

3030

31+
class VideoClassificationAnswer(ClassificationAnswer):
32+
"""
33+
Each answer can have a key frame indepdent of the others.
34+
So unlike object annotations, classification annotations
35+
track key frames at a classification answer level.
36+
"""
37+
keyframe: bool
38+
39+
3140
class Radio(BaseModel):
3241
""" A classification with only one selected option allowed """
33-
answer: ClassificationAnswer
42+
answer: Union[VideoClassificationAnswer, ClassificationAnswer]
3443

3544

3645
class Checklist(_TempName):
3746
""" A classification with many selected options allowed """
3847
name: Literal["checklist"] = "checklist"
39-
answer: List[ClassificationAnswer]
48+
answer: Union[VideoClassificationAnswer, ClassificationAnswer]
4049

4150

4251
class Text(BaseModel):
@@ -50,4 +59,4 @@ class Dropdown(_TempName):
5059
- This is not currently compatible with MAL.
5160
"""
5261
name: Literal["dropdown"] = "dropdown"
53-
answer: List[ClassificationAnswer]
62+
answer: List[Union[VideoClassificationAnswer, ClassificationAnswer]]

labelbox/data/serialization/labelbox_v1/classification.py

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from labelbox.data.annotation_types.classification.classification import VideoClassificationAnswer
12
from typing import List, Union
23

34
from pydantic.main import BaseModel
@@ -12,27 +13,44 @@ class LBV1ClassificationAnswer(LBV1Feature):
1213
...
1314

1415

16+
def get_classification_answer(
17+
answer: LBV1ClassificationAnswer
18+
) -> Union[ClassificationAnswer, VideoClassificationAnswer]:
19+
kwargs = dict(feature_schema_id=answer.schema_id,
20+
name=answer.title,
21+
extra={
22+
'feature_id': answer.feature_id,
23+
'value': answer.value
24+
})
25+
if answer.keyframe is not None:
26+
return VideoClassificationAnswer(keyframe=answer.keyframe, **kwargs)
27+
else:
28+
return ClassificationAnswer(**kwargs)
29+
30+
31+
def get_lbv1_classification_answer(answer: Union[ClassificationAnswer,
32+
VideoClassificationAnswer]):
33+
return LBV1ClassificationAnswer(
34+
schema_id=answer.feature_schema_id,
35+
title=answer.name,
36+
value=answer.extra.get('value'),
37+
feature_id=answer.extra.get('feature_id'),
38+
keyframe=getattr(answer, 'keyframe',
39+
None) # Only applies to video clasifications
40+
)
41+
42+
1543
class LBV1Radio(LBV1Feature):
1644
answer: LBV1ClassificationAnswer
1745

1846
def to_common(self) -> Radio:
19-
return Radio(answer=ClassificationAnswer(
20-
feature_schema_id=self.answer.schema_id,
21-
name=self.answer.title,
22-
extra={
23-
'feature_id': self.answer.feature_id,
24-
'value': self.answer.value
25-
}))
47+
return Radio(answer=get_classification_answer(self.answer))
2648

2749
@classmethod
2850
def from_common(cls, radio: Radio, feature_schema_id: Cuid,
2951
**extra) -> "LBV1Radio":
3052
return cls(schema_id=feature_schema_id,
31-
answer=LBV1ClassificationAnswer(
32-
schema_id=radio.answer.feature_schema_id,
33-
title=radio.answer.name,
34-
value=radio.answer.extra.get('value'),
35-
feature_id=radio.answer.extra.get('feature_id')),
53+
answer=get_lbv1_classification_answer(radio.answer),
3654
**extra)
3755

3856

@@ -41,24 +59,15 @@ class LBV1Checklist(LBV1Feature):
4159

4260
def to_common(self) -> Checklist:
4361
return Checklist(answer=[
44-
ClassificationAnswer(feature_schema_id=answer.schema_id,
45-
name=answer.title,
46-
extra={
47-
'feature_id': answer.feature_id,
48-
'value': answer.value
49-
}) for answer in self.answers
62+
get_classification_answer(answer) for answer in self.answers
5063
])
5164

5265
@classmethod
5366
def from_common(cls, checklist: Checklist, feature_schema_id: Cuid,
5467
**extra) -> "LBV1Checklist":
5568
return cls(schema_id=feature_schema_id,
5669
answers=[
57-
LBV1ClassificationAnswer(
58-
schema_id=answer.feature_schema_id,
59-
title=answer.name,
60-
value=answer.extra.get('value'),
61-
feature_id=answer.extra.get('feature_id'))
70+
get_lbv1_classification_answer(answer)
6271
for answer in checklist.answer
6372
],
6473
**extra)
@@ -69,24 +78,15 @@ class LBV1Dropdown(LBV1Feature):
6978

7079
def to_common(self) -> Dropdown:
7180
return Dropdown(answer=[
72-
ClassificationAnswer(feature_schema_id=answer.schema_id,
73-
name=answer.title,
74-
extra={
75-
'feature_id': answer.feature_id,
76-
'value': answer.value
77-
}) for answer in self.answer
81+
get_classification_answer(answer) for answer in self.answer
7882
])
7983

8084
@classmethod
8185
def from_common(cls, dropdown: Dropdown, feature_schema_id: Cuid,
8286
**extra) -> "LBV1Dropdown":
8387
return cls(schema_id=feature_schema_id,
8488
answer=[
85-
LBV1ClassificationAnswer(
86-
schema_id=answer.feature_schema_id,
87-
title=answer.name,
88-
value=answer.extra.get('value'),
89-
feature_id=answer.extra.get('feature_id'))
89+
get_lbv1_classification_answer(answer)
9090
for answer in dropdown.answer
9191
],
9292
**extra)

0 commit comments

Comments
 (0)