Open In Colab Open this Colab for an interactive tutorial about how to import annotations on images.

Supported annotations

To import annotations in Labelbox, you need to create the annotations payload. In this section, we provide this payload for every annotation type.

Labelbox support two formats for the annotations payload: Python Annotation types (recommended) and NDJSON. Both are described below.

Classification - Radio (Single-choice)

radio_annotation = ClassificationAnnotation(
  name="radio_question", 
  value=Radio(answer = ClassificationAnswer(name = "second_radio_answer"))
)
radio_annotation_ndjson = {
  'name': 'radio_question',
  'answer': {'name': 'second_radio_answer'}
}

Classification - Checklist (Multi-choice)

checklist_annotation = ClassificationAnnotation(
  name="checklist_question", # must match your ontology feature's name
  value=Checklist(answer = [ClassificationAnswer(name = "first_checklist_answer"), ClassificationAnswer(name = "second_checklist_answer")])
 )
checklist_annotation_ndjson = {
  'name': 'checklist_question',
  'answer': [
    {'name': 'first_checklist_answer'},
    {'name': 'second_checklist_answer'}
  ]
}

Classification - Free-form text

text_annotation = ClassificationAnnotation(
  name="free_text",  # must match your ontology feature's name
  value=Text(answer="sample text")
)
text_annotation_ndjson = {
  'name': 'free_text',
  'answer': 'sample text',
}

Bounding Box

# Python Annotation 
bbox_annotation = ObjectAnnotation(
  name = "bounding_box",  # must match your ontology feature's name
  value = Rectangle(
        start=Point(x=977, y=1690), # Top left
        end=Point(x=330, y=225), # Bottom right
    ),
)
####### Bounding box with nested classification #######
bbox_with_radio_subclass_annotation = ObjectAnnotation(
    name="bbox_with_radio_subclass", # must match your ontology feature's name
    value=Rectangle(
        start=Point(x=933, y=541), # Top left
        end=Point(x=191, y=330), # Bottom right
    ),
    classifications=[
        ClassificationAnnotation(
            name="sub_radio_question",
            value=Radio(answer=ClassificationAnswer(name="first_sub_radio_answer"))
    )
  ]
)

bbox_annotation_ndjson = {
  'name': 'bounding_box',
  'bbox': {
          "top": 977,
          "left": 1690,
          "height": 330,
          "width": 225
      }
}


## NDJSON
bbox_with_radio_subclass_ndjson = {
    "name": "bbox_with_radio_subclass", 
    "classifications": [{
        "name": "sub_radio_question",
        "answer": 
            { "name":"first_sub_radio_answer" }
         
    }],
    "bbox": {
          "top": 933,
          "left": 541,
          "height": 191,
          "width": 330
        }
}

Polygon

polygon_annotation = ObjectAnnotation(
  name = "polygon", # must match your ontology feature's name
  value=Polygon( # Coordinates for the verticies of your polygon
        points=[Point(x=1489.581,y=183.934),Point(x=2278.306,y=256.885),Point(x=2428.197,y=200.437),Point(x=2560.0,y=335.419),
                Point(x=2557.386,y=503.165),Point(x=2320.596,y=503.103),Point(x=2156.083, y=628.943),Point(x=2161.111,y=785.519),
                Point(x=2002.115, y=894.647),Point(x=1838.456,y=877.874),Point(x=1436.53,y=874.636),Point(x=1411.403,y=758.579),
                Point(x=1353.853,y=751.74),Point(x=1345.264, y=453.461),Point(x=1426.011,y=421.129)]
    ),
)
polygon_annotation_ndjson = {
  'name': 'polygon',
  'classifications': [],
  'polygon': [
    {'x': 1489.581, 'y': 183.934},
    {'x': 2278.306, 'y': 256.885},
    {'x': 2428.197, 'y': 200.437},
    {'x': 2560.0, 'y': 335.419},
    {'x': 2557.386, 'y': 503.165},
    {'x': 2320.596, 'y': 503.103},
    {'x': 2156.083, 'y': 628.943},
    {'x': 2161.111, 'y': 785.519},
    {'x': 2002.115, 'y': 894.647},
    {'x': 1838.456, 'y': 877.874},
    {'x': 1436.53, 'y': 874.636},
    {'x': 1411.403, 'y': 758.579},
    {'x': 1353.853, 'y': 751.74},
    {'x': 1345.264, 'y': 453.461},
    {'x': 1426.011, 'y': 421.129},
    {'x': 1489.581, 'y': 183.934}
  ]
}

Segmentation Mask

MaskData is mask data in a uint8 array of [H, W, 3]. You can also convert a polygon annotation or a 2D array to MaskData. You can also specify a URL to a cloud-hosted mask (can be hosted on any cloud provider).

# Identifying what values in the numpy array correspond to the mask annotation
color = (0, 0, 0)

# convert a polygon to mask
im_height, im_width = 100,100 #need to provide the height and width of image.
mask_data = MaskData(arr=
                     polygon_annotation.value.draw(height=im_height,width=im_width,color=color))

