Skip to main content

Exports and retention

Exports are per project. Routes on this page are under /curation/projects/{project}/; each project's exports live in its own directory, {OP_PROJECTS_DATA_ROOT}/{slug}/exports/. Class ids in an export are the project's own dense ids, written with the ordered class-name list.

Freezing a test holdout first​

POST /curation/projects/{project}/test_holdout/freeze marks a per-class test split once, so every later export and every model comparison scores against the same frozen items. GET /curation/projects/{project}/test_holdout/stats shows it. Items in the holdout are protected: a class merge that would move them is refused.

Multi-class YOLO export​

curl -s -X POST "$API/curation/projects/$P/export/yolo" -H 'content-type: application/json' -d '{}'
curl -s "$API/curation/projects/$P/export/status"
  • Always every class. POST /export/yolo has no classes field and rejects unknown keys. To train on a subset, either export with /export/single_class, or pass hyperparameters.include_classes to POST /curation/projects/{project}/train/start.
  • Split by source image. Items cut from one image always land in the same split. Images holding a frozen holdout item go to test with all their objects; the rest of that class is split between train and val.
  • One image file and one label file per source image (images/<split>/<image_id>.<ext>, labels/<split>/<image_id>.txt), one cls cx cy w h line per validated object, relative to the full image.
  • Partially labeled images. By default an image is exported with its validated objects even if it holds unlabeled ones, and the manifest counts them (unlabeled_items_on_exported_images); training preflight then warns (export_unlabeled_objects), because the detector will learn those objects as background. require_fully_labeled_images: true leaves such images out instead.
  • Imported data round-trips. A frozen test split, negative frames (images with no box) and split assignments written by a dataset import are honored by the export, and an OpenProcessor export can be imported into another project with its labels, splits and frozen test SHA intact.
  • dataset_sha hashes the written label content (which frames, which split, which boxes) plus the ordered class-name list, so a split change, a corrected box or a class rename all change it.
  • The export's current symlink flips atomically on success, and training defaults to it.

Single-class and class-subset export​

POST /curation/projects/{project}/export/single_class builds a narrowed dataset for one class or a subset (class_ids), adds background and hard-negative frames (empty_bg_ratio, human-marked false positives), can export whole frames or item crops (image_mode), can take boxes from items or from the region annotation (box_source), and records frozen_test_sha over the test split's identity. Single-class versions live under <export_root>/<profile_name>/<version>/ with their own current.

Listing and downloading​

  • GET /curation/projects/{project}/export/datasets lists every export with its kind, dataset_sha, split counts and is_current.
  • GET /curation/projects/{project}/export/registry/{artifact} downloads a frozen artifact of the current export (class_registry.json, data.yaml, manifest.json, label_stats.json); any other name is a 404 before the filesystem is touched.

Retention​

Exports, training runs and caches would otherwise grow without bound.

WhatPolicyKnob
Auto-named exports (YYYYMMDDTHHMMSSZ dirs)Keep-last, applied best-effort after every successful exportOP_EXPORT_KEEP_LAST (default 5, 0 keeps all)
Export backlogSame planner, by handprune_exports.py [--keep-last N] [--apply]
Finished training runs and bake-off outputKeep-last, by handprune_training_runs.py [--keep-last N] [--bakeoff-out-keep-last N] [--apply]
Crop cachePruned back toward 90% of the cap once exceeded (checked every ~256 ingest writes)OP_CROP_CACHE_MAX_BYTES
Event logRotates to a single .1 backupOP_EVENT_LOG_MAX_BYTES

Both scripts live in scripts/curation/ and ship in the api image. Run them there:

docker compose exec -T api python scripts/curation/prune_exports.py --project $P # dry run
docker compose exec -T api python scripts/curation/prune_exports.py --project $P --apply
docker compose exec -T api python scripts/curation/prune_training_runs.py --project $P --keep-last 10

The dry runs of both scripts were run in the api container against a public sample project. They printed Nothing to prune under data/projects/<slug>/exports (keep_last=5). and 0 training run(s) planned for removal (keep_last=10) with Dry-run only. Pass --apply to remove. The --apply forms were not run.

Export pruning never removes a custom-named export directory, the target of the current symlink, or any export a training job, a finished run's lineage, or a queued/running bake-off still references. Training-run pruning never removes a non-terminal run, a promoted run, or one a bake-off still references, and never touches MLflow. Both scripts are dry runs unless you pass --apply, and each visits every active and archived project under that project's own binding (--project narrows it to one).