Annotations

Annotation resource

Access annotations through client.annotations.

📋

Prerequisites

Get started with Vi SDK →


Methods

list()

List annotations for an asset.

# Basic listing
annotations = client.annotations.list(
    dataset_id="dataset_abc123",
    asset_id="asset_xyz789"
)

for annotation in annotations.items:
    print(f"ID: {annotation.annotation_id}")
    print(f"Type: {annotation.spec.bound_type}")
# With custom pagination
from vi.api.types import PaginationParams

annotations = client.annotations.list(
    dataset_id="dataset_abc123",
    asset_id="asset_xyz789",
    pagination=PaginationParams(page_size=100)
)

for annotation in annotations.items:
    print(f"{annotation.annotation_id}: {annotation.spec.bound_type}")
# Using dict for pagination
annotations = client.annotations.list(
    dataset_id="dataset_abc123",
    asset_id="asset_xyz789",
    pagination={"page_size": 100}
)
# Iterate through all pages
for page in client.annotations.list(
    dataset_id="dataset_abc123",
    asset_id="asset_xyz789"
):
    for annotation in page.items:
        print(f"{annotation.annotation_id}: {annotation.spec.bound_type}")
# Collect all annotations for an asset
all_annotations = list(
    client.annotations.list(
        dataset_id="dataset_abc123",
        asset_id="asset_xyz789"
    ).all_items()
)

print(f"Total annotations: {len(all_annotations)}")

Parameters:

ParameterTypeDescriptionDefault
dataset_idstrDataset identifierRequired
asset_idstrAsset identifierRequired
paginationPaginationParams | dictPagination settingsPaginationParams()

Returns: PaginatedResponse[Annotation]


get()

Get a specific annotation.

# Basic usage
annotation = client.annotations.get(
    dataset_id="dataset_abc123",
    asset_id="asset_xyz789",
    annotation_id="annotation_xyz789"
)

print(f"Type: {annotation.spec.bound_type}")
print(f"Content: {annotation.spec.contents}")
# Access caption content
annotation = client.annotations.get(
    dataset_id="dataset_abc123",
    asset_id="asset_xyz789",
    annotation_id="annotation_xyz789"
)

if hasattr(annotation.spec.contents, 'contents'):
    # It's a Caption
    print(f"Caption: {annotation.spec.contents.contents}")
# Access bounding box content
annotation = client.annotations.get(
    dataset_id="dataset_abc123",
    asset_id="asset_xyz789",
    annotation_id="annotation_xyz789"
)

if hasattr(annotation.spec.contents, 'bound'):
    # It's a PhraseGroundingBoundingBox
    print(f"Bounds: {annotation.spec.contents.bound}")
    print(f"Target span: {annotation.spec.contents.target}")
# Display detailed information
annotation = client.annotations.get(
    dataset_id="dataset_abc123",
    asset_id="asset_xyz789",
    annotation_id="annotation_xyz789"
)
annotation.info()  # Prints formatted annotation summary

Parameters:

ParameterTypeDescription
dataset_idstrDataset identifier
asset_idstrAsset identifier
annotation_idstrAnnotation identifier

Returns: Annotation


upload()

Upload annotations to a dataset.

# Upload single JSONL file
result = client.annotations.upload(
    dataset_id="dataset_abc123",
    paths="annotations.jsonl",
    wait_until_done=True
)

print(f"✓ Imported: {result.total_annotations}")
# Upload folder of annotation files
result = client.annotations.upload(
    dataset_id="dataset_abc123",
    paths="./annotations/",
    wait_until_done=True
)

print(result.summary())
# Upload multiple specific files
result = client.annotations.upload(
    dataset_id="dataset_abc123",
    paths=["batch1.jsonl", "batch2.jsonl"],
    wait_until_done=True
)
# Upload and verify count
def upload_and_verify(dataset_id: str, annotations_path: str) -> bool:
    """Upload annotations and verify success."""

    # Count annotations in file
    with open(annotations_path) as f:
        local_count = sum(1 for _ in f)

    # Upload
    result = client.annotations.upload(
        dataset_id=dataset_id,
        paths=annotations_path,
        wait_until_done=True
    )

    # Verify
    print(f"Local: {local_count}")
    print(f"Imported: {result.total_annotations}")

    return result.total_annotations == local_count

success = upload_and_verify("dataset_abc123", "annotations.jsonl")
# Create annotations programmatically and upload
import json
from pathlib import Path

