Skip to content

Python API Reference

HIManish-G edited this page Nov 5, 2026 · 2 revisions

Python API reference

Everything below is importable as shown. from vidlabel import ... gives the names listed in vidlabel.__all__; the rest live in the module named in each heading. Concepts and examples are in the other pages (start with concepts and getting started).

Datasets

Dataset

Dataset(path: str | Path)

A vidlabel dataset: a folder of plain files (see docs/SPEC.md). Make one with Dataset.create(path), open one with Dataset.open(path). There is no server and no lock: one writer per video at a time.

Dataset.add_annotations

add_annotations(
    rows: Iterable[Mapping[str, Any]],
    video_id: str | None = None,
    *,
    create_labels: bool = False,
    validate: bool = True,
    coords: str = 'source',
) -> list[int]

Append annotations. Rows are dicts using the column names (plus box=(x0,y0,x1,y1) shorthand).

A mask row may pass mask=<bool ndarray HxW>; it is encoded and its box filled in automatically. Returns the new annotation ids. Raises ValidationError (writing nothing) if any row is invalid.

coords="source" (default): boxes/masks are in full source-video pixels. coords="view": boxes/masks are in the pixels of the row's view_id (the crop as the pipeline saw it) and are converted to source coordinates here (offset, plus scaling if the view has output_size). Masks must be boolean arrays the size of the view region or of its output_size.

Dataset.add_labels

add_labels(labels: Iterable[str | Label | Mapping]) -> list[str]

Add labels to the shared vocabulary (existing names are left alone). Returns the names actually added.

Dataset.add_video

add_video(
    meta: VideoMetadata | Mapping[str, Any],
    exist_ok: bool = False,
    *,
    frame_index: FrameIndex | None = None,
) -> VideoMetadata

Register a video from a VideoMetadata (or a dict of its fields), e.g. from probe_video. The video file itself is never copied. Raises FileExistsError if the id exists, unless exist_ok=True (which replaces the metadata).

frame_index (from probe_frame_index) is stored next to the metadata; see set_frame_index.

Dataset.add_view

add_view(view: View | Mapping[str, Any], exist_ok: bool = False) -> View

Define a named crop and/or frame range of a registered video (see docs/views.md). The region must fit inside the video, frame_range inside its length, and allowed_labels must already be in the vocabulary.

Dataset.annotations

annotations(
    video_id: str | None = None,
    *,
    kind: str | Sequence[str] | None = None,
    label: str | Sequence[str] | None = None,
    origin: str | Sequence[str] | None = None,
    status: str | Sequence[str] | None = None,
    view_id: str | Sequence[str | None] | None = None,
    track_id: int | Sequence[int] | None = None,
    frames: tuple[int, int] | None = None,
    columns: Sequence[str] | None = None,
) -> pa.Table

Query annotations as a pyarrow Table. frames=(a, b) keeps annotations overlapping [a, b] inclusive.

view_id keeps rows labelled in that view (or any of several). Include None in a list to also (or only) get whole-frame rows that have no view, e.g. view_id=[None]. track_id keeps rows of those tracks; a track id means one object only within one video and view, so combine it with view_id.

Dataset.build

build(**kwargs)

Compile a training release: build(**kwargs) forwards to vidlabel.build.build and returns (Release, path).

Dataset.create

create(path: str | Path, exist_ok: bool = False) -> 'Dataset'

Make an empty dataset folder. Raises FileExistsError if one is already there, unless exist_ok=True.

Dataset.delete_annotations

delete_annotations(video_id: str, annotation_ids: Iterable[int], *, missing: str = 'raise') -> int

Remove annotations of one video by id. Returns how many rows were removed. Ids that are not in this video raise KeyError (and nothing is removed) unless missing="ignore".

Dataset.delete_track

delete_track(video_id: str, track_id: int, *, view_id: str | None = None) -> int

Remove every row of one track. To keep it out of releases without losing it, use update_track(..., status="rejected").

Dataset.derive_view_rows