# convert a 2D array to 3D array
arr_2d = np.zeros((100,100), dtype='uint8')
mask_data = MaskData.from_2D_arr(arr_2d)

# a 3D array where 3rd axis is RGB values.
mask_data = MaskData(arr= np.zeros([400,450,3],dtype='uint8'))

## Alternatively you can specify publicly accessible mask URL 
mask_data  = MaskData(url = mask_url)
  
mask_annotation = ObjectAnnotation(
  name = "mask", # must match your ontology feature's name
  value = Mask(mask=mask_data, color=color),
)
mask_annotation_ndjson = {
  'name': 'mask',
  'classifications': [],
  'mask': {
    'instanceURI': 'https://storage.labelbox.com/cjhfn5y6s0pk507024nz1ocys%2F1d60856c-59b7-3060-2754-83f7e93e0d01-1?Expires=1666901963361&KeyName=labelbox-assets-key-3&Signature=t-2s2DB4YjFuWEFak0wxYqfBfZA',
    'colorRGB': (0, 0, 0)
  }
}

Point

point_annotation = ObjectAnnotation(
  name = "point",  # must match your ontology feature's name
  value = Point(x=10, y=10),
)
point_annotation_ndjson = {
  'name': 'point',
  'classifications': [],
  'point': {'x': 10.0, 'y': 10.0}
}

Polyline

polyline_annotation = ObjectAnnotation(
  name = "polyline", # must match your ontology feature's name
  value=Line( # Coordinates for the keypoints in your polyline
        points=[Point(x=2534.353, y=249.471),Point(x=2429.492, y=182.092),Point(x=2294.322, y=221.962),Point(x=2224.491, y=180.463),Point(x=2136.123, y=204.716),
                Point(x=1712.247, y=173.949),Point(x=1703.838, y=84.438),Point(x=1579.772, y=82.61),Point(x=1583.442, y=167.552),
                Point(x=1478.869, y=164.903),Point(x=1418.941, y=318.149),Point(x=1243.128, y=400.815),Point(x=1022.067, y=319.007),
                Point(x=892.367, y=379.216),Point(x=670.273, y=364.408),Point(x=613.114, y=288.16),Point(x=377.559, y=238.251),
                Point(x=368.087, y=185.064),Point(x=246.557, y=167.286),Point(x=236.648, y=285.61),Point(x=90.929, y=326.412)]
    ),
)
polyline_annotation_ndjson = {
  'name': 'polyline',
  'classifications': [],
  'line': [
    {'x': 2534.353, 'y': 249.471},
    {'x': 2429.492, 'y': 182.092},
    {'x': 2294.322, 'y': 221.962},
    {'x': 2224.491, 'y': 180.463},
    {'x': 2136.123, 'y': 204.716},
    {'x': 1712.247, 'y': 173.949},
    {'x': 1703.838, 'y': 84.438},
    {'x': 1579.772, 'y': 82.61},
    {'x': 1583.442, 'y': 167.552},
    {'x': 1478.869, 'y': 164.903},
    {'x': 1418.941, 'y': 318.149},
    {'x': 1243.128, 'y': 400.815},
    {'x': 1022.067, 'y': 319.007},
    {'x': 892.367, 'y': 379.216},
    {'x': 670.273, 'y': 364.408},
    {'x': 613.114, 'y': 288.16},
    {'x': 377.559, 'y': 238.251},
    {'x': 368.087, 'y': 185.064},
    {'x': 246.557, 'y': 167.286},
    {'x': 236.648, 'y': 285.61},
    {'x': 90.929, 'y': 326.412}
  ]
}

End-to-end example: Import Pre-labels or Ground truth

Whether you are importing annotations as pre-labels or as ground truth, the steps are very similar. Step 6 (importing the annotation payload) is below where the steps are slightly different.

Before you start

You will need to import these libraries to use the code examples in this section.

from labelbox.schema.ontology import OntologyBuilder, Tool, Classification, Option
from labelbox import Client, MALPredictionImport, LabelImport
from labelbox.data.annotation_types import (
    Label, ImageData, ObjectAnnotation, MaskData,
    Rectangle, Point, Line, Mask, Polygon,
    Radio, Checklist, Text,
    ClassificationAnnotation, ClassificationAnswer
)
from labelbox.data.serialization import NDJsonConverter
from labelbox.schema.media_type import MediaType
import uuid
import numpy as np
from labelbox.schema.queue_mode import QueueMode

Step 1: Import data rows

To attach annotations to a data row, your need to have the data row in Catalog first. Here is an example image data row created in Catalog.

# send a sample image as batch to the project
client = Client(API_KEY)
test_img_url = "https://raw.githubusercontent.com/Labelbox/labelbox-python/develop/examples/assets/2560px-Kitano_Street_Kobe01s5s4110.jpg"
dataset = client.create_dataset(name="test dataset")
data_row = dataset.create_data_row(row_data=test_img_url)

