Skip to content

Quickstart: Import a Dataset with Pre-annotations

This guide walks you through importing an annotated dataset into Pixano in three steps: init your data directory, import your dataset, and serve the application. By the end you will have a running Pixano instance displaying images with bounding-box pre-annotations. See Installing Pixano for setup instructions.

Prerequisites

  • Python 3.10+ installed
  • Pixano installed:
pip install pixano
Running from source with uv

Clone the repository and install with uv:

git clone https://github.com/pixano/pixano.git
cd pixano
uv sync

When running from source, prefix every command with uv run (e.g. uv run pixano init ./my_data).

Step 1 — Initialize Your Data Directory

Create the directory structure Pixano needs to store datasets, media files, and models:

pixano init ./my_data

This creates the following tree:

my_data/
  library/   # LanceDB dataset storage
  media/     # Imported media files (images, videos)
  models/    # Model files for inference

Step 2 — Prepare and Import Your Dataset

Pixano imports datasets from a source directory organized by splits (e.g. train/, val/). Each split folder contains the media files and an optional metadata.jsonl file with pre-annotations.

Object Detection

You have images with bounding-box annotations and want to import them into Pixano.

Organize your source folder

street_objects/
  train/
    img_001.jpg
    img_002.jpg
    img_003.jpg
    metadata.jsonl
  val/
    img_004.jpg
    img_005.jpg

Two rules:

  • Each subfolder is a split.
  • A metadata.jsonl file inside a split folder provides pre-annotations for that split. Splits without one (like val/ above) import images with no pre-annotations.

Define your schema

The base Entity class has no domain-specific attributes, so you need a custom schema to attach fields like category to your objects. Save this file as schema.py:

# schema.py
from pixano.features import Entity
from pixano.datasets.workspaces import DefaultImageDatasetItem


class DetectedObject(Entity):
    category: str = ""


class StreetObjectsDatasetItem(DefaultImageDatasetItem):
    objects: list[DetectedObject]  # overrides default Entity table
    image_source: str = ""         # item-level metadata

Extending Entity lets you add custom attributes to each detected object. Extending DefaultImageDatasetItem gives you the standard image tables out of the box — you only need to override what you want to customize.

What's in the default schema?

DefaultImageDatasetItem provides the following attributes. Fields you don't override are included automatically.

Attribute Type Stored in
image Image image table (view)
objects list[Entity] objects table (entities)
bboxes list[BBox] bboxes table (annotations)
masks list[CompressedRLE] masks table (annotations)
keypoints list[KeyPoints] keypoints table (annotations)

In the example above, objects is overridden with list[DetectedObject] to add the category field. The image_source field does not correspond to any schema group, so it is stored as item-level metadata in the item table.

Write the metadata.jsonl

Create a metadata.jsonl file in each split folder that has pre-annotations. Each line is a JSON object describing one image:

street_objects/train/metadata.jsonl

{"image": "img_001.jpg", "image_source": "dashcam", "objects": {"bboxes": [[0.12, 0.25, 0.15, 0.30], [0.55, 0.10, 0.20, 0.45]], "category": ["car", "pedestrian"]}}
{"image": "img_002.jpg", "image_source": "dashcam", "objects": {"bboxes": [[0.30, 0.40, 0.08, 0.12]], "category": ["bicycle"]}}
{"image": "img_003.jpg", "image_source": "drone"}

Format rules:

  • image matches the view field name in the schema and points to the image file in the same folder.
  • image_source is stored as item-level metadata (it does not match any schema table name).
  • objects matches the entity field name in the schema. Its value is a dict containing entity attributes and annotation data.
  • bboxes inside objects matches the annotation table name. Each bounding box is [x, y, w, h].
  • Entity attributes (category) are parallel arrays — one value per bounding box.
  • Items without objects (like img_003.jpg) have no pre-annotations.
  • Coordinates are auto-detected as normalized when all values fall within [0, 1].
Format details

The metadata.jsonl format mirrors the schema structure:

  • Top-level keys that match a view field (e.g. image) are treated as view references — the value is the filename relative to the split folder.
  • Top-level keys that match an entity field (e.g. objects) contain a nested dict. Keys inside this dict that match annotation table names (e.g. bboxes) are parsed as annotation data. All other keys are treated as entity attributes.
  • Top-level keys that don't match any view or entity field (e.g. image_source) are stored as item-level metadata.
  • Entity attributes and annotation arrays must have the same length — each index corresponds to one object.

Run the import

pixano data import ./my_data ./street_objects \
    --name "Street Objects" \
    --schema ./schema.py:StreetObjectsDatasetItem

