Skip to content

split: Assign Train/Val/Test Splits

The split command assigns a generated dataset's images and Pascal VOC annotations to leakage-safe train/val/test splits, grouped by dive (video_sequence_name) so frames from the same dive never end up split across train/val/test -- otherwise near-duplicate frames from one dive would leak across the split, inflating validation/test scores.

Run this after any manual QA/QC on generate's output (filter, dedup, gallery, fix-dimensions, etc.) and before converting to a training format (convert voc-yolo, remap yolo) -- it's the last step before handing a dataset to a training framework:

generate  →  manual QA/QC  →  split  →  format conversion (convert voc-yolo, ...)

Splitting only operates on whatever image/XML pairs are still present in DATASET_DIR at the time it runs, so it's safe to run after QA/QC tools have deleted or modified files.

Usage

m3-download split DATASET_DIR OUT_DATASET_DIR [OPTIONS]

Required Parameters

  • DATASET_DIR: Dataset directory to split (post-QA/QC), expecting JPEGImages/ and Annotations/ subfolders -- the same layout generate writes
  • OUT_DATASET_DIR: Directory to write per-split dataset subdirectories to (each OUT_DATASET_DIR/<split>/ is itself a JPEGImages//Annotations/ dataset dir)

Options

  • --manifest-path: Path to the generate run's manifest.json -- this is where dive (video_sequence_name) membership per image comes from (default: DATASET_DIR/manifest.json)
  • --ratio NAME=VALUE: Split ratio, repeatable (default train=0.8 val=0.1 test=0.1)
  • --seed: Random seed for split assignment (default: 0)
  • --split-manifest-path: Path to write the split manifest to (default: OUT_DATASET_DIR/split_manifest.json)
  • --verbose, -v: Display additional debugging information

How It Works

  1. Loads the generate manifest and builds a filename → dive lookup from its results.images
  2. Matches image/XML pairs currently present on disk (under DATASET_DIR/JPEGImages/ and DATASET_DIR/Annotations/) by filename stem, skipping (with a warning) any orphan -- an image with no matching XML annotation, or vice versa
  3. Groups matched pairs into atomic assignment units: pairs sharing a dive are grouped together so a whole dive is assigned as one unit; pairs with no dive -- including any not present in the manifest at all, e.g. images added manually after the pull -- are each their own unit
  4. Assigns units to splits via a seeded greedy balancing pass, converging close to the requested ratios despite uneven dive sizes
  5. Symlinks (not copies) each pair's image and XML into OUT_DATASET_DIR/<split>/JPEGImages/ and OUT_DATASET_DIR/<split>/Annotations/ -- a generate pull can be hundreds of thousands of images, so symlinking avoids doubling disk usage. If you need real files (e.g. to hand a directory to a tool that doesn't follow symlinks), copy through the symlinks yourself afterward.
  6. Writes split_manifest.json, recording the ratios/seed used and, per split, the image/ localization counts and constituent dive names

Examples

Split with the default 80/10/10 ratios:

m3-download split dataset/ split_dataset/

Split 90/10 train/val only, with a fixed seed:

m3-download split dataset/ split_dataset/ --ratio train=0.9 --ratio val=0.1 --seed 42

Why symlinks depend on the source directory

Since OUT_DATASET_DIR/<split>/ contain symlinks back to DATASET_DIR, don't move or delete the QA'd source directory after splitting without also updating (or re-running) the split.