Metadata-Version: 2.4
Name: vd3
Version: 1.0.1
Summary: DVC-backed media database for computer vision and ML developers
Project-URL: Homepage, https://github.com/muncasterconsulting/vd3
Project-URL: Repository, https://github.com/muncasterconsulting/vd3
Project-URL: Issues, https://github.com/muncasterconsulting/vd3/issues
Author-email: Justin Muncaster <justin@muncasterconsulting.com>, Alec Wicklund <alec.wicklund@muncasterconsulting.com>, Paul Filitchkin <paul.filitchkin@muncasterconsulting.com>
License-Expression: MIT
Keywords: computer-vision,dataset,dvc,video
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.12
Requires-Dist: duckdb>=1.1.0
Requires-Dist: dvc-azure>=3.0
Requires-Dist: dvc-gdrive>=3.0
Requires-Dist: dvc-gs>=3.0
Requires-Dist: dvc-s3>=3.0
Requires-Dist: dvc>=3.50.0
Requires-Dist: ffmpeg-python>=0.2.0
Requires-Dist: orjson>=3.10.0
Requires-Dist: pillow>=10.0.0
Requires-Dist: pydantic>=2.0
Requires-Dist: python-dotenv>=1.0
Requires-Dist: rich>=13.0.0
Requires-Dist: typer>=0.12.0
Requires-Dist: watchdog>=4.0
Description-Content-Type: text/markdown

# VD3


