Import geospatial annotations

How to import annotations on geospatial data and sample import formats.

Open this Colab for an interactive tutorial on importing annotations on geospatial data.

Supported annotations

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

Labelbox supports two formats for the annotations payload:

  • Python annotation types (recommended)
  • NDJSON

Both are described below.

Point

point_annotation = lb_types.ObjectAnnotation(
  name = "point_geo",
  value = lb_types.Point(x=-99.20647859573366, y=19.40018029091072),
)
point_annotation_ndjson = {
    "name": "point_geo",
    "point": {
         "x": -99.20647859573366,
         "y": 19.40018029091072
     }
}

Polyline

# Coordinates in the desired EPSG coordinate system
coords = [
            [
                -99.20842051506044,
                19.40032196622975
            ],
            [
                -99.20809864997865,
                19.39758963475322
            ],
            [
                -99.20758366584778,
                19.39776167179227
            ],
            [
                -99.20728325843811,
                19.3973265189299
            ]
        ]

line_points = []
line_points_ndjson = []

for sub in coords: 
  line_points.append(lb_types.Point(x=sub[0], y=sub[1]))
  line_points_ndjson.append({"x":sub[0], "y":sub[1]})

# Python Annotation 
polyline_annotation = lb_types.ObjectAnnotation(
  name = "polyline_geo",
  value = lb_types.Line(points=line_points),
)
# Coordinates in the desired EPSG coordinate system
coords = [
  [
    -99.20842051506044,
    19.40032196622975
  ],
  [
    -99.20809864997865,
    19.39758963475322
  ],
  [
    -99.20758366584778,
    19.39776167179227
  ],
  [
    -99.20728325843811,
    19.3973265189299
  ]
]

line_points = []
line_points_ndjson = []

for sub in coords: 
  line_points.append(lb_types.Point(x=sub[0], y=sub[1]))
  line_points_ndjson.append({"x":sub[0], "y":sub[1]})

# NDJSON 
polyline_annotation_ndjson = {
  "name": "polyline_geo",
  "line": line_points_ndjson
}

Polygon

polygon_points = []

for sub in coords_polygon: 
  polygon_points.append(lb_types.Point(x=sub[0], y=sub[1]))


polygon_annotation = lb_types.ObjectAnnotation(
  name = "polygon_geo",
  value = lb_types.Polygon(points=polygon_points),
)
# Coordinates in the desired EPSG coordinate system

polygon_points_ndjson = []

for sub in coords_polygon: 
  polygon_points_ndjson.append({"x":sub[0], "y":sub[1]})

polygon_annotation_ndjson = {
  "name": "polygon_geo",
  "polygon": polygon_points_ndjson
}
coords_polygon = [
    [
        -99.21042680740356,
        19.40036244486966
    ],
    [
        -99.2104160785675,
        19.40017017124035
    ],
    [
        -99.2103409767151,
        19.400008256428897
    ],
    [
        -99.21014785766603,
        19.400008256428897
    ],
    [
        -99.21019077301027,
        19.39983622176518
    ],
    [
        -99.21022295951845,
        19.399674306621385
    ],
    [
        -99.21029806137086,
        19.39951239131646
    ],
    [
        -99.2102873325348,
        19.399340356128437
    ],
    [
        -99.21025514602663,
        19.399117722085677
    ],
    [
        -99.21024441719057,
        19.39892544698541
    ],
    [
        -99.2102336883545,
        19.39874329141769
    ],
    [
        -99.21021223068239,
        19.398561135646027
    ],
    [
        -99.21018004417421,
        19.398399219233365
    ],
    [
        -99.21011567115785,
        19.39822718286836
    ],
    [
        -99.20992255210878,
        19.398136104719125
    ],
    [
        -99.20974016189577,
        19.398085505725305
    ],
    [
        -99.20957922935487,
        19.398004547302467
    ],
    [
        -99.20939683914186,
        19.39792358883935
    ],
    [
        -99.20918226242067,
        19.39786286996558
    ],
    [
        -99.20899987220764,
        19.397822390703805
    ],
    [
        -99.20891404151918,
        19.397994427496787
    ],
    [
        -99.20890331268312,
        19.398176583902874
    ],
    [
        -99.20889258384706,
        19.398368859888045
    ],
    [
        -99.20889258384706,
        19.398540896103246
    ],
    [
        -99.20890331268312,
        19.39872305189756
    ],
    [
        -99.20889258384706,
        19.39890520748796
    ],
    [
        -99.20889258384706,
        19.39907724313608
    ],
    [
        -99.20889258384706,
        19.399259398329956
    ],
    [
        -99.20890331268312,
        19.399431433603585
    ],
    [
        -99.20890331268312,
        19.39961358840092
    ],
    [
        -99.20890331268312,
        19.399785623300048
    ],
    [
        -99.20897841453552,
        19.399937418648214
    ],
    [
        -99.20919299125673,
        19.399937418648214
    ],
    [
        -99.2093861103058,
        19.39991717927664
    ],
    [
        -99.20956850051881,
        19.39996777770086
    ],
    [
        -99.20961141586305,
        19.40013981222548
    ],
    [
        -99.20963287353517,
        19.40032196622975
    ],
    [
        -99.20978307724,
        19.4004130431554
    ],
    [
        -99.20996546745302,
        19.40039280384301
    ],
    [
        -99.21019077301027,
        19.400372564528084
    ],
    [
        -99.21042680740356,
        19.40036244486966
    ]
    
]

