Import ground truth

Import your previously generated ground truth data into Labelbox from internal or 3rd party tools.


Only supported in Python SDK 3.7.0 or later

How it works

The annotation import method allows you to import your ground truth annotations created from internal or 3rd party labeling systems into Labelbox Annotate to organize all of your data in one place. Using the label import API to import external data is a useful way to consolidate and migrate all annotations into Labelbox as a single source of truth.

You can use this import Ground Truth annotations to help you get set up with the Model Diagnostics workflow.

Imported annotations will appear when the asset is opened in the Editor as long as the following conditions are met:

  • The imported annotations are assigned to a Data Row within a dataset that is attached to the project
  • The asset has not already been labeled in the Labelbox Editor

Step 1: Prepare your data

The annotation import works similar to the import method used for Model-assisted labeling.

Annotation types (Recommended)

With Labelbox annotation types, you can validate the import data and easily serialize data into the NDJSON format for upload. Learn more annotation types here

from labelbox import Client, OntologyBuilder
from import (
from import NDJsonConverter

client = Client()

PROJECT_ID = "<project-id>"
project = client.get_project(PROJECT_ID)

# upload for queued data rows
queued_data_rows = project.export_queued_data_rows()

annotations = {
    'externalId1': [
            'bbox': [10, 10, 50, 50],  # x,y,h,w
            "class": 'dog'

labels = LabelList()

for datarow in queued_data_rows:
    uploads = []
    for annot in annotations[datarow.external_id]:
        bbox = annot['bbox']
                value=Rectangle.from_xyhw(bbox[0], bbox[1], bbox[3], bbox[2]),

    labels.append(Label(data=ImageData(uid=datarow['id']), annotations=uploads))

# Convert names to feature schema ids

labels_ndjson = list(NDJsonConverter.serialize(labels))


You can manually construct the ndjson with a dictionary.

schema_lookup = { tool.feature_schema_id for tool in OntologyBuilder.from_project(project).tools}
labels_ndjson = []

for datarow in queued_data_rows:
    for annot in annotations[datarow.external_id]:
        bbox = annot['bbox']

                "uuid": str(uuid4()),
                "schemaId": schema_lookup[annot['name']],
                "dataRow": {
                    "id": datarow.uid
                "bbox": {
                    "left": bbox[0],
                    "top": bbox[1],
                    "height": bbox[3],
                    "width": bbox[4]

Step 2: Upload annotations

You can upload labels either from a file or from a list of python dictionaries. Here we show the latter.

import time
from uuid import uuid4
from labelbox.schema.annotation_import import LabelImport

start_time = time.time()

# Upload annotations
upload_task = LabelImport.create_from_objects(client, project.uid, f"upload-job-{uuid4()}", labels_ndjson)

# Wait for upload to finish

print("--- Finished in %s mins ---" % ((time.time() - start_time)/60))



  • Before you begin a new import job to import annotations to a Data Row, make sure there are no existing MAL annotations on the Data Row. Duplicate import jobs may overwrite existing labels or result in unexpected behavior.
  • When you run an import job, the Activity page in Labelbox will not reflect any changes until the entire job is complete.


You can view the number of annotations imported for billing purposes by visiting the billing usage page. Please note that the billing system may indicate different counts for certain annotation types when compared to the annotation count on the project overview page. This is expected behavior.

Did this page help you?