1717
1818
1919def 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]:
299317def _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
355383class 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
363392def 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