Bounding box

      
bbox_top_left = lb_types.Point(x= -99.20746564865112, y=19.39799442829336)
bbox_bottom_right = lb_types.Point(x=-99.20568466186523, y=19.39925939999194)

# Python Annotation
bbox_annotation = lb_types.ObjectAnnotation(
  name = "bbox_geo",
  value = lb_types.Rectangle(start=bbox_top_left, end=bbox_bottom_right)
)
# Coordinates in the desired EPSG coordinate system
coord_object =  {
  "coordinates" : [[
    [
      -99.20746564865112,
      19.39799442829336
    ],
    [
      -99.20746564865112,
      19.39925939999194
    ],
    [
      -99.20568466186523,
      19.39925939999194
    ],
    [
      -99.20568466186523,
      19.39799442829336
    ],
    [
      -99.20746564865112,
      19.39799442829336
    ]
  ]]
}
      
# NDJSON
bbox_annotation_ndjson = {
  "name" : "bbox_geo",
  "bbox" : {
    'top': coord_object["coordinates"][0][1][1],
    'left': coord_object["coordinates"][0][1][0],
    'height': coord_object["coordinates"][0][3][1] - coord_object["coordinates"][0][1][1],        
    'width': coord_object["coordinates"][0][3][0] - coord_object["coordinates"][0][1][0]
  }
}

Bounding box with nested checklist

coord_object_checklist = {
  "coordinates": [
    [
      [
        -99.210266,
        19.39540372195134
      ],
      [
        -99.210266,
        19.396901
      ],
      [
        -99.20621067903966,
        19.396901
      ],
      [
        -99.20621067903966,
        19.39540372195134
      ],
      [
        -99.210266,
        19.39540372195134
      ]
    ]
  ]          
}

# Python Annotation
bbox_with_checklist_subclass = lb_types.ObjectAnnotation(
  name="bbox_checklist_geo",
  value=lb_types.Rectangle(
    start=lb_types.Point(x=-99.210266, y=19.39540372195134), # Top left
    end=lb_types.Point(x=-99.20621067903966, y=19.396901), # Bottom right
  ),
  classifications=[
    lb_types.ClassificationAnnotation(
      name="checklist_class_name",
      value=lb_types.Checklist(
        answer=[lb_types.ClassificationAnswer(name="first_checklist_answer")]
      )
    )
  ]
)
coord_object_checklist = {
  "coordinates": [
    [
      [
        -99.210266,
        19.39540372195134
      ],
      [
        -99.210266,
        19.396901
      ],
      [
        -99.20621067903966,
        19.396901
      ],
      [
        -99.20621067903966,
        19.39540372195134
      ],
      [
        -99.210266,
        19.39540372195134
      ]
    ]
  ]          
}

# NDJSON 
bbox_with_checklist_subclass_ndjson = {
  "name": "bbox_checklist_geo", 
  "classifications": [{
    "name": "checklist_class_name",
    "answer": [
      { "name":"first_checklist_answer" }
    ]   
  }],
  "bbox": {
    'top': coord_object_checklist["coordinates"][0][1][1],
    'left': coord_object_checklist["coordinates"][0][1][0],
    'height': coord_object_checklist["coordinates"][0][3][1] - coord_object_checklist["coordinates"][0][1][1],        
    'width': coord_object_checklist["coordinates"][0][3][0] - coord_object_checklist["coordinates"][0][1][0]
  }
}