Common options:

Option Description
--name Dataset name. Defaults to the source directory name.
--type Dataset type: image (default), video, or vqa.
--mode create (default, fails if exists), overwrite, or add (append).
--schema Custom schema as path/to/file.py:ClassName. Uses the default schema if omitted.
Alternative: build the dataset with Python

You can also build the dataset programmatically:

from pathlib import Path
from pixano.datasets import DatasetInfo
from pixano.datasets.builders import ImageFolderBuilder

builder = ImageFolderBuilder(
    media_dir=Path("./my_data/media"),
    library_dir=Path("./my_data/library"),
    dataset_item=StreetObjectsDatasetItem,
    info=DatasetInfo(
        name="Street Objects",
        description="Street scene images with object detection pre-annotations",
    ),
    dataset_path="street_objects",
)

dataset = builder.build(mode="create")
print(f"Dataset built: {dataset.num_rows} items")

Try it: Pascal VOC 2007

The repository includes a ready-to-run example that downloads a sample of the Pascal VOC 2007 dataset and produces a Pixano-compatible folder. Use it to test the full import workflow with real data.

1. Generate the sample folder

pip install pillow                        # required by the script
python examples/voc/generate_sample.py ./voc_sample --num-samples 50

This downloads VOC 2007, samples 50 images per split, converts the XML annotations to metadata.jsonl files with normalized bounding boxes, and writes everything to ./voc_sample/.

The resulting folder looks like:

voc_sample/
  train/
    000032.jpg
    000045.jpg
    ...
    metadata.jsonl
  validation/
    000007.jpg
    000019.jpg
    ...
    metadata.jsonl

Each line in metadata.jsonl follows the format described above:

{"image": "000032.jpg", "objects": {"bboxes": [[0.078, 0.090, 0.756, 0.792], ...], "category": ["aeroplane", ...], "is_difficult": [false, ...]}}

2. Review the schema

The example schema is at examples/voc/schema.py:

from pixano.datasets.workspaces import DefaultImageDatasetItem
from pixano.features import Entity


class VOCObject(Entity):
    category: str = ""
    is_difficult: bool = False


class VOCDatasetItem(DefaultImageDatasetItem):
    objects: list[VOCObject]

Same pattern as the street-objects example — a custom Entity subclass with domain-specific attributes, plugged into DefaultImageDatasetItem.

3. Import and serve

pixano init ./my_data
pixano data import ./my_data ./voc_sample \
    --name "VOC 2007 Sample" \
    --schema examples/voc/schema.py:VOCDatasetItem
pixano server run ./my_data

Open http://127.0.0.1:7492 to browse the imported VOC images and bounding boxes.

Step 3 — Launch the Server

pixano server run ./my_data

Open http://127.0.0.1:7492 in your browser to explore and annotate your dataset.

  • --host — bind address (e.g. 0.0.0.0 for network access). Default: 127.0.0.1.
  • --port — port number. Default: 7492.

Explore Your Data

The web UI lets you browse items, view annotations, and start annotating. You can also interact with your data programmatically.

Explore the REST API

While the server is running, interactive API documentation is available at:

  • Swagger UI: http://127.0.0.1:7492/docs
  • ReDoc: http://127.0.0.1:7492/redoc

List all datasets and find the dataset ID:

curl -s http://localhost:7492/datasets/info | python -m json.tool

The response includes an id field for each dataset. Use this ID in subsequent requests (referred to as <DATASET_ID> below).

Get items (first 3):

curl -s 'http://localhost:7492/items/<DATASET_ID>?limit=3' | python -m json.tool

Get image views:

curl -s 'http://localhost:7492/views/<DATASET_ID>/image?limit=2' | python -m json.tool

Get entities (objects with category):

curl -s 'http://localhost:7492/entities/<DATASET_ID>/objects?limit=5' | python -m json.tool

Get bounding boxes for a specific item:

curl -s 'http://localhost:7492/annotations/<DATASET_ID>/bboxes?item_ids=<ITEM_ID>' | python -m json.tool

Browse dataset (same endpoint the UI uses):

curl -s 'http://localhost:7492/browser/<DATASET_ID>?limit=10' | python -m json.tool
Verify with the Python API

Load the dataset and inspect items to confirm the import worked:

from pathlib import Path
from pixano.datasets import Dataset

dataset = Dataset(Path("./my_data/library/street_objects"), media_dir=Path("./my_data/media"))
items = dataset.get_dataset_items(limit=3)
for item in items:
    print(f"Item {item.id} | split={item.split}")

Next Steps