def create_and_upload_annotations(dataset_id: str, images_dir: str):
    """Create annotations for images and upload."""
    annotations = []

    for image_path in Path(images_dir).glob("*.jpg"):
        annotation = {
            "asset_id": image_path.stem,
            "caption": f"Image of {image_path.stem}",
            "grounded_phrases": []
        }
        annotations.append(annotation)

    # Save to JSONL
    output_file = "generated_annotations.jsonl"
    with open(output_file, "w") as f:
        for annotation in annotations:
            json.dump(annotation, f)
            f.write("\n")

    # Upload
    result = client.annotations.upload(
        dataset_id=dataset_id,
        paths=output_file,
        wait_until_done=True
    )

    return result

result = create_and_upload_annotations("dataset_abc123", "./images")

Parameters:

ParameterTypeDescriptionDefault
dataset_idstrDataset identifierRequired
pathsstr | list[str]File/folder pathsRequired
wait_until_doneboolWait for completionTrue

Returns: AnnotationUploadResult

🚧

Dataset type support

Annotation upload is currently supported for:

  • Phrase Grounding datasets — Upload captions with grounded phrases and bounding boxes
  • VQA datasets — Upload question-answer pairs
  • 🚧 Freeform datasets — Coming soon

Upload format is Vi JSONL for all dataset types.


delete()

Delete an annotation.

# Delete single annotation
deleted = client.annotations.delete(
    dataset_id="dataset_abc123",
    asset_id="asset_xyz789",
    annotation_id="annotation_xyz789"
)
# Delete all annotations for an asset
annotations = client.annotations.list(
    dataset_id="dataset_abc123",
    asset_id="asset_xyz789"
)

for annotation in annotations.items:
    client.annotations.delete(
        dataset_id="dataset_abc123",
        asset_id="asset_xyz789",
        annotation_id=annotation.annotation_id
    )
    print(f"Deleted: {annotation.annotation_id}")

Parameters:

ParameterTypeDescription
dataset_idstrDataset identifier
asset_idstrAsset identifier
annotation_idstrAnnotation identifier

Returns: DeletedAnnotation


wait_until_done()

Wait for an import session to complete.

# Basic usage
status = client.annotations.wait_until_done(
    dataset_id="dataset_abc123",
    annotation_import_session_id="session_id"
)

print(f"Total: {status.status.annotations.status_count}")
# After async upload
result = client.annotations.upload(
    dataset_id="dataset_abc123",
    paths="annotations.jsonl",
    wait_until_done=False
)

# Do other work...

# Then wait for completion
status = client.annotations.wait_until_done(
    dataset_id="dataset_abc123",
    annotation_import_session_id=result.session_id
)

print(f"Import complete: {status.status.annotations.status_count}")

Parameters:

ParameterTypeDescription
dataset_idstrDataset identifier
annotation_import_session_idstrSession identifier

Returns: AnnotationImportSession


download()

Download annotations without assets.

# Download with default settings (ViJsonl format)
result = client.annotations.download(
    dataset_id="dataset_abc123"
)

print(result.summary())
# Download in COCO format
from vi.api.resources.datasets.types import (
    DatasetExportFormat,
    DatasetExportSettings
)

result = client.annotations.download(
    dataset_id="dataset_abc123",
    export_settings=DatasetExportSettings(
        format=DatasetExportFormat.COCO
    ),
    save_dir="./annotations"
)

print(f"Downloaded to: {result.save_dir}")
# Download in YOLO format
result = client.annotations.download(
    dataset_id="dataset_abc123",
    export_settings={"format": DatasetExportFormat.YOLO_DARKNET},
    save_dir="./yolo_annotations"
)
# Download using existing export
result = client.annotations.download(
    dataset_id="dataset_abc123",
    dataset_export_id="export_xyz789"
)
# Download without progress bars (CI/CD environments)
result = client.annotations.download(
    dataset_id="dataset_abc123",
    show_progress=False,
    overwrite=True
)
# Download and process annotations
from pathlib import Path
import json

result = client.annotations.download(
    dataset_id="dataset_abc123",
    export_settings={"format": DatasetExportFormat.VI_JSONL},
    save_dir="./annotations"
)

# Process downloaded annotations
annotation_files = Path(result.save_dir).glob("*.jsonl")
for file in annotation_files:
    with open(file) as f:
        for line in f:
            annotation = json.loads(line)
            print(f"Asset: {annotation['asset_id']}")

Parameters:

ParameterTypeDescriptionDefault
dataset_idstrDataset identifierRequired
dataset_export_idstr | NoneExisting export IDNone
export_settingsDatasetExportSettings | dict | NoneExport format configurationNone (ViJsonl)
save_dirPath | strDirectory to save annotations~/.datature/vi/annotations/
overwriteboolOverwrite existing filesFalse
show_progressboolShow progress barsTrue

Returns: DatasetDownloadResult

💡

Supported export formats

Available formats depend on your dataset type:

Phrase Grounding datasets:

  • CsvFourCorner — CSV with 4-corner coordinates
  • CsvWidthHeight — CSV with width/height format
  • Coco — COCO JSON format
  • PascalVoc — Pascal VOC XML format
  • YoloDarknet — YOLO Darknet TXT format
  • YoloKerasPytorch — YOLO Keras/PyTorch format
  • ViJsonl — Vi JSONL format (default)
  • ViTfrecord — Vi TFRecord format

VQA datasets:

  • ViJsonl — Vi JSONL format (default)
  • ViTfrecord — Vi TFRecord format

Freeform datasets: 🚧 Coming soon

  • ViJsonl — Vi JSONL format (default)

The ViFull format is not supported for annotation-only downloads.

Performance tip

Downloading annotations without assets is much faster than downloading the full dataset. Use this method when you only need to update or review annotations, or when working with large datasets where asset files are not needed.

⚠️

Format compatibility

Using incompatible export formats will raise a ViValidationError. For example, COCO format is not supported for VQA or freeform datasets. Make sure to use formats compatible with your dataset type.


Response types

Annotation

Main annotation response object.

from vi.api.resources.datasets.annotations.responses import Annotation
PropertyTypeDescription
annotation_idstrUnique identifier
asset_idstrParent asset ID
organization_idstrOrganization ID
dataset_idstrDataset ID
specAnnotationSpecAnnotation specification
metadataResourceMetadataMetadata
self_linkstrAPI link
etagstrEntity tag

Methods:

MethodReturnsDescription
info()NoneDisplay formatted annotation information

AnnotationSpec

from vi.api.resources.datasets.annotations.responses import AnnotationSpec
PropertyTypeDescription
bound_typestrBoundary type (box, polygon, etc.)
tagintTag identifier
aggregationAnnotationAggregationAggregation info
contentsAnnotationContentAnnotation content

AnnotationAggregation

from vi.api.resources.datasets.annotations.responses import AnnotationAggregation
PropertyTypeDescription
wholeWholeReference to complete annotation
this_partThisPartThis part's role info

Whole

from vi.api.resources.datasets.annotations.responses import Whole
PropertyTypeDescription
idstrParent annotation ID

ThisPart

from vi.api.resources.datasets.annotations.responses import ThisPart
PropertyTypeDescription
roleRolesPart role type

AnnotationContent

Union type for annotation content variants.

from vi.api.resources.datasets.annotations.responses import AnnotationContent

AnnotationContent = Caption | PhraseGroundingBoundingBox

Caption

Caption content for annotations.

from vi.api.resources.datasets.annotations.responses import Caption
PropertyTypeDescription
contentsstrCaption text

PhraseGroundingBoundingBox

Bounding box for phrase grounding.

from vi.api.resources.datasets.annotations.responses import PhraseGroundingBoundingBox
PropertyTypeDescription
boundlist[list[float]]4 coordinate pairs [[x1,y1], [x2,y1], [x2,y2], [x1,y2]]
targettuple[int, int]Phrase span (start_index, end_index)

AnnotationUploadResult

Result from uploading annotations.

PropertyTypeDescription
session_idstrImport session ID
total_annotationsintTotal annotations imported
sessionAnnotationImportSessionSession details

Methods:

MethodReturnsDescription
summary()strGet summary string

AnnotationImportSession

from vi.api.resources.datasets.annotations.responses import AnnotationImportSession
PropertyTypeDescription
userstrInitiating user ID
organization_idstrOrganization ID
dataset_idstrDataset ID
annotation_import_session_idstrSession identifier
self_linkstrAPI link
etagstrEntity tag
metadataResourceMetadataMetadata
specAnnotationImportSpecImport specification
statusAnnotationImportSessionStatusSession status

AnnotationImportSpec

from vi.api.resources.datasets.annotations.responses import AnnotationImportSpec
PropertyTypeDescription
upload_beforeintUpload deadline timestamp
failure_policiesAnnotationImportFailurePolicyFailure handling
sourceAnnotationImportSourceImport source type

AnnotationImportFailurePolicy

from vi.api.resources.datasets.annotations.responses import AnnotationImportFailurePolicy
PropertyTypeDescription
on_bad_annotationFailurePolicyBad annotation handling
on_bad_fileFailurePolicyBad file handling
on_overwrittenFailurePolicyOverwrite handling

AnnotationImportSessionStatus

from vi.api.resources.datasets.annotations.responses import AnnotationImportSessionStatus
PropertyTypeDescription
conditionslist[AnnotationImportSessionCondition]Session conditions
filesAnnotationFileStatusFile status
annotationsAnnotationStatusAnnotation status
reasonstr | NoneStatus reason

AnnotationImportSessionCondition