Bounding box with nested free-form text

bbox_with_free_text_subclass = lb_types.ObjectAnnotation(
  name="bbox_text_geo",
  value=lb_types.Rectangle(
    start=lb_types.Point(x=-99.21019613742828, y=19.397447957052933), # Top left
    end=lb_types.Point(x=-99.20986354351044, y=19.39772119262215), # Bottom right
  ),
  classifications=[
    lb_types.ClassificationAnnotation(
      name="free_text_geo",
      value=lb_types.Text(answer="sample text")
    )
  ]
)
coord_object_text ={
  "coordinates": [
    [
      [
        -99.21019613742828,
        19.397447957052933
      ],
      [
        -99.21019613742828,
        19.39772119262215
      ],
      [
        -99.20986354351044,
        19.39772119262215
      ],
      [
        -99.20986354351044,
        19.397447957052933
      ],
      [
        -99.21019613742828,
        19.397447957052933
      ]
    ]
  ]
}

# NDJSON 
bbox_with_free_text_subclass_ndjson = {
  "name":"bbox_text_geo",
  "classifications": [{
    "name": "free_text_geo",
    "answer": "sample text"
  }],
  "bbox": {
    'top': coord_object_text["coordinates"][0][1][1],
    'left': coord_object_text["coordinates"][0][1][0],
    'height': coord_object_text["coordinates"][0][3][1] - coord_object_text["coordinates"][0][1][1],        
    'width': coord_object_text["coordinates"][0][3][0] - coord_object_text["coordinates"][0][1][0]
  }
}

Classification: Radio (single-choice)

radio_annotation = lb_types.ClassificationAnnotation(
  name="radio_question_geo", 
  value=lb_types.Radio(answer=lb_types.ClassificationAnswer(name="first_radio_answer"))
)
radio_annotation_ndjson = {
  "name": "radio_question_geo",
  "answer": { "name": "first_radio_answer"}
}

Classification: Checklist (multi-choice)

checklist_annotation = lb_types.ClassificationAnnotation(
  name="checklist_question_geo",
  value=lb_types.Checklist(answer = [
    lb_types.ClassificationAnswer(name = "first_checklist_answer"),
    lb_types.ClassificationAnswer(name = "second_checklist_answer"),
    lb_types.ClassificationAnswer(name = "third_checklist_answer")
  ])
)
checklist_annotation_ndjson = {
  "name": "checklist_question_geo",
  "answer": [
    {"name": "first_checklist_answer"},
    {"name": "second_checklist_answer"},
    {"name": "third_checklist_answer"},
  ]
}

Classification: Radio with nested classification

nested_radio_annotation = lb_types.ClassificationAnnotation(
  name="nested_radio_question",
  value=lb_types.Radio(
    answer=lb_types.ClassificationAnswer(
      name="first_radio_answer",
      classifications=[
        lb_types.ClassificationAnnotation(
          name="sub_radio_question",
          value=lb_types.Radio(
            answer=lb_types.ClassificationAnswer(
              name="first_sub_radio_answer"
            )
          )
        )
      ]
    )
  )
)
nested_radio_annotation_ndjson= {
  "name": "nested_radio_question",
  "answer": {
    "name": "first_radio_answer",
    "classifications": [{
      "name":"sub_radio_question",
      "answer": { "name" : "first_sub_radio_answer"}
    }]
  }
}

Classification: Checklist with nested classification

nested_checklist_annotation = lb_types.ClassificationAnnotation(
  name="nested_checklist_question",
  value=lb_types.Checklist(
    answer=[lb_types.ClassificationAnswer(
      name="first_checklist_answer",
      classifications=[
        lb_types.ClassificationAnnotation(
          name="sub_checklist_question",
          value=lb_types.Checklist(
            answer=[lb_types.ClassificationAnswer(
            name="first_sub_checklist_answer"
          )]
        ))
      ]
    )]
  )
)
nested_checklist_annotation_ndjson = {
  "name": "nested_checklist_question",
  "answer": [{
    "name": "first_checklist_answer", 
    "classifications" : [
      {
        "name": "sub_checklist_question", 
        "answer": {"name": "first_sub_checklist_answer"}
      }          
    ]         
  }]
}

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 where the process becomes slightly different and is explained below in detail.

