Skip to content

Commit 66e4c44

Browse files
chore: lint
1 parent 1266338 commit 66e4c44

File tree

5 files changed

+176
-95
lines changed

5 files changed

+176
-95
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,3 @@ class AudioClassificationAnnotation(ClassificationAnnotation):
3434
serialization_alias="end_frame",
3535
)
3636
segment_index: Optional[int] = None
37-

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,29 @@ def _get_annotations_by_type(self, annotation_type):
7777

7878
def frame_annotations(
7979
self,
80-
) -> Dict[int, Union[VideoObjectAnnotation, VideoClassificationAnnotation, AudioClassificationAnnotation]]:
80+
) -> Dict[
81+
int,
82+
Union[
83+
VideoObjectAnnotation,
84+
VideoClassificationAnnotation,
85+
AudioClassificationAnnotation,
86+
],
87+
]:
8188
"""Get temporal annotations organized by frame
82-
89+
8390
Returns:
8491
Dict[int, List]: Dictionary mapping frame (milliseconds) to list of temporal annotations
85-
92+
8693
Example:
8794
>>> label.frame_annotations()
8895
{2500: [VideoClassificationAnnotation(...), AudioClassificationAnnotation(...)]}
8996
"""
9097
frame_dict = defaultdict(list)
9198
for annotation in self.annotations:
92-
if isinstance(annotation, (VideoObjectAnnotation, VideoClassificationAnnotation)):
99+
if isinstance(
100+
annotation,
101+
(VideoObjectAnnotation, VideoClassificationAnnotation),
102+
):
93103
frame_dict[annotation.frame].append(annotation)
94104
elif isinstance(annotation, AudioClassificationAnnotation):
95105
frame_dict[annotation.start_frame].append(annotation)

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import copy
33
from itertools import groupby
44
from operator import itemgetter
5-
from typing import Any, Dict, Generator, List, Tuple, Union
5+
from typing import Generator, List, Tuple, Union
66
from uuid import uuid4
77

