|
170 | 170 | }, |
171 | 171 | { |
172 | 172 | "metadata": {}, |
173 | | - "source": "ontology_builder = lb.OntologyBuilder(classifications=[\n lb.Classification(class_type=lb.Classification.Type.TEXT,\n name=\"text_audio\"),\n lb.Classification(\n class_type=lb.Classification.Type.CHECKLIST,\n name=\"checklist_audio\",\n options=[\n lb.Option(value=\"first_checklist_answer\"),\n lb.Option(value=\"second_checklist_answer\"),\n ],\n ),\n lb.Classification(\n class_type=lb.Classification.Type.RADIO,\n name=\"radio_audio\",\n options=[\n lb.Option(value=\"first_radio_answer\"),\n lb.Option(value=\"second_radio_answer\"),\n ],\n ),\n])\n\nontology = client.create_ontology(\n \"Ontology Audio Annotations\",\n ontology_builder.asdict(),\n media_type=lb.MediaType.Audio,\n)", |
| 173 | + "source": "ontology_builder = lb.OntologyBuilder(classifications=[\n lb.Classification(class_type=lb.Classification.Type.TEXT,\n name=\"text_audio\"),\n lb.Classification(\n class_type=lb.Classification.Type.CHECKLIST,\n name=\"checklist_audio\",\n options=[\n lb.Option(value=\"first_checklist_answer\"),\n lb.Option(value=\"second_checklist_answer\"),\n ],\n ),\n lb.Classification(\n class_type=lb.Classification.Type.RADIO,\n name=\"radio_audio\",\n options=[\n lb.Option(value=\"first_radio_answer\"),\n lb.Option(value=\"second_radio_answer\"),\n ],\n ),\n # Temporal classification for token-level annotations\n lb.Classification(\n class_type=lb.Classification.Type.TEXT,\n name=\"User Speaker\",\n scope=lb.Classification.Scope.INDEX, # INDEX scope for temporal\n ),\n])\n\nontology = client.create_ontology(\n \"Ontology Audio Annotations\",\n ontology_builder.asdict(),\n media_type=lb.MediaType.Audio,\n)", |
174 | 174 | "cell_type": "code", |
175 | 175 | "outputs": [], |
176 | 176 | "execution_count": null |
|
252 | 252 | ], |
253 | 253 | "cell_type": "markdown" |
254 | 254 | }, |
| 255 | + { |
| 256 | + "metadata": {}, |
| 257 | + "source": [ |
| 258 | + "## Temporal Audio Annotations\n", |
| 259 | + "\n", |
| 260 | + "Labelbox supports temporal annotations for audio/video with frame-level precision using the new temporal classification API.\n", |
| 261 | + "\n", |
| 262 | + "### Key Features:\n", |
| 263 | + "- **Frame-based timing**: All annotations use millisecond precision\n", |
| 264 | + "- **Deep nesting**: Support for multi-level nested classifications (Text > Text > Text, Radio > Radio > Radio, etc.)\n", |
| 265 | + "- **Inductive structures**: Multiple parent values can share nested classifications that are automatically split based on frame overlap\n", |
| 266 | + "- **Frame validation**: Frames start at 1 (not 0) and must be non-overlapping for Text and Radio siblings\n", |
| 267 | + "\n", |
| 268 | + "### Important Constraints:\n", |
| 269 | + "1. **Frame indexing**: Frames are 1-based (frame 0 is invalid)\n", |
| 270 | + "2. **Non-overlapping siblings**: Text and Radio classifications at the same level cannot have overlapping frame ranges\n", |
| 271 | + "3. **Overlapping checklists**: Only Checklist answers can have overlapping frame ranges with their siblings" |
| 272 | + ], |
| 273 | + "cell_type": "markdown" |
| 274 | + }, |
| 275 | + { |
| 276 | + "metadata": {}, |
| 277 | + "source": "# Define tokens with precise timing (from demo script)\ntokens_data = [\n (\"Hello\", 586, 770), # Hello: frames 586-770\n (\"AI\", 771, 955), # AI: frames 771-955\n (\"how\", 956, 1140), # how: frames 956-1140\n (\"are\", 1141, 1325), # are: frames 1141-1325\n (\"you\", 1326, 1510), # you: frames 1326-1510\n (\"doing\", 1511, 1695), # doing: frames 1511-1695\n (\"today\", 1696, 1880), # today: frames 1696-1880\n]\n\n# Create temporal annotations for each token\ntemporal_annotations = []\nfor token, start_frame, end_frame in tokens_data:\n token_annotation = lb_types.AudioClassificationAnnotation(\n frame=start_frame,\n end_frame=end_frame,\n name=\"User Speaker\",\n value=lb_types.Text(answer=token),\n )\n temporal_annotations.append(token_annotation)\n\nprint(f\"Created {len(temporal_annotations)} temporal token annotations\")", |
| 278 | + "cell_type": "code", |
| 279 | + "outputs": [], |
| 280 | + "execution_count": null |
| 281 | + }, |
| 282 | + { |
| 283 | + "metadata": {}, |
| 284 | + "source": "# Create label with both regular and temporal annotations\nlabel_with_temporal = []\nlabel_with_temporal.append(\n lb_types.Label(\n data={\"global_key\": global_key},\n annotations=[text_annotation, checklist_annotation, radio_annotation] +\n temporal_annotations,\n ))\n\nprint(\n f\"Created label with {len(label_with_temporal[0].annotations)} total annotations\"\n)\nprint(\" - Regular annotations: 3\")\nprint(f\" - Temporal annotations: {len(temporal_annotations)}\")", |
| 285 | + "cell_type": "code", |
| 286 | + "outputs": [], |
| 287 | + "execution_count": null |
| 288 | + }, |
255 | 289 | { |
256 | 290 | "metadata": {}, |
257 | 291 | "source": [ |
|
260 | 294 | ], |
261 | 295 | "cell_type": "markdown" |
262 | 296 | }, |
| 297 | + { |
| 298 | + "metadata": {}, |
| 299 | + "source": "# Upload temporal annotations via MAL\ntemporal_upload_job = lb.MALPredictionImport.create_from_objects(\n client=client,\n project_id=project.uid,\n name=f\"temporal_mal_job-{str(uuid.uuid4())}\",\n predictions=label_with_temporal,\n)\n\ntemporal_upload_job.wait_until_done()\nprint(\"Temporal upload completed!\")\nprint(\"Errors:\", temporal_upload_job.errors)\nprint(\"Status:\", temporal_upload_job.statuses)", |
| 300 | + "cell_type": "code", |
| 301 | + "outputs": [], |
| 302 | + "execution_count": null |
| 303 | + }, |
263 | 304 | { |
264 | 305 | "metadata": {}, |
265 | 306 | "source": "# Upload our label using Model-Assisted Labeling\nupload_job = lb.MALPredictionImport.create_from_objects(\n client=client,\n project_id=project.uid,\n name=f\"mal_job-{str(uuid.uuid4())}\",\n predictions=label,\n)\n\nupload_job.wait_until_done()\nprint(\"Errors:\", upload_job.errors)\nprint(\"Status of uploads: \", upload_job.statuses)", |
|
0 commit comments