How to import PascalVOC annotations into Kili
In this tutorial, we will demonstrate how to import PascalVOC annotations into Kili.
Setup
Let's start by installing Kili and other packages.
%%capture
%pip install kili xmltodict
import os
import urllib.request
from pathlib import Path
import xmltodict
from kili.client import Kili
Download Pascal VOC format
The images and annotations for the full dataset can be found here.
For this tutorial we will use a subset from the validation set from 2012.
images_names = [
"2007_000027",
"2010_001121",
"2010_001140",
"2011_004772",
"2012_003624",
"2012_004328",
]
annos_folder = "Annotations"
images_folder = "JPEGImages"
images_extension = ".jpg"
Path(annos_folder).mkdir(parents=True, exist_ok=True)
Path(images_folder).mkdir(parents=True, exist_ok=True)
for image_name in images_names:
for fld, img_ext in zip([images_folder, annos_folder], [images_extension, ".xml"]):
url = f"https://raw.githubusercontent.com/kili-technology/kili-python-sdk/main/recipes/datasets/pascalVOC2012/valsubset/{fld}/{image_name}{img_ext}"
urllib.request.urlretrieve(url, f"{fld}/{image_name}{img_ext}")
Reading xml files
def xml_to_dict(xml_file: str):
with open(xml_file) as file:
xml_data = file.read()
xml_dict = xmltodict.parse(xml_data)
return xml_dict
images_annotations = []
for image_name in images_names:
images_annotations.append(xml_to_dict(os.path.join(annos_folder, image_name + ".xml")))
Create a Kili project
Initialize the Kili client using your API key.
kili = Kili()
In the dictionary below, we map the category ids to the category names from all annotation files:
category_names = set()
for image_annos in images_annotations:
pascalvoc_annos = image_annos["annotation"]["object"]
if isinstance(pascalvoc_annos, dict):
category_names.add(pascalvoc_annos["name"])
else:
for anno in image_annos["annotation"]["object"]:
category_names.add(anno["name"])
category_names = list(category_names)
The json_interface
variable contains the ontology of the project.
json_interface = {"jobs": {}}
categories = {
cat_name: {"children": [], "name": cat_name, "id": i}
for i, cat_name in enumerate(category_names)
}
json_interface["jobs"]["OBJECT_DETECTION_JOB"] = {
"content": {"categories": categories, "input": "radio"},
"instruction": "BBox",
"mlTask": "OBJECT_DETECTION",
"required": 0,
"tools": ["rectangle"],
"isChild": False,
}
We can take a look at all the categories found with their respective ids:
for category in categories.values():
print(category["id"], ":", category["name"])
0 : cat
1 : person
2 : car
3 : sofa
Create a Kili project using the json interface.
project = kili.create_project(
title="[Kili SDK Notebook]: PascalVOC 2012",
input_type="IMAGE",
json_interface=json_interface,
)
Importing images
Now that the project is imported, let's import the images.
content_array = []
external_id_array = []
for image_id, image_name in enumerate(images_names):
content_array.append(os.path.join(images_folder, image_name + images_extension))
external_id_array.append(str(image_name))
kili.append_many_to_dataset(
project["id"], content_array=content_array, external_id_array=external_id_array
)
Importing annotations to Kili
Below, we import an useful function to convert annotations to Kili label format.
from typing import Dict, List
from kili.utils.labels.bbox import bbox_points_to_normalized_vertices
def pascal_bbox_to_kili_normalized_vertices(
pascal_bbox: Dict, img_width: int, img_height: int
) -> List[Dict]:
x1, y1, x2, y2 = (
pascal_bbox["xmin"],
pascal_bbox["ymin"],
pascal_bbox["xmax"],
pascal_bbox["ymax"],
)
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
kili_normalized_vertices = bbox_points_to_normalized_vertices(
bottom_left={"x": x1, "y": y2},
bottom_right={"x": x2, "y": y2},
top_right={"x": x2, "y": y1},
top_left={"x": x1, "y": y1},
img_height=img_height,
img_width=img_width,
origin_location="top_left",
)
return kili_normalized_vertices
We then add all annotations into the json_response
which is uploaded to Kili.
json_response_array = []
for image_id, image_name in enumerate(images_names):
# Get image size and annotations in pascalvoc format
image_annos = images_annotations[image_id]["annotation"]
img_width, img_height = int(image_annos["size"]["width"]), int(image_annos["size"]["height"])
json_resp = {}
kili_bbox_annotations = []
if isinstance(image_annos["object"], dict):
image_annos["object"] = [image_annos["object"]]
# Convert all annotations into Kili format and append them to the kili_bbox_annotations list
for ann in image_annos["object"]:
kili_bbox_ann = {
"children": {},
"boundingPoly": [
{
"normalizedVertices": pascal_bbox_to_kili_normalized_vertices(
ann["bndbox"], img_width=img_width, img_height=img_height
)
}
],
"categories": [{"name": ann["name"]}],
"type": "rectangle",
}
kili_bbox_annotations.append(kili_bbox_ann)
# Append the json response
json_resp["OBJECT_DETECTION_JOB"] = {"annotations": kili_bbox_annotations}
json_response_array.append(json_resp)
kili.append_labels(
asset_external_id_array=external_id_array,
project_id=project["id"],
json_response_array=json_response_array,
)
In the Kili labeling interface, we can see the images and the annotations.