88
from pydantic import BaseModel
@@ -86,7 +86,6 @@ def _get_consecutive_frames(
8686
consecutive.append((group[0], group[-1]))
8787
return consecutive
8888

89-
9089
@classmethod
9190
def _get_segment_frame_ranges(
9291
cls,
@@ -173,25 +172,23 @@ def _create_audio_annotations(
173172
"""Create audio annotations with nested classifications using modular hierarchy builder."""
174173
# Extract audio annotations from the label
175174
audio_annotations = [
176-
annot for annot in label.annotations
175+
annot
176+
for annot in label.annotations
177177
if isinstance(annot, AudioClassificationAnnotation)
178178
]
179-
179+
180180
if not audio_annotations:
181181
return
182-
182+
183183
# Use the modular hierarchy builder to create NDJSON annotations
184184
ndjson_annotations = create_audio_ndjson_annotations(
185-
audio_annotations,
186-
label.data.global_key
185+
audio_annotations, label.data.global_key
187186
)
188-
187+
189188
# Yield each NDJSON annotation
190189
for annotation in ndjson_annotations:
191190
yield annotation
192191

193-
194-
195192
@classmethod
196193
def _create_non_video_annotations(cls, label: Label):
197194
non_video_annotations = [

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

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717

1818

1919
def create_temporal_ndjson_annotations(
20-
annotations: List[Any],
21-
data_global_key: str,
22-
frame_extractor: callable
23-
) -> List['TemporalNDJSON']:
20+
annotations: List[Any], data_global_key: str, frame_extractor: callable
21+
) -> List["TemporalNDJSON"]:
2422
"""
2523
Create NDJSON temporal annotations with hierarchical structure.
2624
@@ -53,14 +51,16 @@ def create_temporal_ndjson_annotations(
5351
TemporalNDJSON(
5452
name=display_name,
5553
answer=answers,
56-
dataRow={"globalKey": data_global_key}
54+
dataRow={"globalKey": data_global_key},
5755
)
5856
)
5957

6058
return results
6159

6260

63-
def _process_annotation_group(annotations: List[Any], frame_extractor: callable) -> List[Dict[str, Any]]:
61+
def _process_annotation_group(
62+
annotations: List[Any], frame_extractor: callable
63+
) -> List[Dict[str, Any]]:
6464
"""
6565
Process a group of annotations with the same name/schema_id.
6666
Groups by answer value and handles nested classifications recursively.
@@ -95,7 +95,9 @@ def _process_annotation_group(annotations: List[Any], frame_extractor: callable)
9595
return results
9696

9797

98-
def _process_checklist(annotations: List[Any], frame_extractor: callable) -> List[Dict[str, Any]]:
98+
def _process_checklist(
99+
annotations: List[Any], frame_extractor: callable
100+
) -> List[Dict[str, Any]]:
99101
"""Process checklist annotations - collect all unique options across all annotations."""
100102
# Collect all unique option names and their data
101103
option_data = defaultdict(lambda: {"frames": [], "nested": []})
@@ -113,20 +115,19 @@ def _process_checklist(annotations: List[Any], frame_extractor: callable) -> Lis
113115
option_data[opt_name]["frames"].extend(opt_frames)
114116

115117
# Collect nested classifications
116-
if hasattr(opt, 'classifications') and opt.classifications:
118+
if hasattr(opt, "classifications") and opt.classifications:
117119
option_data[opt_name]["nested"].extend(opt.classifications)
118120

119121
# Build answer entries
120122
results = []
121123
for opt_name in sorted(option_data.keys()):
122-
entry = {
123-
"name": opt_name,
124-
"frames": option_data[opt_name]["frames"]
125-
}
124+
entry = {"name": opt_name, "frames": option_data[opt_name]["frames"]}
126125

127126
# Recursively process nested classifications
128127
if option_data[opt_name]["nested"]:
129-
nested = _process_nested_classifications(option_data[opt_name]["nested"])
128+
nested = _process_nested_classifications(
129+
option_data[opt_name]["nested"]
130+
)
130131
if nested:
131132
entry["classifications"] = nested
132133

@@ -135,7 +136,9 @@ def _process_checklist(annotations: List[Any], frame_extractor: callable) -> Lis
135136
return results
136137

137138

138-
def _process_radio(annotations: List[Any], frame_extractor: callable) -> Dict[str, Any]:
139+
def _process_radio(
140+
annotations: List[Any], frame_extractor: callable
141+
) -> Dict[str, Any]:
139142
"""Process radio annotations - merge frames and nested classifications."""
140143
first = annotations[0]
141144
opt_name = first.value.answer.name
@@ -153,7 +156,10 @@ def _process_radio(annotations: List[Any], frame_extractor: callable) -> Dict[st
153156
all_frames.extend(opt_frames)
154157

155158
# Collect nested
156-
if hasattr(ann.value.answer, 'classifications') and ann.value.answer.classifications:
159+
if (
160+
hasattr(ann.value.answer, "classifications")
161+
and ann.value.answer.classifications
162+
):
157163
all_nested.extend(ann.value.answer.classifications)
158164

159165
entry = {"name": opt_name, "frames": all_frames}
@@ -167,10 +173,16 @@ def _process_radio(annotations: List[Any], frame_extractor: callable) -> Dict[st
167173
return entry
168174

169175

170-
def _process_text(annotations: List[Any], frame_extractor: callable) -> Dict[str, Any]:
176+
def _process_text(
177+
annotations: List[Any], frame_extractor: callable
178+
) -> Dict[str, Any]:
171179
"""Process text annotations - collect frames and nested classifications."""
172180
first = annotations[0]
173-
text_value = first.value.answer if hasattr(first.value, "answer") else str(first.value)
181+
text_value = (
182+
first.value.answer
183+
if hasattr(first.value, "answer")
184+
else str(first.value)
185+
)
174186

175187
# Collect all frames and nested
176188
all_frames = []
@@ -181,7 +193,7 @@ def _process_text(annotations: List[Any], frame_extractor: callable) -> Dict[str
181193
all_frames.append({"start": start, "end": end})
182194

183195
# Text nesting is at annotation level
184-
if hasattr(ann, 'classifications') and ann.classifications:
196+
if hasattr(ann, "classifications") and ann.classifications:
185197
all_nested.extend(ann.classifications)
186198

187199
entry = {"value": text_value, "frames": all_frames}
@@ -195,7 +207,9 @@ def _process_text(annotations: List[Any], frame_extractor: callable) -> Dict[str
195207
return entry
196208

197209

198-
def _process_nested_classifications(classifications: List[Any]) -> List[Dict[str, Any]]:
210+
def _process_nested_classifications(
211+
classifications: List[Any],
212+
) -> List[Dict[str, Any]]:
199213
"""
200214
Recursively process nested ClassificationAnnotation objects.
201215
This uses the same grouping logic as top-level annotations.
@@ -233,15 +247,14 @@ def _process_nested_classifications(classifications: List[Any]) -> List[Dict[str
233247
# Text
234248
answers.append(_process_nested_text(cls_group))
235249

236-
results.append({
237-
"name": display_name,
238-
"answer": answers
239-
})
250+
results.append({"name": display_name, "answer": answers})
240251

241252
return results
242253

243254

244-
def _process_nested_checklist(classifications: List[Any]) -> List[Dict[str, Any]]:
255+
def _process_nested_checklist(
256+
classifications: List[Any],
257+
) -> List[Dict[str, Any]]:
245258
"""Process nested checklist classifications."""
246259
option_data = defaultdict(lambda: {"frames": [], "nested": []})
247260

@@ -253,15 +266,17 @@ def _process_nested_checklist(classifications: List[Any]) -> List[Dict[str, Any]
253266
opt_frames = _extract_frames(opt, cls_frames)
254267
option_data[opt.name]["frames"].extend(opt_frames)
255268

256-
if hasattr(opt, 'classifications') and opt.classifications:
269+
if hasattr(opt, "classifications") and opt.classifications:
257270
option_data[opt.name]["nested"].extend(opt.classifications)
258271

259272
results = []
260273
for opt_name in sorted(option_data.keys()):
261274
entry = {"name": opt_name, "frames": option_data[opt_name]["frames"]}
262275

263276
if option_data[opt_name]["nested"]:
264-
nested = _process_nested_classifications(option_data[opt_name]["nested"])
277+
nested = _process_nested_classifications(
278+
option_data[opt_name]["nested"]
279+
)
265280
if nested:
266281
entry["classifications"] = nested
267282

@@ -283,7 +298,10 @@ def _process_nested_radio(classifications: List[Any]) -> Dict[str, Any]:
283298
opt_frames = _extract_frames(cls.value.answer, cls_frames)
284299
all_frames.extend(opt_frames)
285300

286-
if hasattr(cls.value.answer, 'classifications') and cls.value.answer.classifications:
301+
if (
302+
hasattr(cls.value.answer, "classifications")
303+
and cls.value.answer.classifications
304+
):
287305
all_nested.extend(cls.value.answer.classifications)
288306

289307
entry = {"name": opt_name, "frames": all_frames}
@@ -299,7 +317,11 @@ def _process_nested_radio(classifications: List[Any]) -> Dict[str, Any]:
299317
def _process_nested_text(classifications: List[Any]) -> Dict[str, Any]:
300318
"""Process nested text classifications."""
301319
first = classifications[0]
302-
text_value = first.value.answer if hasattr(first.value, "answer") else str(first.value)
320+
text_value = (
321+
first.value.answer
322+
if hasattr(first.value, "answer")
323+
else str(first.value)
324+
)
303325

304326
all_frames = []
305327
all_nested = []
@@ -308,7 +330,7 @@ def _process_nested_text(classifications: List[Any]) -> Dict[str, Any]:
308330
frames = _extract_frames(cls, [])
309331
all_frames.extend(frames)
310332

311-
if hasattr(cls, 'classifications') and cls.classifications:
333+
if hasattr(cls, "classifications") and cls.classifications:
312334
all_nested.extend(cls.classifications)
313335

314336
entry = {"value": text_value, "frames": all_frames}
@@ -321,13 +343,19 @@ def _process_nested_text(classifications: List[Any]) -> Dict[str, Any]:
321343
return entry
322344

323345

324-
def _extract_frames(obj: Any, fallback_frames: List[Dict[str, int]]) -> List[Dict[str, int]]:
346+
def _extract_frames(
347+
obj: Any, fallback_frames: List[Dict[str, int]]
348+
) -> List[Dict[str, int]]:
325349
"""
326350
Extract frame range from an object (annotation, answer, or classification).
327351
Uses explicit frames if available, otherwise falls back to provided frames.
328352
"""
329-
if (hasattr(obj, 'start_frame') and obj.start_frame is not None and
330-
hasattr(obj, 'end_frame') and obj.end_frame is not None):
353+
if (
354+
hasattr(obj, "start_frame")
355+
and obj.start_frame is not None
356+
and hasattr(obj, "end_frame")
357+
and obj.end_frame is not None
358+
):
331359
return [{"start": obj.start_frame, "end": obj.end_frame}]
332360
elif fallback_frames:
333361
return fallback_frames
@@ -354,15 +382,15 @@ def _get_value_key(obj: Any) -> str:
354382

355383
class TemporalNDJSON(BaseModel):
356384
"""NDJSON format for temporal annotations (audio, video, etc.)."""
385+
357386
name: str
358387
answer: List[Dict[str, Any]]
359388
dataRow: Dict[str, str]
360389

361390

362391
# Audio-specific convenience function
363392
def create_audio_ndjson_annotations(
364-
annotations: List[AudioClassificationAnnotation],
365-
data_global_key: str
393+
annotations: List[AudioClassificationAnnotation], data_global_key: str
366394
) -> List[TemporalNDJSON]:
367395
"""
368396
Create NDJSON audio annotations with hierarchical structure.
@@ -374,7 +402,12 @@ def create_audio_ndjson_annotations(
374402
Returns:
375403
List of TemporalNDJSON objects
376404
"""
377-
def audio_frame_extractor(ann: AudioClassificationAnnotation) -> Tuple[int, int]:
405+
406+
def audio_frame_extractor(
407+
ann: AudioClassificationAnnotation,
408+
) -> Tuple[int, int]:
378409
return (ann.start_frame, ann.end_frame or ann.start_frame)
379410

380-
return create_temporal_ndjson_annotations(annotations, data_global_key, audio_frame_extractor)
411+
return create_temporal_ndjson_annotations(
412+
annotations, data_global_key, audio_frame_extractor
413+
)

0 commit comments

Comments
 (0)