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 name
s 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).