from vi.api.resources.datasets.annotations.responses import AnnotationImportSessionCondition
PropertyTypeDescription
conditionstrCondition name
statusConditionStatusCondition status
last_transition_timeint | floatTransition timestamp
reasonConditionReason | NoneCondition reason

AnnotationFileStatus

from vi.api.resources.datasets.annotations.responses import AnnotationFileStatus
PropertyTypeDescription
page_countintPages processed
total_size_bytesintTotal size in bytes
status_countdict[str, int]Status distribution

AnnotationStatus

from vi.api.resources.datasets.annotations.responses import AnnotationStatus
PropertyTypeDescription
status_countdict[str, int]Status distribution

ExportedAnnotations

from vi.api.resources.datasets.annotations.responses import ExportedAnnotations
PropertyTypeDescription
kindstrResource kind
organization_idstrOrganization ID
dataset_idstrDataset ID
dataset_export_idstrExport identifier
specdict[str, Any]Export specification
statusExportedAnnotationsStatusExport status
metadataResourceMetadataMetadata

ExportedAnnotationsStatus

from vi.api.resources.datasets.annotations.responses import ExportedAnnotationsStatus
PropertyTypeDescription
conditionslist[dict]Status conditions
download_urlExportedAnnotationsDownloadUrlDownload URL

ExportedAnnotationsDownloadUrl

from vi.api.resources.datasets.annotations.responses import ExportedAnnotationsDownloadUrl
PropertyTypeDescription
urlstrDownload URL
expires_atintExpiration timestamp

Enums

Roles

from vi.api.resources.datasets.annotations.responses import Roles
ValueDescription
CAPTIONCaption annotation
PHRASE_GROUNDING_BOUNDING_BOXPhrase grounding bounding box

FailurePolicy

from vi.api.resources.datasets.annotations.responses import FailurePolicy
ValueDescription
WARNLog warning and continue
REJECT_FILEReject the file
REJECT_SESSIONReject entire session

AnnotationImportSource

from vi.api.resources.datasets.annotations.responses import AnnotationImportSource
ValueDescription
UPLOADED_INDIVIDUAL_FILESUploaded individual files

ConditionReason

from vi.api.resources.datasets.annotations.responses import ConditionReason
ValueDescription
CANCELLED_BY_USERCancelled by user
USER_UPLOAD_ERROREDUpload error

Annotation formats

Phrase Grounding format

{
  "asset_id": "asset_123",
  "caption": "A cat sitting on a couch",
  "grounded_phrases": [
    {
      "phrase": "cat",
      "bbox": [0.1, 0.2, 0.5, 0.7]
    },
    {
      "phrase": "couch",
      "bbox": [0.6, 0.2, 0.9, 0.7]
    }
  ]
}

Fields:

FieldTypeDescription
asset_idstrAsset identifier (required)
captionstrCaption text
grounded_phraseslist[GroundedPhrase]List of grounded phrases

GroundedPhrase Schema:

FieldTypeDescription
phrasestrPhrase text
bboxlist[float]Normalized bbox [x_min, y_min, x_max, y_max]

VQA Format

{
  "asset_id": "asset_123",
  "contents": {
    "interactions": [
      {
        "question": "What is the cat doing?",
        "answer": "Sitting on a couch"
      }
    ]
  }
}

Fields:

FieldTypeDescription
asset_idstrAsset identifier (required)
contents.interactionslist[Interaction]Q&A pairs

Interaction Schema:

FieldTypeDescription
questionstrQuestion text
answerstrAnswer text

Freeform Format

🚧

Coming soon

Freeform annotation format support is currently in development. Check back for updates on the format specification and upload capabilities.


Coordinate system

Bounding box coordinates are normalized to [0, 1] range:

  • x_min: Left edge (0 = left, 1 = right)
  • y_min: Top edge (0 = top, 1 = bottom)
  • x_max: Right edge
  • y_max: Bottom edge

Convert to Pixels:

def to_pixels(bbox: list[float], width: int, height: int) -> list[int]:
    """Convert normalized bbox to pixel coordinates."""
    x_min, y_min, x_max, y_max = bbox
    return [
        int(x_min * width),
        int(y_min * height),
        int(x_max * width),
        int(y_max * height)
    ]

# Example
bbox = [0.1, 0.2, 0.5, 0.7]
pixel_bbox = to_pixels(bbox, 1920, 1080)
# Result: [192, 216, 960, 756]

Convert from Pixels:

def from_pixels(bbox: list[int], width: int, height: int) -> list[float]:
    """Convert pixel coordinates to normalized bbox."""
    x_min, y_min, x_max, y_max = bbox
    return [
        x_min / width,
        y_min / height,
        x_max / width,
        y_max / height
    ]

Related resources