Skip to main content

Unified annotation schema and JSONL IO for vision datasets.

Project description

VDschema

VDschema: A unified annotation schema for Vision Datasets with JSONL I/O support.

It supports two primary workflows:

  • Write — annotation pipelines produce unified JSONL annotations data
  • Read — training pipelines load JSONL into typed Python annotation objects.

Supported tasks

Task TaskType Task_dict Python type
detection TaskType.DETECTION {id: name} DetectionAnnotation
keypoint TaskType.KEYPOINT {id: name} KeypointAnnotation
segmentation TaskType.SEGMENTATION {id: name} SegmentationAnnotation
classification TaskType.CLASSIFICATION {head: {id: name}} ClassificationAnnotation
relationship TaskType.RELATIONSHIP {detection: {...}, relationship: {...}} RelationshipAnnotation
vlm TaskType.VLM VlmAnnotation
conversation TaskType.CONVERSATION ConversationAnnotation
action TaskType.ACTION {id: name} ActionAnnotation

JSON examples for each task: src/vdschema/schema/example.md.

Schema definitions: annotation_schema.json, annotation_dict.json.

Install

PyPI (pip)

pip install vdschema

PyPI (uv)

uv pip install vdschema

# Add as a project dependency if need
uv add vdschema

From source (git)

git clone https://github.com/Arrkwen/vdschema.git
cd vdschema
pip install -e .

Basic Usage

Detection

from vdschema import AnnotationReader, AnnotationWriter, TaskType

writer = AnnotationWriter(
    TaskType.DETECTION,
    task_dict={1: "person", 2: "car"},
)
writer.append(
    filename="images/sample.jpg",
    width=640,
    height=480,
    instances=[
        {"id": 0, "category_id": 1, "bbox": [10, 20, 100, 200]},
    ],
)
# default writes to output/detection/
writer.save()

# annotations: list[DetectionAnnotation]; task_dict: plain dict (id -> name)
annotations, task_dict = AnnotationReader(
    TaskType.DETECTION, writer.save_dir()
).load()
print(task_dict)  # {1: "person", 2: "car"}

# custom output directory
output_dir="my_dataset/detection"
writer = AnnotationWriter(
    TaskType.DETECTION,
    task_dict={1: "person", 2: "car"},
    task_dir=output_dir
)
writer.save()

# raw JSON objects per line
annotations_raw = list(
    AnnotationReader(TaskType.DETECTION, output_dir).iter_raw()
)

Keypoint

from vdschema import AnnotationReader, AnnotationWriter, TaskType

writer = AnnotationWriter(TaskType.KEYPOINT, task_dict={1: "person"})
writer.append(
    filename="images/keypoint_001.jpg",
    width=640,
    height=480,
    instances=[
        {
            "id": 0,
            "category_id": 1,
            "bbox": [200, 100, 380, 420],
            "keypoints": {"body": [[210, 120, 2], [230, 140, 2]]},
        }
    ],
)
writer.save()
annotations, task_dict = AnnotationReader(
    TaskType.KEYPOINT, writer.save_dir()
).load()

Segmentation

from vdschema import (
    AnnotationReader,
    AnnotationWriter,
    Bbox,
    SegmentationRLE,
    TaskType,
)
import numpy as np

mask = np.zeros((480, 640), dtype=np.uint8)
mask[20:80, 30:120] = 1
task_dir = "output/segmentation"
writer = AnnotationWriter(
    TaskType.SEGMENTATION,
    task_dict={1: "person"},
    task_dir=task_dir,
)
# if bbox is not xyxy format, support other format(xywh, cxxywh)
writer.append(
    filename="images/segmentation_001.jpg",
    width=640,
    height=480,
    instances=[
        {
            "id": 0,
            "category_id": 1,
            "bbox": Bbox.from_xywh([120, 30, 200, 180]),
            "segmentation": SegmentationRLE.from_mask(mask),
        }
    ],
)
writer.save()
annotations, task_dict = AnnotationReader(TaskType.SEGMENTATION, task_dir).load()

Classification

from vdschema import AnnotationReader, AnnotationWriter, TaskType

writer = AnnotationWriter(
    TaskType.CLASSIFICATION,
    task_dict={
        "hair_color": {1: "black", 2: "brown"},
        "age": {1: "young", 2: "middle-aged"},
    },
)
writer.append(
    filename="images/classification_001.jpg",
    width=640,
    height=480,
    categories=[
        {"category_type": "hair_color", "category_ids": [1]},
        {"category_type": "age", "category_ids": [1]},
    ],
)
writer.save()
annotations, task_dict = AnnotationReader(
    TaskType.CLASSIFICATION, writer.save_dir()
).load()

Relationship

from vdschema import AnnotationReader, AnnotationWriter, TaskType