Before you start

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

import uuid
import numpy as np
import cv2
import labelbox as lb
import labelbox.types as lb_types

Replace with your API key

API_KEY = ""
client = lb.Client(API_KEY)

Step 1: Import data rows

To attach annotations to a data row, it must first be uploaded to Catalog. Here we create an example image data row in Catalog.

top_left_bound = lb_types.Point(x=-99.21052827588443, y=19.400498983095076)
bottom_right_bound = lb_types.Point(x=-99.20534818927473, y=19.39533555271248)

epsg = lb_types.EPSG.EPSG4326
bounds = lb_types.TiledBounds(epsg=epsg, bounds=[top_left_bound, bottom_right_bound])
global_key = "mexico_city"

tile_layer = lb_types.TileLayer(
    url="https://s3-us-west-1.amazonaws.com/lb-tiler-layers/mexico_city/{z}/{x}/{y}.png"
)

tiled_image_data = lb_types.TiledImageData(tile_layer=tile_layer,
                                  tile_bounds=bounds,
                                  zoom_levels=[17, 23])

asset = {
    "row_data": tiled_image_data.asdict(),
    "global_key": global_key,
    "media_type": "TMS_GEO"
}

dataset = client.create_dataset(name="geo_demo_dataset")
task= dataset.create_data_rows([asset])
print("Errors:",task.errors)
print("Failed data rows:", task.failed_data_rows)

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 annotations to ensure the correct feature schemas are matched.

ontology_builder = lb.OntologyBuilder(
    tools=[
        lb.Tool(tool=lb.Tool.Type.POINT, name="point_geo"),
        lb.Tool(tool=lb.Tool.Type.LINE, name="polyline_geo"),
        lb.Tool(tool=lb.Tool.Type.POLYGON, name="polygon_geo"),
        lb.Tool(tool=lb.Tool.Type.POLYGON, name="polygon_geo_2"),
        lb.Tool(tool=lb.Tool.Type.BBOX, name="bbox_geo"), 
        lb.Tool( 
          tool=lb.Tool.Type.BBOX, 
          name="bbox_checklist_geo",
          classifications=[
                lb.Classification(
                    class_type=lb.Classification.Type.CHECKLIST,
                    name="checklist_class_name",
                    options=[
                      lb.Option(value="first_checklist_answer")
                    ]
                ),
            ]
          ),
        lb.Tool( 
          tool=lb.Tool.Type.BBOX, 
          name="bbox_text_geo",
          classifications=[
                lb.Classification(
                    class_type=lb.Classification.Type.TEXT,
                    name="free_text_geo"
                ),
            ]
          )    
      ],
      classifications = [
          lb.Classification(
              class_type=lb.Classification.Type.CHECKLIST, 
              name="checklist_question_geo",
              options=[
                  lb.Option(value="first_checklist_answer"),
                  lb.Option(value="second_checklist_answer"), 
                  lb.Option(value="third_checklist_answer")
              ]
          ), 
          lb.Classification(
              class_type=lb.Classification.Type.RADIO, 
              name="radio_question_geo",
              options=[
                  lb.Option(value="first_radio_answer")
              ]
          ),
          
        lb.Classification( 
          class_type=lb.Classification.Type.RADIO, 
          name="nested_radio_question", 
          options=[
            lb.Option(value="first_radio_answer",
              options=[
                  lb.Classification(
                    class_type=lb.Classification.Type.RADIO,
                    name="sub_radio_question",
                    options=[
                      lb.Option(value="first_sub_radio_answer")
                    ]
                ),
              ]
            ),
          ], 
        ),
        lb.Classification(
          class_type=lb.Classification.Type.CHECKLIST,
          name="nested_checklist_question",
          options=[
              lb.Option("first_checklist_answer",
                options=[
                  lb.Classification(
                      class_type=lb.Classification.Type.CHECKLIST,
                      name="sub_checklist_question", 
                      options=[lb.Option("first_sub_checklist_answer")]
                  )
              ]
            )
          ]
      ) 
    ]
)

ontology = client.create_ontology("Ontology Geospatial Annotations", ontology_builder.asdict(), media_type=lb.MediaType.Geospatial_Tile)

Step 3: Create a labeling project

Connect the ontology to the labeling project.

