Skip to content

Commit 68773cf

Browse files
chore: stan comments
1 parent 6c3c50a commit 68773cf

File tree

4 files changed

+46
-110
lines changed

4 files changed

+46
-110
lines changed

libs/labelbox/src/labelbox/data/annotation_types/audio.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Optional
2+
from pydantic import Field, AliasChoices
23

34
from labelbox.data.annotation_types.annotation import (
45
ClassificationAnnotation,
@@ -23,7 +24,15 @@ class AudioClassificationAnnotation(ClassificationAnnotation):
2324
extra (Dict[str, Any]): Additional metadata
2425
"""
2526

26-
start_frame: int
27+
start_frame: int = Field(
28+
validation_alias=AliasChoices("start_frame", "frame"),
29+
serialization_alias="frame",
30+
)
31+
end_frame: Optional[int] = Field(
32+
default=None,
33+
validation_alias=AliasChoices("end_frame", "endFrame"),
34+
serialization_alias="end_frame",
35+
)
2736
segment_index: Optional[int] = None
2837

2938

@@ -45,7 +54,14 @@ class AudioTextClassificationAnnotation(ClassificationAnnotation):
4554
extra (Dict[str, Any]): Additional metadata
4655
"""
4756

48-
start_frame: int
49-
end_frame: int = None
57+
start_frame: int = Field(
58+
validation_alias=AliasChoices("start_frame", "frame"),
59+
serialization_alias="frame",
60+
)
61+
end_frame: Optional[int] = Field(
62+
default=None,
63+
validation_alias=AliasChoices("end_frame", "endFrame"),
64+
serialization_alias="end_frame",
65+
)
5066

5167

libs/labelbox/src/labelbox/data/annotation_types/label.py

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -77,36 +77,21 @@ def _get_annotations_by_type(self, annotation_type):
7777

7878
def frame_annotations(
7979
self,
80-
) -> Dict[str, Union[VideoObjectAnnotation, VideoClassificationAnnotation]]:
81-
frame_dict = defaultdict(list)
82-
for annotation in self.annotations:
83-
if isinstance(
84-
annotation,
85-
(VideoObjectAnnotation, VideoClassificationAnnotation),
86-
):
87-
frame_dict[annotation.frame].append(annotation)
88-
return frame_dict
89-
90-
def audio_annotations_by_frame(
91-
self,
92-
) -> Dict[
93-
int, List[AudioClassificationAnnotation]
94-
]:
95-
"""Get audio annotations organized by frame (millisecond)
96-
80+
) -> Dict[int, Union[VideoObjectAnnotation, VideoClassificationAnnotation, AudioClassificationAnnotation]]:
81+
"""Get temporal annotations organized by frame
82+
9783
Returns:
98-
Dict[int, List]: Dictionary mapping frame (milliseconds) to list of audio annotations
99-
84+
Dict[int, List]: Dictionary mapping frame (milliseconds) to list of temporal annotations
85+
10086
Example:
101-
>>> label.audio_annotations_by_frame()
102-
{2500: [AudioClassificationAnnotation(...)]}
87+
>>> label.frame_annotations()
88+
{2500: [VideoClassificationAnnotation(...), AudioClassificationAnnotation(...)]}
10389
"""
10490
frame_dict = defaultdict(list)
10591
for annotation in self.annotations:
106-
if isinstance(
107-
annotation,
108-
AudioClassificationAnnotation,
109-
):
92+
if isinstance(annotation, (VideoObjectAnnotation, VideoClassificationAnnotation)):
93+
frame_dict[annotation.frame].append(annotation)
94+
elif isinstance(annotation, AudioClassificationAnnotation):
11095
frame_dict[annotation.start_frame].append(annotation)
11196
return dict(frame_dict)
11297

libs/labelbox/src/labelbox/data/serialization/ndjson/classification.py

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
from ...annotation_types.annotation import ClassificationAnnotation
1414
from ...annotation_types.video import VideoClassificationAnnotation
15-
from ...annotation_types.audio import AudioClassificationAnnotation
1615
from ...annotation_types.llm_prompt_response.prompt import (
1716
PromptClassificationAnnotation,
1817
PromptText,
@@ -401,11 +400,7 @@ class NDClassification:
401400
@staticmethod
402401
def to_common(
403402
annotation: "NDClassificationType",
404-
) -> Union[
405-
ClassificationAnnotation,
406-
VideoClassificationAnnotation,
407-
AudioClassificationAnnotation,
408-
]:
403+
) -> Union[ClassificationAnnotation, VideoClassificationAnnotation]:
409404
common = ClassificationAnnotation(
410405
value=annotation.to_common(),
411406
name=annotation.name,
@@ -420,26 +415,11 @@ def to_common(
420415
results = []
421416
for frame in annotation.frames:
422417
for idx in range(frame.start, frame.end + 1, 1):
423-
# Check if this is an audio annotation by looking at the extra data
424-
# Audio annotations will have start_frame/end_frame in extra, video annotations won't
425-
if (
426-
hasattr(annotation, "extra")
427-
and annotation.extra
428-
and "frames" in annotation.extra
429-
):
430-
# This is likely an audio temporal annotation
431-
results.append(
432-
AudioClassificationAnnotation(
433-
frame=idx, **common.model_dump(exclude_none=True)
434-
)
435-
)
436-
else:
437-
# This is a video temporal annotation
438-
results.append(
439-
VideoClassificationAnnotation(
440-
frame=idx, **common.model_dump(exclude_none=True)
441-
)
418+
results.append(
419+
VideoClassificationAnnotation(
420+
frame=idx, **common.model_dump(exclude_none=True)
442421
)
422+
)
443423
return results
444424

445425
@classmethod
@@ -448,7 +428,6 @@ def from_common(
448428
annotation: Union[
449429
ClassificationAnnotation,
450430
VideoClassificationAnnotation,
451-
AudioClassificationAnnotation,
452431
],
453432
data: GenericDataRowData,
454433
) -> Union[NDTextSubclass, NDChecklistSubclass, NDRadioSubclass]:
@@ -473,7 +452,6 @@ def lookup_classification(
473452
annotation: Union[
474453
ClassificationAnnotation,
475454
VideoClassificationAnnotation,
476-
AudioClassificationAnnotation,
477455
],
478456
) -> Union[NDText, NDChecklist, NDRadio]:
479457
return {Text: NDText, Checklist: NDChecklist, Radio: NDRadio}.get(

libs/labelbox/src/labelbox/data/serialization/ndjson/label.py

Lines changed: 12 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -85,45 +85,6 @@ def _get_consecutive_frames(
8585
consecutive.append((group[0], group[-1]))
8686
return consecutive
8787

88-
@classmethod
89-
def _get_audio_frame_ranges(cls, annotation_group: List[AudioClassificationAnnotation]) -> List[Tuple[int, int]]:
90-
"""Get frame ranges for audio annotations (simpler than video segments)"""
91-
return [(ann.start_frame, getattr(ann, 'end_frame', None) or ann.start_frame) for ann in annotation_group]
92-
93-
@classmethod
94-
def _has_changing_values(cls, annotation_group: List[AudioClassificationAnnotation]) -> bool:
95-
"""Check if annotations have different values (multi-value per instance)"""
96-
if len(annotation_group) <= 1:
97-
return False
98-
first_value = annotation_group[0].value.answer
99-
return any(ann.value.answer != first_value for ann in annotation_group)
100-
101-
@classmethod
102-
def _create_multi_value_annotation(cls, annotation_group: List[AudioClassificationAnnotation], data):
103-
"""Create annotation with frame-value mapping for changing values"""
104-
import json
105-
106-
# Build frame data and mapping in one pass
107-
frames_data = []
108-
frame_mapping = {}
109-
110-
for ann in annotation_group:
111-
start, end = ann.start_frame, getattr(ann, 'end_frame', None) or ann.start_frame
112-
frames_data.append({"start": start, "end": end})
113-
frame_mapping[str(start)] = ann.value.answer
114-
115-
# Create content structure
116-
content = json.dumps({
117-
"frame_mapping": frame_mapping,
118-
})
119-
120-
# Update template annotation
121-
template = annotation_group[0]
122-
from ...annotation_types.classification.classification import Text
123-
template.value = Text(answer=content)
124-
template.extra = {"frames": frames_data}
125-
126-
yield NDClassification.from_common(template, data)
12788

12889
@classmethod
12990
def _get_segment_frame_ranges(
@@ -208,28 +169,24 @@ def _create_video_annotations(
208169
def _create_audio_annotations(
209170
cls, label: Label
210171
) -> Generator[Union[NDChecklistSubclass, NDRadioSubclass], None, None]:
211-
"""Create audio annotations with multi-value support"""
172+
"""Create audio annotations serialized in Video NDJSON classification format."""
212173
audio_annotations = defaultdict(list)
213-
214-
# Collect audio annotations
174+
175+
# Collect audio annotations by name/schema_id
215176
for annot in label.annotations:
216177
if isinstance(annot, AudioClassificationAnnotation):
217178
audio_annotations[annot.feature_schema_id or annot.name].append(annot)
218179

219180
for annotation_group in audio_annotations.values():
220-
frame_ranges = cls._get_audio_frame_ranges(annotation_group)
221-
222-
# Process classifications
223-
if isinstance(annotation_group[0], AudioClassificationAnnotation):
224-
if cls._has_changing_values(annotation_group):
225-
# For audio with changing values, create frame-value mapping
226-
yield from cls._create_multi_value_annotation(annotation_group, label.data)
227-
else:
228-
# Standard processing for audio with same values
229-
annotation = annotation_group[0]
230-
frames_data = [{"start": start, "end": end} for start, end in frame_ranges]
231-
annotation.extra.update({"frames": frames_data})
232-
yield NDClassification.from_common(annotation, label.data)
181+
# Simple grouping: one NDJSON entry per annotation group (same as video)
182+
annotation = annotation_group[0]
183+
frames_data = []
184+
for ann in annotation_group:
185+
start = ann.start_frame
186+
end = getattr(ann, "end_frame", None) or ann.start_frame
187+
frames_data.append({"start": start, "end": end})
188+
annotation.extra.update({"frames": frames_data})
189+
yield NDClassification.from_common(annotation, label.data)
233190

234191

235192

0 commit comments

Comments
 (0)