writer = AnnotationWriter(
    TaskType.RELATIONSHIP,
    task_dict={
        "detection": {1: "person", 2: "car"},
        "relationship": {0: "near", 1: "left_of"},
    },
)
writer.append(
    filename="images/relationship_001.jpg",
    width=640,
    height=480,
    instances=[
        {"id": 0, "category_id": 1, "bbox": [10, 20, 100, 200]},
        {"id": 1, "category_id": 2, "bbox": [120, 30, 200, 180]},
    ],
    relationships=[{"subject_id": 0, "object_id": 1, "relation_type": "near"}],
)
writer.save()
annotations, task_dict = AnnotationReader(
    TaskType.RELATIONSHIP, writer.save_dir()
).load()

VLM

No label dictionary file. Omit task_dict for tasks without vocabulary.

from vdschema import AnnotationReader, AnnotationWriter, TaskType

writer = AnnotationWriter(TaskType.VLM)
writer.append(
    filename="images/vlm_001.jpg",
    width=640,
    height=480,
    description="A street intersection with three people crossing.",
)
writer.save()
annotations, task_dict = AnnotationReader(
    TaskType.VLM, writer.save_dir()
).load()
assert task_dict is None

Conversation

from vdschema import (
    AnnotationReader,
    AnnotationWriter,
    ConversationRole,
    ConversationTurn,
    TaskType,
)

writer = AnnotationWriter(TaskType.CONVERSATION)
writer.append(
    filename="images/conversation_001.jpg",
    width=640,
    height=480,
    conversations=[
        ConversationTurn(
            role=ConversationRole.USER,
            image="images/conversation_001.jpg",
            text="Describe the image.",
        ),
        ConversationTurn(role=ConversationRole.ASSISTANT, text="A street scene."),
    ],
)
writer.save()
annotations, task_dict = AnnotationReader(
    TaskType.CONVERSATION, writer.save_dir()
).load()

Action

from vdschema import AnnotationReader, AnnotationWriter, TaskType

writer = AnnotationWriter(
    TaskType.ACTION,
    task_dict={2: "fall", 4: "walk"},
)
writer.append(
    filename="videos/action_001.mp4",
    width=1920,
    height=1080,
    description="An elderly person falls down.",
    actions=[
        {
            "action_id": 2,
            "track_id": 0,
            "start_idx": 4,
            "end_idx": 6,
            "description": "An elderly person falls down.",
            "tracks": [{"frame_idx": 0, "bbox": [520, 300, 620, 380]}],
        }
    ],
)
writer.save()
annotations, task_dict = AnnotationReader(
    TaskType.ACTION, writer.save_dir()
).load()

More examples

Full runnable tests: tests/test_annotation_format.py. Run:

uv run pytest

Each task follows the same pattern: pick a TaskType, pass a plain task_dict (when needed), writer.append(...), writer.save(), then annotations, task_dict = AnnotationReader(task_type, writer.save_dir()).load(). By default, files go to output/{task_type}/; pass task_dir to override.

Development

git clone https://github.com/Arrkwen/vdschema.git
cd vdschema
uv sync --group dev
uv run pytest
uv run ruff check .
uv build

Publishing

  1. Bump __version__ in src/vdschema/_version.py (used at runtime and by uv build).
  2. Create a Published GitHub Release with a new tag (e.g. 0.1.0).

The publish.yml workflow runs on release publish and uploads the build to PyPI. You can also re-run it manually from Actions → vdschema-publisher.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vdschema-0.2.0.tar.gz (22.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vdschema-0.2.0-py3-none-any.whl (20.9 kB view details)

Uploaded Python 3

File details

Details for the file vdschema-0.2.0.tar.gz.

File metadata

  • Download URL: vdschema-0.2.0.tar.gz
  • Upload date:
  • Size: 22.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for vdschema-0.2.0.tar.gz
Algorithm Hash digest
SHA256 11c5d7e922ce8e9dbdf044c18cbaab35a9d219a11772b750b91cb1a8d58d6a50
MD5 bc96c34c1a9448af6d141c91dc916fdb
BLAKE2b-256 ea5378ad9134bd19de5185100c2b848c4290a961094bb89b307ceed5c94b4cf6

See more details on using hashes here.

Provenance

The following attestation bundles were made for vdschema-0.2.0.tar.gz:

Publisher: publish.yml on Arrkwen/vdschema

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vdschema-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: vdschema-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 20.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for vdschema-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 008736301235f3ad1505a71e5af6ea4830a46b2636721e67db70d2c7c8952870
MD5 6735440c0ead69e868344e955587c780
BLAKE2b-256 ce12deafcba8c1fc2c1e3e7f821165eb68818396d6f4c32be90eb07964a7a3d9

See more details on using hashes here.

Provenance

The following attestation bundles were made for vdschema-0.2.0-py3-none-any.whl:

Publisher: publish.yml on Arrkwen/vdschema

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page