derive_view_rows(
    video_id: str,
    view_id: str,
    *,
    labels: Sequence[str] | None = None,
    origins: Sequence[str] | None = None,
    statuses: Sequence[str] = ('unreviewed', 'approved'),
    dry_run: bool = False,
) -> dict[str, Any]

Copy whole-frame rows into a view, for the rows that belong there: frames inside the view's time windows, box or mask entirely inside its region, label allowed in the view. Each copy keeps its columns (including track_id), gets the view's view_id and a new annotation_id, and records its source in extra["derived_from"], so running this again copies only what is new. The originals stay. Rows that only partly overlap the region are not copied (cutting a box or mask would change it): they are counted in skipped_partial.

Returns copied, ids, already_derived, and the skip counts skipped_partial, skipped_outside, skipped_label, skipped_frames. With dry_run=True nothing is written and copied is how many rows would be. Whole-frame samples (no view_id) then see both the original and the copy: build whole-frame releases with build(view_ids=[None]) to leave the copies out.

Dataset.frame_index

frame_index(video_id: str) -> FrameIndex | None

The stored frame index of a video (timestamps and keyframes), or None if it has none.

Dataset.manifest (property)

The parsed manifest.json: spec version, video ids and view ids.

Dataset.open

open(path: str | Path) -> 'Dataset'

Open an existing dataset. Raises FileNotFoundError if the folder is not a vidlabel dataset.

Dataset.resolve_video_path

resolve_video_path(video_id: str, roots: Sequence[str | Path] = ()) -> Path | None

Find the video file: try each recorded source, then search roots for the same file name.

Dataset.set_frame_index

set_frame_index(video_id: str, index: FrameIndex) -> None

Store (or replace) the frame index of a registered video. Raises ValueError if it has a different number of frames than the metadata says, or was made from a different file (hash mismatch).

Dataset.stats

stats() -> dict[str, Any]

Counts for vidlabel info: totals, and breakdowns by kind, label, origin, status and view, plus one entry per video.

Dataset.tracks

tracks(video_id: str, *, view_id: str | None = None) -> list[dict[str, Any]]

One summary row per track of this video and view (view_id=None: whole-frame rows), sorted by track id: track_id, labels, n_rows, first_frame, last_frame, mean_confidence (None if unscored), statuses (counts) and origins.

Dataset.update_annotations

update_annotations(
    video_id: str,
    annotation_ids: Iterable[int],
    *,
    missing: str = 'raise',
    **values: Any,
) -> int

