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
- Bump
__version__insrc/vdschema/_version.py(used at runtime and byuv build). - 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11c5d7e922ce8e9dbdf044c18cbaab35a9d219a11772b750b91cb1a8d58d6a50
|
|
| MD5 |
bc96c34c1a9448af6d141c91dc916fdb
|
|
| BLAKE2b-256 |
ea5378ad9134bd19de5185100c2b848c4290a961094bb89b307ceed5c94b4cf6
|
Provenance
The following attestation bundles were made for vdschema-0.2.0.tar.gz:
Publisher:
publish.yml on Arrkwen/vdschema
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vdschema-0.2.0.tar.gz -
Subject digest:
11c5d7e922ce8e9dbdf044c18cbaab35a9d219a11772b750b91cb1a8d58d6a50 - Sigstore transparency entry: 1591060756
- Sigstore integration time:
-
Permalink:
Arrkwen/vdschema@30e462261dc292202e8e686ebe554e011c7384af -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/Arrkwen
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@30e462261dc292202e8e686ebe554e011c7384af -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
008736301235f3ad1505a71e5af6ea4830a46b2636721e67db70d2c7c8952870
|
|
| MD5 |
6735440c0ead69e868344e955587c780
|
|
| BLAKE2b-256 |
ce12deafcba8c1fc2c1e3e7f821165eb68818396d6f4c32be90eb07964a7a3d9
|
Provenance
The following attestation bundles were made for vdschema-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on Arrkwen/vdschema
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vdschema-0.2.0-py3-none-any.whl -
Subject digest:
008736301235f3ad1505a71e5af6ea4830a46b2636721e67db70d2c7c8952870 - Sigstore transparency entry: 1591060776
- Sigstore integration time:
-
Permalink:
Arrkwen/vdschema@30e462261dc292202e8e686ebe554e011c7384af -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/Arrkwen
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@30e462261dc292202e8e686ebe554e011c7384af -
Trigger Event:
release
-
Statement type: