View source Download .ipynb

Create Custom Bounding Box Table¶

Build a 3LC Table from scratch with custom bounding box annotations and schema definitions for specialized object detection scenarios.

img

While COCO and YOLO formats cover most use cases, specialized applications may require custom schemas, additional metadata, or non-standard annotation formats. Custom tables give you complete control over data structure and validation.

This notebook provides a step-by-step guide to defining custom table schemas and formatting bounding box data for 3LC. We demonstrate manual schema creation, data validation, and proper formatting of coordinate systems and class labels. Use this approach when working with proprietary annotation formats, specialized coordinate systems, or when you need additional metadata that standard formats don’t support.

Install dependencies¶

[ ]:
%pip install 3lc

Imports¶

[ ]:
from pathlib import Path

import tlc
from PIL import Image

Project setup¶

[ ]:
DATA_PATH = "../../../data"
PROJECT_NAME = "3LC Tutorials - Cats & Dogs"
[ ]:
cats_and_dogs_folder = Path(DATA_PATH) / "cats-and-dogs"
assert cats_and_dogs_folder.exists()

Setup the TableWriter¶

First, we need to import the tlc library and create a tlc.TableWriter object. We will provide a tlc.Schema to the table writer in a later cell.

A column of bounding boxes in 3LC is represented as a dictionary of the form:

{
    "image_width": float,
    "image_height": float,
    "bb_list": [
        {
            "x0": float,  # First "horizontal" coordinate
            "x1": float,  # Second "horizontal" coordinate
            "y0": float,  # First "vertical" coordinate
            "y1": float,  # Second "vertical" coordinate
            "label": str  # Label of the bounding box
        },
        ...
    ]
}
[ ]:
bb_schema = tlc.BoundingBoxListSchema(
    label_value_map={0.0: tlc.MapElement("dog"), 1.0: tlc.MapElement("cat")},
    x0_number_role=tlc.NUMBER_ROLE_BB_CENTER_X,
    y0_number_role=tlc.NUMBER_ROLE_BB_CENTER_Y,
    x1_number_role=tlc.NUMBER_ROLE_BB_SIZE_X,
    y1_number_role=tlc.NUMBER_ROLE_BB_SIZE_Y,
    x0_unit="relative",
    y0_unit="relative",
    x1_unit="relative",
    y1_unit="relative",
    include_segmentation=False,
)

schemas = {
    "image": tlc.ImageUrlSchema(),
    "bounding_boxes": bb_schema,
}

table_writer = tlc.TableWriter(
    project_name="3LC Tutorials - Create Tables",
    dataset_name="cats-and-dogs",
    table_name="initial-bbs",
    column_schemas=schemas,
)

Create Table data¶

Let’s load the data to populate the tlc.Table with. We have a dictionary with a mapping from image to it’s bounding boxes.

The labels are all in XcYcWH relative format, the one specified in the schema. This means each bounding box is defined by its:

Xc: The x coordinate of the center of the box,
Yc: The y coordinate of the center of the box,
W: The width of the bounding box,
H: The height of the bounding box,
C: The category index of the bounding box - here 0 means dog and 1 means cat

The coordinates are between 0 and 1, i.e. relative to the image width and height.

[ ]:
# Each image has a list of bounding boxes
data = {
    "cats/1500.jpg": [[0.527, 0.529, 0.941, 0.938, 1]],
    "cats/1501.jpg": [[0.470, 0.543, 0.866, 0.829, 1]],
    "cats/1502.jpg": [[0.520, 0.537, 0.705, 0.708, 1]],
    "cats/1503.jpg": [[0.591, 0.501, 0.814, 0.992, 1]],
    "cats/1504.jpg": [[0.487, 0.437, 0.819, 0.790, 1]],
    "dogs/1500.jpg": [[0.496, 0.495, 0.948, 0.897, 0]],
    "dogs/1501.jpg": [[0.484, 0.493, 0.308, 0.923, 0]],
    "dogs/1502.jpg": [[0.531, 0.652, 0.487, 0.688, 0]],
    "dogs/1503.jpg": [[0.520, 0.504, 0.945, 0.968, 0]],
    "dogs/1504.jpg": [[0.530, 0.497, 0.929, 0.944, 0]],
}

When populating the tlc.Table, we need to convert these boxes to appropriately formatted dictionaries.

[ ]:
table_rows = {"image": [], "bounding_boxes": []}

for relative_image_path, bbs in data.items():
    # Prepare full image path
    image_path = cats_and_dogs_folder / relative_image_path

    # Prepare bounding boxes
    image = Image.open(image_path)
    image_width, image_height = image.size
    bb_list = [{"x0": bb[0], "y0": bb[1], "x1": bb[2], "y1": bb[3], "label": bb[4]} for bb in bbs]
    boxes = {"image_height": image_height, "image_width": image_width, "bb_list": bb_list}

    # Populate table rows
    table_rows["image"].append(tlc.Url(image_path).to_relative().to_str())
    table_rows["bounding_boxes"].append(boxes)
[ ]:

Inspect the data¶

[ ]:
# Inspect the first row
table[0]