Step 2: Create an Ontology

Your project should have the correct ontology setup with all the tools and classifications supported for your annotations, and the Tool names and Classification instructions should match the name fields in your annotation to ensure the correct feature schemas are matched.

Here is an example of creating an ontology programmatically for all the sample annotations above.

from labelbox.schema.ontology import OntologyBuilder, Tool, Classification, Option

ontology_builder = OntologyBuilder(
  classifications=[ # List of Classification objects
    Classification( # Radio classification given the name "text" with two options: "first_radio_answer" and "second_radio_answer"
      class_type=Classification.Type.RADIO, 
      instructions="radio_question", 
      options=[
        Option(value="first_radio_answer"),
        Option(value="second_radio_answer")
      ]
    ),
    Classification( # Checklist classification given the name "text" with two options: "first_checklist_answer" and "second_checklist_answer"
      class_type=Classification.Type.CHECKLIST, 
      instructions="checklist_question", 
      options=[
        Option(value="first_checklist_answer"),
        Option(value="second_checklist_answer")            
      ]
    ), 
     Classification( # Text classification given the name "text"
      class_type=Classification.Type.TEXT,
      instructions="free_text")
  ],
  tools=[ # List of Tool objects
    Tool( 
      tool=Tool.Type.BBOX, 
      name="bounding_box"), 
    Tool( 
      tool=Tool.Type.BBOX, 
      name="bounding_box_with_radio_subclass",
      classifications=[
            Classification(
                class_type=Classification.Type.RADIO,
                instructions="sub_radio_question",
                options=[
                  Option(value="first_sub_radio_answer"),
                  Option(value="second_sub_radio_answer")
                ]
              ),
        ]
      ), 
    Tool( 
      tool=Tool.Type.POLYGON, 
      name="polygon"),
    Tool( 
      tool=Tool.Type.SEGMENTATION, 
      name="mask"),
      Tool( 
      tool=Tool.Type.POINT, 
      name="point"), 
    Tool( 
      tool=Tool.Type.LINE, 
      name="polyline")]
)

ontology = client.create_ontology("Ontology Image Annotations", ontology_builder.asdict())

Step 3: Create a labeling project

Connect the ontology to the labeling project

# create a project and configure the ontology 
project = client.create_project(
    name="annotations_import_project",
    media_type=MediaType.Image,
    queue_mode=QueueMode.Batch)

project.setup_editor(ontology) # Connect your ontology and editor to your MAL project

Step 4: Send a batch of Data Rows to the project

# add data rows to project as a batch
project.create_batch("Initial batch", # name of the batch
  [data_row.uid], # list of Data Rows
  1 # priority between 1-5
)

Step 5: Create the annotations payload

Create the annotations payload using the snippets of code here.

Labelbox support two formats for the annotations payload: NDJSON and Python Annotation types. Both are described below to compose your annotations into Labels attached to the data rows.

The resulting label_ndjson should have exactly the same content for annotations that are supported by both (with exception of the uuid strings that are generated)

# Create a Label
label = Label(
    data=ImageData(uid=data_row.uid),
    annotations = [
        radio_annotation, checklist_annotation, text_annotation,
        bbox_annotation, bbox_with_radio_subclass_annotation, polygon_annotation, 
        mask_annotation, point_annotation, polyline_annotation
    ]
)

# Create urls to mask data for upload
def signing_function(obj_bytes: bytes) -> str:
    url = client.upload_data(content=obj_bytes, sign=True)
    return url

label.add_url_to_masks(signing_function)

# Convert our label from a Labelbox class object to the underlying NDJSON format required for upload 
label_ndjson = list(NDJsonConverter.serialize([label]))
label_ndjson = []
for annot in [radio_annotation_ndjson, checklist_annotation_ndjson, text_annotation_ndjson,
        bbox_annotation_ndjson, bbox_with_radio_subclass_annotation_ndjson, polygon_annotation_ndjson, mask_annotation_ndjson, point_annotation_ndjson, polyline_annotation_ndjson]:
  annot.update({
      'uuid': str(uuid.uuid4()),
      'dataRow': {'id': data_row.uid},
  })
  label_ndjson.append(annot)

Step 6: Import the annotation payload

Option A: Upload to a labeling project as pre-labels (Model-assisted labeling)

# Upload MAL label for this data row in project
upload_job = MALPredictionImport.create_from_objects(
    client = client, 
    project_id = project.uid, 
    name="mal_job"+str(uuid.uuid4()), 
    predictions=label_ndjson)

print("Errors:", upload_job.errors)

Option B: Upload to a labeling project as ground truth

# Upload label for this data row in project
upload_job = LabelImport.create_from_objects(
    client = client, 
    project_id = project.uid, 
    name="label_import_job"+str(uuid.uuid4()),  
    labels=label_ndjson)

print("Errors:", upload_job.errors)

End-to-end Python tutorial

Open this Colab for an end-to-end tutorial on importing annotations on images (Steps 1-6).

Open In Colab