Set columns (e.g. status='approved', label='x') on the given annotations. Returns rows changed. Ids that are not in this video raise KeyError (and nothing changes) unless missing="ignore"; giving no column to change is an error. mask= (a boolean array of the frame's size, or encoded bytes) replaces the mask of mask rows and sets their box from it; the result is checked like any write (box, size, kind), so a mask on a box row is refused.

Dataset.update_track

update_track(video_id: str, track_id: int, *, view_id: str | None = None, **values: Any) -> int

Change columns on every row of one track at once, e.g. update_track("clip", 7, status="rejected"). A track id means one object only within one video and view, so pass view_id for a track made in a view. KeyError if there is no such track.

Dataset.validate

validate(deep: bool = False) -> list[Issue]

Check the whole dataset against the spec and return a list of Issues (empty = valid). Never raises for bad data. deep=True also decodes every mask and checks it against its box and the frame size.

Dataset.video

video(video_id: str) -> VideoMetadata

Metadata of one registered video. Raises KeyError for an unknown id.

Dataset.video_ids

video_ids() -> list[str]

Ids of all registered videos, sorted.

Dataset.view

view(view_id: str) -> View

One view definition. Raises KeyError for an unknown id.

Dataset.view_ids

view_ids() -> list[str]

Ids of all views, sorted.

Dataset.vocab (property)

The parsed vocab.json: the one shared label list.

Releases (compiled training tables)

vidlabel.build.build

build(
    ds: Dataset,
    *,
    statuses: Sequence[str] = ('unreviewed', 'approved'),
    origins: Sequence[str] | None = None,
    kinds: Sequence[str] | None = None,
    video_ids: Sequence[str] | None = None,
    view_ids: Sequence[str | None] | None = None,
    row_group_size: int = 20000,
    check: bool = True,
) -> tuple[Release, Path]

Write compiled/<release_id>/{annotations.parquet, vocab.json, release.json}. Rejected annotations are excluded by default.

vocab.json is a snapshot of the label vocabulary, so class indices derived from a pinned release cannot change when labels are added to the dataset later.

The release id is the hash of the file's bytes: building an unchanged dataset again gives the same id, so a training run can record it and later read exactly the same table. (Annotation ids are random, so re-creating the data gives a new id; share the folder.)

vidlabel.build.Compiled

vidlabel.build.Compiled(path: str | Path)

Reader for a compiled release. Fast clip lookups use Parquet row-group statistics (the table is sorted).

vidlabel.build.Compiled.clip

clip(
    video_id: str,
    start: int,
    end: int,
    columns: Sequence[str] | None = None,
    view_id: str | None = None,
) -> pa.Table

Annotations of one video overlapping frames [start, end] inclusive, reading only the row groups that can match.

view_id additionally keeps only rows labelled in that view.

vidlabel.build.Compiled.read

read(columns: Sequence[str] | None = None) -> pa.Table

The whole release table (or only columns) as a pyarrow Table, sorted by video, start frame and annotation id.

vidlabel.build.Compiled.release_id (property)

The release id, e.g. r3fa91c0b2d44: derived from the table's contents.

vidlabel.build.Compiled.row_groups_for

row_groups_for(video_id: str, start: int, end: int) -> list[int]

Indices of the Parquet row groups that can contain annotations of video_id overlapping frames [start, end].

vidlabel.build.Compiled.vocab (property)

The label vocabulary as it was when this release was built (None for releases made before it was saved).

Training samples

vidlabel.samples.FrameSamples

vidlabel.samples.FrameSamples(
    ds: Dataset,
    *,
    release: str | None = 'latest',
    view_id: str | None = None,
    videos: Sequence[str] | None = None,
    kinds: Sequence[str] = ('box', 'mask'),
    labels: Sequence[str] | None = None,
    origins: Sequence[str] | None = None,
    min_confidence: float | None = None,
    frames: str = 'annotated',
    stride: int = 1,
    with_masks: bool = False,
    box_format: str = 'xyxy',
    normalized: bool = False,
    label_offset: int = 0,
    roots: Sequence[str | Path] = (),
    cache: FrameCache | str | Path | None = None,
)

Which frames to train on, and what the targets for each are. Build it once, then read samples by (video, frame).

Parameters (all keyword-only except ds):

  • release: "latest", a release id such as "r3fa91c0b2d44" (pinned: the same rows and class indices every time), or None to read the live annotations (unreviewed and approved), which can change between runs.
  • view_id: train on that view's crop with only that view's annotations. None: full frames, all annotations.
  • videos: restrict to these video ids (default: all).
  • kinds: which kinds define the samples and targets, from "box", "mask", "marker". Markers always fill frame_labels; add "marker" to also sample frames that only have a marker.
  • labels: keep only these labels; class indices then follow this order (default: the vocabulary's order).
  • origins: keep only rows with these origins, e.g. ["human"].
  • min_confidence: drop rows whose confidence is below this (rows with no confidence are kept).
  • frames: "annotated" (frames with a selected annotation) or "all" (every frame).
  • stride: keep every Nth frame of that list.
  • with_masks: decode masks into the sample (masks, aligned with boxes).
  • box_format: "xyxy", "xywh" or "cxcywh"; normalized=True gives fractions of the image size.
  • label_offset: added to class indices (e.g. 1 for detectors that reserve 0 for background).
  • roots: folders to search for videos whose recorded path is stale.
  • cache: a FrameCache (or the folder of one made by vidlabel cache). Frames it holds are read as image files instead of decoded from video; anything missing falls back to the video. See docs/training.md.

See docs/training.md for what a sample contains.

vidlabel.samples.FrameSamples.describe

describe() -> dict

What to log next to a training run so it can be reproduced.

vidlabel.samples.FrameSamples.frames_of

frames_of(video_id: str) -> list[int]

The frame numbers sampled from one video, in order.

vidlabel.samples.FrameSamples.iter_plan

iter_plan(
    plan: ReadPlan,
    *,
    epoch: int = 0,
    shard: int = 0,
    num_shards: int = 1,
    shuffle_buffer: int = 0,
    max_open: int = 4,
) -> Iterator[dict]

Yield the samples of one shard of one epoch of plan (NumPy samples, like sample), reading each keyframe interval once.

Chunks come in the plan's order for this epoch; frames inside a chunk are read in increasing order. A frame requested several times is decoded once and yields one sample per request. shuffle_buffer (samples held in memory, each a whole decoded frame) mixes neighbouring frames; 0 yields them as read. max_open videos are kept open at a time. For several workers or ranks, give each its own shard out of num_shards: together they yield every sample exactly once.

vidlabel.samples.FrameSamples.plan_reads

plan_reads(requests: Iterable, *, seed: int = 0, block: int = 250) -> ReadPlan

Arrange the reading of a list of frames you chose, so each keyframe interval is decoded once (see ReadPlan).

requests holds (video_id, frame) pairs or integer positions in self.index (what numpy.random.Generator.choice(len(samples), ...) returns), in any order, with repeats allowed. Which frames to request, and how likely each is, is entirely up to you. Videos without a frame index are cut into blocks of block frames. Run the plan with iter_plan or PlannedStream.

vidlabel.samples.FrameSamples.reader

reader(video_id: str)

A reader for one video (read(frame), read_many(frames), close()), already set up with this object's view. It uses the video's frame index when it has one, and the frame cache if there is one that matches the video. Close it when done (or use with).

vidlabel.samples.FrameSamples.sample

sample(video_id: str, frame: int, image: np.ndarray) -> dict

The full sample for a frame whose pixels you already have.

vidlabel.samples.FrameSamples.targets

targets(video_id: str, frame: int, image_size: tuple[int, int]) -> dict

Boxes, labels, masks and frame labels for one frame. image_size is (width, height) of the image returned.

vidlabel.samples.FrameSamples.video_ids

video_ids() -> list[str]

Videos that contribute at least one sample.

vidlabel.samples.FrameSamples.video_path

video_path(video_id: str) -> Path

Where the video file is on this machine (recorded paths first, then roots). Raises FileNotFoundError.

Reading plans (vidlabel.readplan)

vidlabel.readplan.ReadPlan

vidlabel.readplan.ReadPlan(
    requests: Iterable[tuple[str, int]],
    *,
    indexes: Mapping[str, FrameIndex | None] | None = None,
    frame_counts: Mapping[str, int] | None = None,
    block: int = 250,
    seed: int = 0,
)

A reading plan for a list of requests. Build it with FrameSamples.plan_reads(...), or directly from (video_id, frame) pairs.

  • requests: (video_id, frame) pairs, in any order, repeats allowed (each repeat gives one more sample).
  • indexes: video id -> its FrameIndex (or None). Videos without one are cut into blocks of block frames, which is only an approximation of the real keyframes, so costs for them are estimates.
  • frame_counts: video id -> number of frames; if given, requests outside the video raise ValueError.
  • seed: makes the chunk order and its sharding reproducible; together with the epoch it fixes the order of every epoch.

vidlabel.readplan.ReadPlan.order

order(epoch: int = 0) -> list[Chunk]

All chunks in this epoch's order: the same for a given seed and epoch, different between epochs.

vidlabel.readplan.ReadPlan.shard

shard(epoch: int = 0, shard: int = 0, num_shards: int = 1) -> list[Chunk]

The chunks one worker (or rank) reads in this epoch, in reading order. Every chunk belongs to exactly one shard, whatever num_shards is, and shards get nearly the same number of samples (they differ by at most one chunk's worth).

vidlabel.readplan.ReadPlan.shard_sizes

shard_sizes(epoch: int = 0, num_shards: int = 1) -> list[int]

Samples per shard, to see how even the split is (distributed training wants equal numbers).

vidlabel.readplan.ReadPlan.summary

summary() -> dict[str, int]

samples, distinct_frames, videos, chunks, and the estimated frames decoded per epoch under this plan (est_decoded_frames) against reading the same requests one at a time (est_decoded_frames_one_by_one).

vidlabel.readplan.ReadPlan.video_ids (property)

vidlabel.readplan.Chunk

vidlabel.readplan.Chunk(
    video_id: str,
    start: int,
    frames: tuple[int, ...],
    repeats: tuple[int, ...],
    cost: int,
) -> None

The requested frames of one video inside one keyframe interval.

vidlabel.readplan.Chunk.n_samples (property)

PyTorch (vidlabel.torch_data, needs vidlabel[torch])

VideoStream, VideoFrames and PlannedStream are documented in training; collate_frames(batch) batches samples (stacking images that share a size) and to_tensors(sample) converts a NumPy sample to tensors (image as uint8 CHW).

Frames and previews (need OpenCV: vidlabel[video])

vidlabel.frames.read_frames

read_frames(
    path: str | Path,
    frame_indices: Iterable[int],
    meta: VideoMetadata,
    *,
    view: View | None = None,
) -> Iterator[tuple[int, np.ndarray]]

Yield (frame_index, RGB frame) for each requested index, in increasing order.

Raises ValueError if the file's frame size does not match meta (wrong or re-encoded file) and IndexError if a requested frame is past the end of the video.

vidlabel.frames.read_frame

read_frame(
    path: str | Path,
    index: int,
    meta: VideoMetadata,
    *,
    view: View | None = None,
) -> np.ndarray

One frame, RGB, displayed orientation. Decodes from the start of the video: use read_frames or FrameReader for many.

vidlabel.frames.check_video

check_video(path: str | Path, meta: VideoMetadata, *, verify_hash: bool = False) -> list[str]

Compare a video file with the metadata registered for it. Returns a list of problems (empty = consistent).

vidlabel.frames.crop_to_view

crop_to_view(frame: np.ndarray, view: View | None) -> np.ndarray

Crop a displayed-orientation frame to a view's region and resize to its output_size (if any).

vidlabel.frames.seek_mismatches

seek_mismatches(
    path: str | Path,
    meta: VideoMetadata,
    positions: Iterable[int],
    index: FrameIndex | None = None,
) -> list[int]

Frames at positions for which seeking does NOT return exactly what sequential decoding returns.

Empty means seeking is frame-exact at those positions. OpenCV's seek is exact for ordinary constant-frame-rate files but can land a few frames off on variable-frame-rate video or odd containers, which would silently misalign every box, so without an index seeking is only used after this check (see FrameReader). With index, the reader under test is the indexed one.

vidlabel.frames.FrameReader

vidlabel.frames.FrameReader(
    path: str | Path,
    meta: VideoMetadata,
    view: View | None = None,
    *,
    seek: str = 'auto',
    seek_threshold: int = 100,
    index: FrameIndex | None = None,
)

Keeps one video open and reads frames by index.

Reading forwards only decodes what is in between. Asking for an earlier frame, or one far ahead, means decoding from a keyframe; seek and index decide how that is done:

  • With index (a FrameIndex): the reader knows where the keyframes are, so it jumps only when that decodes fewer frames than reading on, and never inside a keyframe interval it is already in. After every frame it compares the decoder's timestamp with the index; if they ever disagree it warns and falls back to sequential decoding, so a wrong frame is never returned silently. This works on variable-frame-rate video too. An index for a different file (hash or frame count differs) is ignored with a warning.
  • Without an index, seek="auto" (default) seeks only if it has been verified for this file: not variable frame rate, and the frames returned after seeking are identical to sequential decoding at a few test positions (checked once per file per process). Otherwise it decodes sequentially, with a warning.
  • seek="off": never seek (the index, if any, is not used).
  • seek="always": seek without the pixel check (for tests and for seek_mismatches).

read_many reads several frames at once in the cheapest order. For training prefer VideoStream, which reads each video front to back. Not thread-safe; use one reader per process or thread.

vidlabel.frames.FrameReader.close

close() -> None

Release the open video file. Safe to call twice; the reader reopens it on the next read.

vidlabel.frames.FrameReader.read

read(index: int) -> np.ndarray

RGB frame index in displayed orientation, cropped to the view if there is one.

vidlabel.frames.FrameReader.read_many

read_many(indices: Iterable[int]) -> list[np.ndarray]

Frames for indices, in the order given. They are read in increasing order so each keyframe interval is decoded once however the request is ordered; repeated indices are decoded once and copied.

vidlabel.frames.FrameReader.seek_enabled (property)

Whether this reader will use seeking (without an index, runs the one-off check in "auto" mode).

vidlabel.preview.render_preview

render_preview(
    ds: Dataset,
    video_id: str,
    out_dir: str | Path,
    *,
    frames: Sequence[int] | None = None,
    video_path: str | Path | None = None,
    view_id: str | None = None,
    mp4: bool = False,
    roots: Sequence[str | Path] = (),
    style: PreviewStyle | None = None,
) -> list[Path]

Write annotated frames (PNG, or one MP4) to out_dir and return the paths.

With view_id, shows that view's crop with only that view's annotations, as its pipeline would see them.

vidlabel.preview.draw_annotations

draw_annotations(
    frame: np.ndarray,
    rows: Iterable[dict],
    frame_index: int,
    *,
    view: View | None = None,
    style: PreviewStyle | None = None,
) -> np.ndarray

Return a copy of an RGB frame with the annotations active at frame_index drawn on it.

Rejected rows are drawn dashed, dimmed and prefixed rej, under the others. Labels are kept inside the frame and moved so that they do not cover each other. Span markers are listed as captions at the top left.

vidlabel.preview.parse_color

parse_color(text: str) -> tuple[int, int, int]

'red', '#ff8800', 'ff8800' or '255,136,0' -> (r, g, b).

vidlabel.preview.PreviewStyle

vidlabel.preview.PreviewStyle(
    color: tuple[int, int, int] | None = None,
    label_colors: Mapping[str, tuple[int, int, int]] = <factory>,
    thickness: int | None = None,
    font_scale: float | None = None,
    show_labels: bool = True,
    outline: bool = True,
    mask_opacity: float = 0.5,
) -> None

How annotations are drawn. Anything left as None is chosen automatically from the frame size.

vidlabel.preview.PreviewStyle.color_for

color_for(label: str) -> tuple[int, int, int]

The colour used for a label: its entry in label_colors, else color, else a stable colour derived from the name.

Probing videos (needs ffprobe)

vidlabel.probe.probe_video

probe_video(path: str | Path, video_id: str | None = None, *, hash: bool = True) -> VideoMetadata

Read a video's facts with ffprobe (size, fps as a fraction, frame count, rotation, codec, bit rate) and hash the file unless hash=False. Needs ffprobe on the PATH. video_id defaults to the file name without extension.

vidlabel.probe.file_hash

file_hash(path: str | Path) -> str

sha256:<hex> of a file's contents.

vidlabel.probe.probe_frame_index

probe_frame_index(path: str | Path, *, content_hash: str | None = None) -> FrameIndex

Read a video's per-frame timestamps and keyframes from its packet table with ffprobe. Nothing is decoded, so it takes seconds even for long videos. Pass content_hash (from the metadata) to record which file the index belongs to. Raises ValueError if the container has no timestamps (some raw streams), and RuntimeError without ffprobe.

vidlabel.probe.reconcile_frame_count

reconcile_frame_count(meta: VideoMetadata, index: FrameIndex) -> VideoMetadata

Make meta.frame_count agree with index. If the count was only estimated when the video was probed, the index (an exact count) replaces it; a real disagreement raises ValueError, because one of the two describes a different file.

Frame cache (vidlabel.cache, needs OpenCV)

vidlabel.cache.FrameCache

vidlabel.cache.FrameCache(root: str | Path)

Frames saved as <root>/<video_id>/<frame>.jpg|png, plus <root>/cache.json saying which video file each came from.

Open an existing cache with FrameCache(root); make or extend one with FrameCache.build(samples, root).

vidlabel.cache.FrameCache.build

build(
    samples,
    root: str | Path,
    *,
    format: str = 'jpg',
    quality: int = 95,
    overwrite: bool = False,
) -> tuple['FrameCache', CacheStats]

Decode every frame samples (a FrameSamples) will use, in order, and save it under root.

Frames already cached are kept unless overwrite=True. Returns the cache and what was done. Needs OpenCV.

vidlabel.cache.FrameCache.has

has(video_id: str, frame: int) -> bool

vidlabel.cache.FrameCache.read

read(video_id: str, frame: int) -> np.ndarray

The cached frame as an RGB uint8 array (whole frame, displayed orientation). KeyError if it is not cached.

vidlabel.cache.FrameCache.usable

usable(video_id: str, meta: VideoMetadata) -> bool

Whether this cache has frames for the video as registered: same file (hash) and same size.

vidlabel.cache.CachedReader

vidlabel.cache.CachedReader(
    cache: FrameCache,
    video_id: str,
    meta: VideoMetadata,
    view: View | None,
    fallback,
)

Same interface as FrameReader (read, read_many, close), but frames come from a FrameCache when it has them and from the video file otherwise, so a partial cache is fine. Crops to view like FrameReader does.

vidlabel.cache.CachedReader.close

close() -> None

vidlabel.cache.CachedReader.read

read(index: int)

vidlabel.cache.CachedReader.read_many

read_many(indices: Iterable[int]) -> list

Frames for indices in the order given; frames missing from the cache are decoded together, in increasing order.

vidlabel.cache.CacheStats

vidlabel.cache.CacheStats(written: int, skipped: int, bytes: int, seconds: float) -> None

What FrameCache.build did.

vidlabel.cache.CacheStats.mean_kb (property)

Masks (vidlabel.masks)

vidlabel.masks.encode

encode(mask) -> bytes

Encode a boolean (H, W) array: crop to its bounding box, bit-pack the rows, zstd-compress. Lossless.

vidlabel.masks.decode

decode(blob: bytes) -> np.ndarray

Full-frame boolean mask (H, W).

vidlabel.masks.decode_crop

decode_crop(blob: bytes) -> tuple[np.ndarray, int, int]

Decode only the cropped region: (crop[h, w] bool, y0, x0). Skips allocating the full frame.

vidlabel.masks.bbox

bbox(blob: bytes) -> tuple[int, int, int, int] | None

(x0, y0, x1, y1) of the mask, or None if empty.

vidlabel.masks.info

info(blob: bytes) -> dict

Read only the header: frame size, crop rectangle. Cheap.

vidlabel.masks.place_in_frame

place_in_frame(mask, x: int, y: int, frame_w: int, frame_h: int) -> np.ndarray

Paste a crop-sized mask into an empty full-frame canvas with its top-left at (x, y); parts outside are clipped.

vidlabel.masks.resize_nearest

resize_nearest(mask, h: int, w: int) -> np.ndarray

Nearest-neighbour resize of a boolean mask to (h, w).

vidlabel.masks.from_coco_rle

from_coco_rle(rle: dict) -> np.ndarray

Decode a COCO RLE dict {'size': [h, w], 'counts': list[int] | str} to a boolean (h, w) mask.

vidlabel.masks.to_coco_rle

to_coco_rle(mask, compressed: bool = False) -> dict

A boolean mask as a COCO run-length dict (counts, size); compressed=True gives the compact string form.

Boxes (vidlabel.boxes)

vidlabel.boxes.convert_boxes

convert_boxes(
    boxes,
    src: str = 'xyxy',
    dst: str = 'xyxy',
    *,
    size: tuple[int, int] | None = None,
    src_normalized: bool = False,
    dst_normalized: bool = False,
) -> np.ndarray

Convert an (N, 4) array of boxes. size is (width, height) of the image they refer to; it is required when src_normalized or dst_normalized is set. Returns float32.

vidlabel.boxes.rotate_boxes

rotate_boxes(boxes, rotation: int, stored_size: tuple[int, int]) -> np.ndarray

Move (N, 4) xyxy boxes from the stored frame to the displayed orientation of a rotated video.

rotation is the video's clockwise rotation (0, 90, 180 or 270, as in VideoMetadata.rotation) and stored_size is (width, height) of the stored frame. Annotations in a vidlabel dataset use the displayed orientation, so a source that drew boxes on the unrotated stored frame needs this before add_annotations. x1/y1 stay exclusive.

Validation

vidlabel.validate_dataset

validate_dataset(path: str | Path, deep: bool = False) -> list[Issue]

Validate a whole dataset directory. Never raises for bad data; returns issues.

vidlabel.validate.check_annotation_table

check_annotation_table(
    table: pa.Table,
    *,
    frame_count: int | None = None,
    size: tuple[int, int] | None = None,
    vocab_names: set[str] | None = None,
    views: Mapping[str, View] | None = None,
    video_id: str | None = None,
    deep_masks: bool = False,
) -> list[Issue]

Check one annotation table. size is (width, height) in display orientation.

views maps view_id -> View; rows tagged with a view must lie inside it (region, frame range, allowed labels).

vidlabel.Issue and vidlabel.ValidationError

validate() returns a list of Issue(level, code, message, count, sample_ids); level is "error" or "warning". ValidationError is raised by writes and by build when there are errors; its .issues lists them. The codes are listed in the spec.

Spec models (vidlabel_spec, also importable from vidlabel)

These pydantic models are the contract; unknown fields are rejected (custom data goes in extra).

VideoMetadata

Generic facts about one video. Lives in videos/<video_id>/metadata.json.

field type default description
video_id str required
content_hash `str None` none
duration_s `float None` none
frame_count int required
fps Rational required
timebase `Rational None` none
is_vfr bool False
width int required
height int required
rotation int 0 Clockwise degrees (0, 90, 180, 270) applied to the stored frame to display it upright. 90 and 270 swap width and height.
codec `str None` none
pix_fmt `str None` none
bit_rate `int None` none
sources list[VideoSource] empty
ingested_at `datetime.datetime None` none
extra dict[str, Any] empty

VideoSource

One known location of the video file. Several may be listed; paths move, hashes don't.

field type default description
uri str required
note `str None` none

FrameIndex

Optional per-frame timing of one video, stored in videos/<video_id>/frame_index.json.

Read from the file's packet table (no decoding), so it is cheap to make. It lets a reader seek by timestamp, including in variable-frame-rate video, check where a seek really landed, and plan reads so each keyframe interval is decoded once.

pts holds one presentation timestamp per frame, in timebase ticks, in display order (so pts[i] belongs to frame i) and strictly increasing. keyframes lists the frame numbers where decoding can start without earlier frames.

field type default description
content_hash `str None` none
timebase Rational required
pts list[int] required
keyframes list[int] empty

Rational

field type default description
num int required
den int required

Region

field type default description
x int required
y int required
width int required
height int required

View

A named crop and/or time range of a video. No new video is made; annotations stay in source coordinates.

region is in the video's displayed orientation. If the crop is resized before it reaches a pipeline, give output_size (width, height) so coordinates can be converted back. allowed_labels optionally limits which labels may be used in this view (e.g. a caption strip that only allows caption labels).

field type default description
view_id str required
video_id str required
name `str None` none
region `Region None` none
output_size `tuple[int, int] None` none
frame_range `tuple[int, int] None` none
frame_ranges `list[tuple[int, int]] None` none
allowed_labels `list[str] None` none
extra dict[str, Any] empty

Label

field type default description
name str required
parent `str None` none
description `str None` none
extra dict[str, Any] empty

Vocab

The one shared list of labels.

field type default description
version str '0'
labels list[Label] empty

Manifest

Dataset-level index. Lives in manifest.json.

field type default description
spec_version str '0.6.0'
videos list[str] empty
views list[str] empty
vocab_version `str None` none
extra dict[str, Any] empty

Release

Describes one compiled (training) build. release_id is derived from content, so a training run can pin it.

field type default description
release_id str required
spec_version str required
created_at datetime.datetime required
n_rows int required
n_videos int required
row_group_size int required
files dict[str, str] required file name -> 'sha256:'
extra dict[str, Any] empty Build filters etc.

Source: docs/python-api.md

Clone this wiki locally