A [DVC](https://dvc.org)-backed media database for computer vision and ML developers. Tracks video and imageset assets, annotations, and worksets as MP4/JSON media with CSV-based metadata, so datasets stay versioned and reproducible across local disks and remote storage backends.

## Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [Adding Assets](#adding-assets)
  - [Asset and datasource names](#asset-and-datasource-names)
  - [Videos](#videos)
  - [Imagesets](#imagesets)
  - [Annotation layers](#annotation-layers)
  - [Layer groups](#layer-groups)
  - [COCO](#coco)
  - [Git-backed vs. DVC-backed annotations](#git-backed-vs-dvc-backed-annotations)
  - [Layer versioning (status, push, pull, revert)](#layer-versioning-status-push-pull-revert)
- [Worksets](#worksets)
- [Remote Storage](#remote-storage)
- [Listing & Inspection](#listing--inspection)
- [Exporting](#exporting)
- [Library API](#library-api)
- [Versioning & stability](#versioning--stability)
  - [Public API](#public-api)
  - [Deprecations](#deprecations)
- [CLI Reference](#cli-reference)

## Installation

```bash
pip install vd3
```
With **uv** run:
```bash
uv pip install vd3
```

Add as a dependency to your project:

```toml
# pyproject.toml
[project]
dependencies = ["vd3"]
```
Then resolve dependencies and sync:
```bash
uv sync
```

## Quick Start

```bash
# Initialize a content database in the current directory
vd3 db init

# ...or in a specific directory
vd3 db init /path/to/mydb

# Add a video under a datasource
vd3 datasource add-video my-datasource clip.mp4 -p /path/to/mydb

# Add multiple videos with a glob (quote to prevent shell expansion)
vd3 datasource add-video my-datasource '*.mp4' -p /path/to/mydb

# List assets in a datasource
vd3 datasource assets my-datasource -p /path/to/mydb

# Show media availability
vd3 media status -p /path/to/mydb
```

Every command reads `vd3 <noun> <verb> [args]`. The nouns are `db`,
`datasource`, `workset`, `asset`, `layer`, `media`, and `eval`. Containers
(`datasource`, `workset`) own ingest verbs and contained-asset listings; the
`asset` noun is reserved for operations on a known asset.

All commands that accept `--path` also honor the `VD3_DB` environment variable, so
you can point at a database once and drop the `-p` flag from individual commands.
On startup `vd3` automatically loads a `.env` file from the current directory or
the nearest ancestor, so a project-level `.env` containing:

```env
VD3_DB=/path/to/mydb
```

is enough — no `export` or shell sourcing required:

```bash
vd3 asset list
vd3 media status
```

Precedence: explicit `--path` > shell-exported `VD3_DB` > `.env` `VD3_DB` > current
directory. Shell-exported values win over `.env` so you can do one-off overrides
without editing the file.

`--path` on a specific command still overrides the env var.

## Core Concepts

- **Datasource** — groups assets by origin (e.g. `dashcam-2024`, `test-data`). Required when importing.
- **Asset** — a single video or imageset (directory of images). A video is backed either by an **MP4** (ffmpeg-decoded) or an uncompressed **`.tar` of images** (a "tar-of-images video" — frames served straight from the archive, kept intact as one DVC blob); import either with `vd3 datasource add-video`.
- **Workset** — a named subset of assets, optionally organized into packages (folders). Independent of storage layout.
- **Annotation layer** — detections or tracks attached to an asset, with a key (e.g. `gt`, `det/yolo-v8`) and a `source` of `GROUND_TRUTH`, `HUMAN_ANNOTATED`, or `RESULT`. An asset may have at most one `GROUND_TRUTH` layer. Legacy `human`/`machine` values in older databases are still *read* (`machine` → `RESULT`; `human` → `GROUND_TRUTH` on a `*/ground_truth` key, else `HUMAN_ANNOTATED`), but on *write* legacy `human` always becomes `HUMAN_ANNOTATED` and warns — pass `GROUND_TRUTH` explicitly to create ground truth. Layers are **git-backed** by default (small, always present after clone); large layers can be **DVC-backed** (fetched on demand like media) via `add_annotation_layer(..., dvc=True)` / `vd3 layer add-vd3 --dvc`, or added to DVC later with `vd3 layer add-to-dvc`.
- **Layer group** — a per-layer identifier that groups the outputs of one *computer-vision solution* together (a CNN detector + a background-model MTI detector + the tracker built from either, all belonging to the same pipeline release). Downstream selectors (e.g. the visdata layer picker) show one entry per group and expose the individual layers as sub-toggles. Defaults to the trailing segment of the layer key after the last `/`; can be set explicitly. See **[Layer groups](#layer-groups)** for examples.
- **Per-image metadata** — arbitrary non-bbox metadata attached per image (labels, attributes, …), stored as a DVC-backable `image_metadata` layer via `add_image_metadata` / `read_image_metadata`. Keeps bulky per-image metadata out of the git-tracked `imageset.json` manifest; `vd3 layer migrate-imageset-extra` moves existing inline `extra` into one.

## Adding Assets

### Asset and datasource names

An asset's name and its datasource are both used verbatim as URL path components and on-disk
metadata/media path segments (`…/<datasource>/<name>`), so both are validated to stay safe in those
roles.

**Asset name** (the `--name` you pass to an ingest command, or the source filename it defaults to):

- **Allowed characters:** letters, digits, and `-` `_` `.` `/` — all URL-safe.
- **Slashes are path separators.** `--name batch1/clip3` nests the asset one directory deeper.
  Segments between slashes must be non-empty (no leading/trailing `/`, no `//`) and must not be `.`
  or `..`.
- **Length:** up to 200 characters.

**Datasource** — same rules, but stricter: because a datasource is a single path/URL segment (never
nested), it may contain **no slashes at all** (neither `/` nor `\`). Allowed characters are letters,
digits, and `-` `_` `.`; it may not be `.` or `..`; up to 200 characters.

Invalid names are rejected up front (before any file is copied) with a clear error, and are also
validated when a database is loaded — so a hand-edited `assets.csv` / `datasources.csv` can't smuggle
in an unsafe value.

### Videos

```bash
# Single file
vd3 datasource add-video dashcam clip.mp4

# Glob (recursive)
vd3 datasource add-video dashcam 'rawdata/**/*.mp4'

# Tar-of-images video (frames served from the archive; --fps stamps a nominal rate)
vd3 datasource add-video dashcam frames.tar --fps 30

# Force re-import of a duplicate (matched by SHA-256)
vd3 datasource add-video dashcam clip.mp4 --force

# Add and assign to a workset/package
vd3 datasource add-video dashcam clip.mp4 -w my-workset -k batch1
```

### Imagesets

```bash
# Directory of images
vd3 datasource add-imageset my-datasource /path/to/images

# Tar archive
vd3 datasource add-imageset my-datasource images.tar
```

### Annotation layers

VD3 JSON detections/tracks into an existing asset. The second positional is a
layer-name prefix prepended to every layer key in the file (e.g. importing a
file with `det/yolo` under `run-1` produces `run-1/det/yolo`):

```bash
vd3 layer add-vd3 clip run-1 results.json -p /path/to/mydb
```

Layers can also be added directly from Python — pass the frame-indexed annotations
plus a `source` (`GROUND_TRUTH` / `HUMAN_ANNOTATED` / `RESULT`) and a `layer_type`
(`detections` / `tracks`):

```python
from vd3storage import VD3Storage

with VD3Storage("/path/to/mydb") as db:
    asset = db.get_asset("dashcam", "clip1")
    db.add_annotation_layer(
        asset.asset_id,
        layer="det/cnn-v1",
        display_name="CNN detector v1",
        annotations={0: [{"class_name": "car", "bbox": [10, 20, 30, 40], "confidence": 0.91}]},
        source="RESULT",
        layer_type="detections",
    )
```

### Layer groups

A `layer_group` labels each annotation layer with the **computer-vision solution** it
belongs to, so multiple outputs from the same pipeline surface together in downstream
tools. This matters as soon as a single "solution" writes more than one layer per asset —
which is the common case for modern CV pipelines. Consider a solution named `sb-v3` that
runs three stages:

| Layer key                    | `layer_type` | `layer_group` | What it holds                                    |
| ---------------------------- | ------------ | ------------- | ------------------------------------------------ |
| `det/sb-v3-cnn`              | detections   | `sb-v3`       | CNN detector output (per-frame boxes)            |
| `det/sb-v3-mti`              | detections   | `sb-v3`       | MTI detector output (moving-target boxes from a background model) |
| `track/sb-v3`                | tracks       | `sb-v3`       | Tracker output that fused the two detection sources |

All three carry `layer_group="sb-v3"`. When you open the asset in visdata, the layer picker
shows **one entry** for `sb-v3`, and clicking it exposes the three underlying overlays
individually — you can toggle the CNN detections, the MTI detections, and the resulting
tracks against each other without hunting through the full flat list.

**Default rule** — when `layer_group` is not set explicitly, vd3 uses the trailing
segment of the layer key after the last `/`, so a common naming convention already
groups related layers together for free:

```python
# The det/track pair land in group "sb-v3" via the default rule; the MTI layer
# defaults to its own group -- see below.
db.add_annotation_layer(asset.asset_id, "det/sb-v3",   "SB v3 CNN", cnn_dets, source="RESULT", layer_type="detections")
db.add_annotation_layer(asset.asset_id, "det/sb-v3-mti", "SB v3 MTI", mti_dets, source="RESULT", layer_type="detections")  # default -> "sb-v3-mti"
db.add_annotation_layer(asset.asset_id, "track/sb-v3", "SB v3 tracks", tracks, source="RESULT", layer_type="tracks")
```

Notice the MTI layer defaults to its own group because the trailing segment is
`sb-v3-mti`. To pull it into the shared `sb-v3` group, pass `layer_group=` explicitly:

```python
db.add_annotation_layer(
    asset.asset_id,
    layer="det/sb-v3-cnn",
    display_name="SB v3 CNN",
    annotations=cnn_dets,
    source="RESULT",
    layer_type="detections",
    layer_group="sb-v3",          # <- explicit
)
db.add_annotation_layer(
    asset.asset_id,
    layer="det/sb-v3-mti",
    display_name="SB v3 MTI",
    annotations=mti_dets,
    source="RESULT",
    layer_type="detections",
    layer_group="sb-v3",          # <- share the group with the CNN layer
)
db.add_annotation_layer(
    asset.asset_id,
    layer="track/sb-v3",
    display_name="SB v3 tracks",
    annotations=tracker_output,
    source="RESULT",
    layer_type="tracks",
    layer_group="sb-v3",          # <- and with the tracker built from them
)
```

**When you write a layer JSON directly** — for example when generating results from a
model-serving pipeline that emits VD3 JSON files — set the `layer_group` field in the
body itself:

```json
{
  "layer": "det/sb-v3-mti",
  "display_name": "SB v3 MTI detections",
  "layer_type": "detections",
  "layer_group": "sb-v3",
  "source": "RESULT",
  "frames": { "0": [ ... ], "1": [ ... ] }
}
```

Import it the same way as any VD3 JSON layer (`vd3 layer add-vd3 …`); the `layer_group`
value round-trips into the `asset_layers` table on write and is re-read from the JSON
body on future scans. The database-wide inventory shows one column per stored field:

```bash
vd3 layer list          # ↕ per layer_group aggregation (asset_layers table)
vd3 asset layers clip1  # per-asset detail
```

### COCO

Import COCO annotations into an existing imageset:

```bash
vd3 layer add-coco my-imageset gt annotations.json \
    --source GROUND_TRUTH --reviewed-all
```

Import a full COCO dataset (creates the imageset and imports annotations in
one step):

```bash
vd3 datasource add-imageset-from-coco my-datasource gt annotations.json \
    --image-root /path/to/images
```

### Git-backed vs. DVC-backed annotations

Annotation layers are **git-backed by default** — the JSON is committed directly, so
it's always present after a clone (ideal for small ground-truth files). Large or bulky
layers can instead be **DVC-backed**, like media: git tracks only a small `.json.dvc`
pointer, and the body is fetched on demand and travels with its media on push/pull.
(Media/video files are always DVC-backed; only annotations offer the choice.)

Add a new layer straight to DVC with `--dvc`:

```bash
vd3 layer add-vd3 clip run-1 results.json --dvc -p /path/to/mydb
```

Already have layers on disk? Use `add-to-dvc` to add them to version control via DVC.
It handles both cases uniformly: a brand-new, not-yet-committed layer is added straight
to DVC, and a layer already tracked by Git is untracked from Git first, then added.
Preview first, then scope by asset/layer or add everything (optionally only layers above
a size):

```bash
vd3 layer add-to-dvc --all --dry-run        # preview what would be added
vd3 layer add-to-dvc --all                  # add every layer
vd3 layer add-to-dvc clip --layer det/qm    # or scope to one asset/layer
vd3 layer add-to-dvc --all --min-size 1     # only layers ≥ 1 MB
```

Use `--layer` to add just one layer while leaving the rest un-added — handy when some
layers on an asset are ready to version but others are still work-in-progress and should
stay uncommitted:

```bash
# Add only det/qm to DVC across every asset; det/wip and any others are left as-is.
vd3 layer add-to-dvc --all --layer det/qm
```

`add-to-dvc` `git rm --cached`s each already-tracked body, `dvc add`s it, and writes the
`.dvc` pointer plus an `annotations/.gitignore` entry. Afterward, commit the new pointers
and `.gitignore`, then push the bodies to your remote:

```bash
vd3 media push --all
```

`add-to-dvc` only acts on layers VD3 already tracks (created via `vd3 layer …` or the
import commands). Raw JSON dropped into `db/metadata/.../annotations/` by hand must be
imported first (`vd3 layer add-vd3 … --dvc`, or import then add).

### Layer versioning (status, push, pull, revert)

DVC-backed annotation layers have the same push/pull lifecycle as media, plus tooling to
inspect sync state and undo local edits. These commands are grouped under a **Versioning**
panel in `vd3 layer --help`. All of `push` / `pull` / `revert` scope with
`--asset` / `--datasource` / `--workset` / `--all`.

**See what's synced and what changed locally** — a per-layer counterpart to `vd3 media status`:

```bash
vd3 layer status                     # summary + per-datasource breakdown
vd3 layer status --list              # one row per layer (Datasource/Asset/Layer/Backing/Status)
vd3 layer status --datasource dashcam
```

Each layer reports one of `synced` (on the remote), `local (not pushed)`, `remote-only`
(pointer present, body not pulled), `modified` (edited since its last `dvc add`, or
uncommitted git changes for a git-backed layer), `local` (DVC-backed but no remote
configured), or `git-tracked`.

**Push / pull layer bodies** — a layer-only counterpart to `vd3 media push` / `pull` (which
bundle media *and* layers). Use these to move just the annotations:

```bash
vd3 layer push --datasource dashcam  # upload DVC-backed layers in the datasource
vd3 layer pull --workset my-experiment
vd3 layer push --all                 # every DVC-backed layer in the database
```

> A layer shown as `modified` won't upload its new bytes with a plain push — `dvc push`
> sends what the `.dvc` pointer currently references. Re-record it first
> (`dvc add db/metadata/.../annotations/<layer>.json`), then `vd3 layer push`.

**Revert local modifications** — discard edits and restore the last-recorded content
(DVC-backed bodies from the DVC cache, git-backed *tracked* bodies from git). Dry-run by
default, so you always preview first:

```bash
vd3 layer revert --all               # DRY RUN — lists what would be reverted, changes nothing
vd3 layer revert --all --no-dry-run  # actually restore
vd3 layer revert --asset clip        # scope to one asset
```

Untracked git-backed bodies have no previously-recorded version, so `revert` leaves them
untouched.

## Worksets

```bash
# Create
vd3 workset create "My Experiment"

# Add assets by name or ID
vd3 workset add my-experiment clip-001 clip-002

# ...or by media-path glob (run from the database root; files must be on disk)
cd /path/to/mydb
vd3 workset add my-experiment 'db/media/videos/fc/*.mp4'

# Inspect
vd3 workset list
vd3 workset show my-experiment      # metadata + packages (use `workset layers` for layers)
vd3 workset assets my-experiment    # assets in the workset

# Remove an asset / delete the workset
vd3 workset remove my-experiment clip-001
vd3 workset delete my-experiment
```

## Remote Storage

Media files are tracked by DVC. A content database has a single configured remote.

```bash
# Set the remote (replaces any existing one)
vd3 media remote set gs://my-bucket/vd3-data
vd3 media remote show

# Sync (push and pull both accept --workset/-w, --asset/-a, --datasource/-d, --all)
vd3 media push --all
vd3 media pull --workset my-experiment
vd3 media status
```

Supported backends:

| Backend | URL form | Notes |
|---|---|---|
| Google Cloud Storage | `gs://bucket/path` | `gcloud auth application-default login` |
| Amazon S3 | `s3://bucket/path` | Standard AWS credential chain |
| Azure Blob Storage | `azure://container/path` | |
| Google Drive | `gdrive://folder-id` | via `dvc-gdrive` |
| Local / NAS | `/mnt/nas/vd3-backup` | |

## Listing & Inspection

```bash
vd3 asset list                       # all assets (cross-container)
vd3 datasource list                  # all datasources
vd3 datasource assets dashcam        # assets in a datasource
vd3 datasource assets dashcam --paths      # one media path per line
vd3 datasource assets dashcam --filenames  # one filename per line
vd3 datasource layers dashcam        # annotation layers across the datasource
vd3 workset assets my-experiment     # assets in a workset
vd3 workset layers my-experiment     # annotation layers across the workset
vd3 asset layers clip                # annotation layers on an asset
vd3 layer list                       # annotation layers across the whole database (per-layer coverage)
vd3 layer status                     # per-layer DVC sync state + local modifications
vd3 asset show clip                  # asset details
vd3 db info                          # database overview
vd3 db query "SELECT ..."            # raw DuckDB SQL against the CSV tables
```

## Exporting

```bash
# Extract frames from a video or images from an imageset
vd3 asset export-frames clip -o ./out
```

## Library API

The CLI is a thin wrapper around `VD3Storage`, which is also usable directly.

```python
from vd3storage import VD3Storage, Asset, Workset  # Tag, WorksetAsset also exported

# Open an existing database (or use VD3Storage.init(path) to create one)
storage = VD3Storage("/path/to/mydb")

# Browse assets
for a in storage.list_assets(datasource="dashcam"):
    print(f"{a.name} ({a.asset_type}): {a.frame_count} frames @ {a.nominal_fps} fps")

# Look up by (datasource, name) or by ID
clip = storage.get_asset("dashcam", "clip-001")
clip = storage.get_asset_by_id("3f1a...")

# Import a video
asset = storage.import_video("clip.mp4", datasource="dashcam")

# Resolve where the media file lives on disk
storage.resolve_media_path(clip)

# Annotation layers
storage.list_annotation_layers(clip.asset_id)
storage.read_annotation_layer(clip.asset_id, "gt")

# Worksets
ws = storage.create_workset("My Experiment")
storage.add_asset_to_workset(ws.workset_id, clip.asset_id, package="batch1")
storage.list_workset_assets(ws.workset_id)

# Raw DuckDB SQL against the underlying CSV tables
rows = storage.execute_sql("SELECT name, frame_count FROM assets WHERE asset_type = 'video'")
```

Other useful methods: `import_imageset`, `import_coco`, `import_coco_dataset`, `import_result`, `export_coco`, `open_video`, `open_imageset`, `get_frame_image`, `add_tag`, `is_media_available`, `pull`, `push`. Inspect `help(VD3Storage)` for the full surface.

## Versioning & stability

The package follows [Semantic Versioning](https://semver.org). All notable
changes are recorded in [CHANGELOG.md](CHANGELOG.md), and every release is
tagged `vX.Y.Z` in git.

**Since 1.0.0 (2026-07-13) the package follows strict SemVer**: MAJOR = breaking,
MINOR = additive, PATCH = fix. Every breaking change is called out under a
`### Breaking` heading in the CHANGELOG entry for that release.

(Before 1.0.0, minor bumps such as 0.2 → 0.3 could contain breaking changes;
the pre-1.0 entries in the CHANGELOG should be read with that in mind.)

### Public API

A change is "breaking" only if it alters one of the following:

1. **Names re-exported from the top-level `vd3storage` package** (i.e. listed
   in `vd3storage.__all__`):
   - `VD3Storage` and its documented methods
   - `AlreadyInitializedError`
   - The model classes `Asset`, `Tag`, `Workset`, `WorksetAsset`
     (including their field names and types)
   - `__version__`
2. **The `vd3` CLI** — command names, option names, exit codes, and the
   documented input file formats (VD3 JSON, COCO).
3. **The on-disk layout of a content database** — directory structure under
   `db/`, CSV table schemas (tracked by `SCHEMA_VERSION` in `db/tables/`), the
   shape of `video.json` / `imageset.json` / annotation JSON files, and the
   structure of generated `pyproject.toml` / `.gitignore`.

Everything else is **internal** and may change without a major-version bump
even if it is reachable via an import path. That includes the `vd3storage.orm`,
`vd3storage.dvc`, `vd3storage.media`, `vd3storage.importers`,
`vd3storage.exporters`, and `vd3storage.cli` submodules; helper functions in
`vd3storage.storage` that start with `_`; and the on-disk format of files
written into `.dvc/` (those belong to DVC).

### Deprecations

When a public API needs to change incompatibly, the old form keeps working and
emits `DeprecationWarning` for at least one minor release before being removed.
Current deprecations are listed in the CHANGELOG under each release's
`### Deprecated` heading.

To surface them in your own code:

```bash
python -W "default::DeprecationWarning:vd3storage" your_script.py
```

## CLI Reference

```
vd3 --help                       Top-level help
vd3 <noun> --help                Help for a noun
vd3 <noun> <verb> --help         Help for a specific command
```

Every command reads `vd3 <noun> <verb> [args]`. Positionals carry identity
(target → composite parts → payload); flags carry modifiers
(`--paths`, `--filenames`, `--source GROUND_TRUTH`, ...).

| Command | Description |
|---|---|
| `db init` | Initialize a content database (defaults to cwd) |
| `db info` | Show database overview |
| `db query` | Run raw DuckDB SQL against the CSV tables |
| `datasource list` | List datasources |
| `datasource show` | Show datasource stats |
| `datasource assets` | List assets in a datasource |
| `datasource layers` | List annotation layers across a datasource (per-layer coverage) |
| `datasource add-video` | Import video files into a datasource |
| `datasource add-imageset` | Import an imageset (directory or tar) into a datasource |
| `datasource add-imageset-from-coco` | Import a COCO dataset as imageset + layer |
| `datasource set-description` | Set a datasource's human-readable description |
| `workset create` | Create a workset |
| `workset list` | List worksets |
| `workset show` | Show workset metadata + packages |
| `workset assets` | List assets in a workset |
| `workset layers` | List annotation layers across a workset (per-layer coverage) |
| `workset add` | Add assets to a workset |
| `workset remove` | Remove an asset from a workset |
| `workset delete` | Delete a workset (assets are kept) |
| `asset list` | List every asset (cross-container) |
| `asset layers` | List annotation layers on an asset |
| `asset show` | Show asset details |
| `asset remove` | Delete an asset |
| `asset export-frames` | Extract frames from a video or imageset |
| `asset split` | Set dataset split affinity (`trainval`/`train`/`val`/`test`/`omit`, or `""` for unassigned) |
| `asset star` / `unstar` | Set or clear the queryable bookmark flag (`asset list --starred` filters on it) |
| `asset set-comments` | Set the free-text comments field on an asset |
| `asset extra` / `extra-set` | Read or write a consumer's namespaced blob in `extra_json` (sibling namespaces preserved) |
| `asset environment` | Set capture environment (`urban`, `rural`, `highway`, `marine`, …) |
| `asset placement` | Set sensor placement (`outdoor`, `indoor`, `various`, `unknown`) |
| `asset time-of-day` | Set lighting conditions (`day`, `night`, `dawn_dusk_cloudy`, `unknown`) |
| `asset spectrum` | Set sensor spectrum (`visible`, `infrared`, `various`, `unknown`) |
| `asset synthetic` | Mark whether the media was synthetically generated (`no`, `yes`, `unknown`) |
| `layer list` | List annotation layers across the whole database (per-layer coverage) |
| `layer status` | Show per-layer DVC sync state (synced / not pushed / remote-only) and local modifications (`--asset`/`--datasource`/`--workset`, `--list`) |
| `layer push` | Push DVC-backed annotation layers to the remote by scope (`--asset`/`--datasource`/`--workset`/`--all`) |
| `layer pull` | Fetch DVC-backed annotation-layer bodies from the remote by scope |
| `layer revert` | Discard local modifications to layers (DVC cache / git); dry-run by default (`--no-dry-run` to apply) |
| `layer add-coco` | Import COCO annotations into an existing imageset |
| `layer add-vd3` | Import VD3 JSON detections/tracks under a layer-name prefix (`--dvc` stores them DVC-backed) |
| `layer rename` | Rename a layer key on one asset (`--asset`) or every asset (`--all`); dry-run by default (`--no-dry-run` to apply) |
| `layer remove` | Delete a layer from one asset (`--asset`) or every asset that has it (`--all`, required); dry-run by default (`--no-dry-run` to apply) |
| `layer add-to-dvc` | Add layers to version control via DVC, or migrate Git-backed layers to DVC (`--layer`, `--all`, `--min-size`, `--dry-run`) |
| `layer source` | Re-classify a layer's source (`GROUND_TRUTH` / `HUMAN_ANNOTATED` / `RESULT`) |
| `layer promote` | Promote a result layer to ground truth |
| `layer approve` / `reject` | Record a human review verdict on a ground-truth layer (`--by`, `--reason`); surfaces as `Asset.gt_reviewed` |
| `layer unreview` | Clear the review verdict and its audit fields |
| `layer pin` / `unpin` | Set or clear the asset's canonical `RESULT` layer (`Asset.pinned_result`) |
| `layer migrate-source` | Rewrite legacy `human`/`machine` source values to the current taxonomy |
| `layer migrate-imageset-extra` | Move inline `imageset.json` `extra` data into an `image_metadata` layer |
| `media status` | Show media availability |
| `media push` | Push media to remote storage |
| `media pull` | Pull media from remote storage |
| `media remote set` | Set the remote storage URL |
| `media remote show` | Show the configured remote |
| `eval add` | Record an evaluation/metrics blob for an asset or workset (`--run`, `--metrics`, `--config`) |
| `eval list` | List recorded evaluations |
| `eval show` | Show one evaluation's metrics and config |