# Create a project
project = client.create_project(
  name="Geospatial Project Demo",
  media_type=lb.MediaType.Geospatial_Tile
)

# Connect the ontology
project.setup_editor(ontology)

Step 4: Send a batch of data rows to the project

# Create a batch to send to your MAL project
batch = project.create_batch(
  "first-batch-geo-demo", # Each batch in a project must have a unique name
  global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys
  priority=5 # priority between 1(Highest) - 5(lowest)
)

print("Batch: ", batch)

Step 5: Create the annotations payload

Create the annotations payload using the snippets of code shown above.

Labelbox supports two formats for the annotations payload: NDJSON and Python annotation types. Both approaches are described below with instructions to compose annotations into labels attached to a data row.

Python annotations

Here we create the complete payload of annotations using only Python annotation format. There is one annotation for each reference to an annotation created above.

Additionally, create a polygon annotation using the cv2 Python library with the snippet below.

# Let's create another polygon annotation with Python annotation tools that draws the image using cv2 libraries

hsv = cv2.cvtColor(tiled_image_data.value, cv2.COLOR_RGB2HSV)
mask = cv2.inRange(hsv, (25, 50, 25), (100, 150, 255))
kernel = np.ones((15, 20), np.uint8)
mask = cv2.erode(mask, kernel)
mask = cv2.dilate(mask, kernel)
mask_annotation = lb_types.MaskData.from_2D_arr(mask)
mask_data = lb_types.Mask(mask=mask_annotation, color=[255, 255, 255])
h, w, _ = tiled_image_data.value.shape
pixel_bounds = lb_types.TiledBounds(epsg=lb_types.EPSG.SIMPLEPIXEL,
                          bounds=[lb_types.Point(x=0, y=0),
                                  lb_types.Point(x=w, y=h)])
transformer = lb_types.EPSGTransformer.create_pixel_to_geo_transformer(
    src_epsg=pixel_bounds.epsg,
    pixel_bounds=pixel_bounds,
    geo_bounds=tiled_image_data.tile_bounds,
    zoom=20)
pixel_polygons = mask_data.shapely.simplify(3)
list_of_polygons = [transformer(lb_types.Polygon.from_shapely(p)) for p in pixel_polygons.geoms]
polygon_annotation_two = lb_types.ObjectAnnotation(value=list_of_polygons[0], name="polygon_geo_2")

Step 5: Create the annotations payload

Create the annotations payload using the snippets of code shown above.

Labelbox supports two formats for the annotations payload: NDJSON and Python annotation types. Both approaches are described below with instructions to compose annotations into Labels attached to the data rows.

The resultinglabels and ndjson_labels from each approach will include every annotation (created above) supported by the respective method.

labels =[]
labels.append(
    lb_types.Label(
        data=lb_types.TiledImageData(
            global_key=global_key,
            tile_layer=tile_layer,
            tile_bounds=bounds,
            zoom_levels=[12, 20]
        ),
        annotations = [
            point_annotation,
            polyline_annotation,
            polygon_annotation,
            bbox_annotation,
            radio_annotation,
            bbox_with_checklist_subclass,  
            bbox_with_free_text_subclass,
            checklist_annotation,
            polygon_annotation_two, 
            nested_checklist_annotation, 
            nested_radio_annotation
        ]
    )
)
label_ndjson = []

for annotations in [point_annotation_ndjson,
                    polyline_annotation_ndjson,
                    polygon_annotation_ndjson,
                    bbox_annotation_ndjson,
                    radio_annotation_ndjson,
                    bbox_with_checklist_subclass_ndjson,  
                    bbox_with_free_text_subclass_ndjson,
                    checklist_annotation_ndjson,
                    nested_checklist_annotation_ndjson,
                    nested_radio_annotation_ndjson
                    ]:
  annotations.update({
      'dataRow': {
          'globalKey': global_key
      }
  })
  label_ndjson.append(annotations)

Step 6: Import the annotation payload

For both options, you can pass either the labels list or label_ndjson payload created above as the value for the predictions or labels parameter.

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

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

upload_job.wait_until_done();
print("Errors:", upload_job.errors)
print("Status of uploads: ", upload_job.statuses)

Option B: Upload to a labeling project as ground truth

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

upload_job.wait_until_done();
print("Errors:", upload_job.errors)
print("Status of uploads: ", upload_job.statuses)