Skip to content

DeepLabCut Format (.csv)

Load annotations from DeepLabCut, a popular markerless pose estimation tool. sleap-io reads DLC annotations from the project's CSV files (optionally augmented by a config.yaml); it does not read DLC .h5 outputs.

load_dlc reads a single DLC annotation CSV. When a project config.yaml is available — either passed explicitly via config= or auto-discovered by walking up from the CSV — the following extra metadata is imported:

  • Skeleton edges from the config skeleton: list. Edges that reference bodyparts not present in the labeled data are dropped with a warning.
  • Source videos: each labeled-data/<video>/ image folder is linked back to its original video file (from the config video_sets) via Video.source_video, matched by filename stem. The link is best-effort and left unset on a stem mismatch or missing file.

Pass config=False to disable config use entirely and reproduce the legacy, config-free output.

Cropping (video_sets[...].crop)

DeepLabCut's video_sets[...].crop is a virtual read-time crop (an ROI that DLC's video reader slices out of each full frame). The images under labeled-data/<video>/ are the cropped region and the labels are stored in cropped-frame coordinates, while the linked source_video is the original, uncropped video. sleap-io now imports this crop:

  • The crop rect is parsed from video_sets (DLC stores it width-range-first as x1, x2, y1, y2; sleap-io reorders it to its (x1, y1, x2, y2) convention, x2/y2 exclusive) and recorded under labels.provenance["dlc_crops"], keyed by source-video path. This record persists through an SLP round-trip.
  • Labels are left verbatim in cropped-frame coordinates on the uncropped labeled-data ImageVideo — no offset is applied (and the already-cropped images are never cropped again). To map a label into the full source frame, use Video.to_source_coords with the recorded rect (it adds the crop origin (x1, y1)).
  • When the source video file is available, source_video is set to a Video.from_crop view of it, so source_video.crop_rect / to_source_coords work in memory (this view's crop is in-memory only; the persistent record is provenance["dlc_crops"]). When the source is absent, source_video is a closed Video as before.
  • Identity crops at the origin (0, W, 0, H — the DLC no-cropping default) record no crop and leave the link exact.

See the virtual cropping guide for the crop conventions.

import sleap_io as sio

# Single CSV; auto-discovers config.yaml for edges + source-video links.
labels = sio.load_dlc("project/labeled-data/vid1/CollectedData_Scorer.csv")

# Strict legacy output (no edges/links), even inside a project.
labels = sio.load_dlc("project/labeled-data/vid1/CollectedData_Scorer.csv", config=False)

Unannotated rows become empty frames

Every CSV row (image) is loaded as a LabeledFrame, including rows with no labeled bodyparts (all-NaN) — these become empty LabeledFrames, so the frame count matches the input. Drop them with Labels.clean() if you don't want them.

sleap_io.io.main.load_dlc(filename, video_search_paths=None, config=None, **kwargs)

Read DeepLabCut annotations from a CSV file and return a Labels object.

Parameters:

Name Type Description Default
filename str

Path to DLC CSV file with annotations.

required
video_search_paths list[str | Path] | None

Optional list of paths to search for video files.

None
config str | Path | bool | None

Path to a DLC project config.yaml. When provided (or auto-discovered), skeleton edges and source-video links are imported. Pass None (the default) to auto-discover config.yaml by walking up from the CSV, an explicit path to force a specific config, or False to disable config use entirely (strict legacy output).

None
**kwargs

Additional arguments passed to DLC loader.

required

Returns:

Type Description
Labels

Parsed labels as a Labels instance.

Source code in sleap_io/io/main.py
def load_dlc(
    filename: str,
    video_search_paths: list[str | Path] | None = None,
    config: str | Path | bool | None = None,
    **kwargs,
) -> Labels:
    """Read DeepLabCut annotations from a CSV file and return a `Labels` object.

    Args:
        filename: Path to DLC CSV file with annotations.
        video_search_paths: Optional list of paths to search for video files.
        config: Path to a DLC project ``config.yaml``. When provided (or
            auto-discovered), skeleton edges and source-video links are imported.
            Pass `None` (the default) to auto-discover ``config.yaml`` by walking
            up from the CSV, an explicit path to force a specific config, or
            `False` to disable config use entirely (strict legacy output).
        **kwargs: Additional arguments passed to DLC loader.

    Returns:
        Parsed labels as a `Labels` instance.
    """
    from sleap_io.io import dlc

    return dlc.load_dlc(
        filename, video_search_paths=video_search_paths, config=config, **kwargs
    )

Loading a whole DLC project

load_dlc_project loads every labeled-data/<video>/ folder in a project at once and merges them into a single Labels that shares one Skeleton (with edges) and one set of Tracks, recording provenance under the dlc_project, dlc_scorer, and dlc_task keys. A project directory or its config.yaml can also be passed to load_file, which routes it here automatically.

labels = sio.load_dlc_project("path/to/dlc_project")  # dir or config.yaml
labels = sio.load_file("path/to/dlc_project")          # auto-routed

sleap_io.io.main.load_dlc_project(config, video_search_paths=None, **kwargs)

Read an entire DeepLabCut project from its config.yaml.

All labeled-data/<video>/ folders are loaded and merged into a single Labels sharing one Skeleton (with edges from the config) and one set of Tracks, with each video linked back to its original via Video.source_video.

Parameters:

Name Type Description Default
config str | Path

Path to a DLC project config.yaml (or the project directory containing one).

required
video_search_paths list[str | Path] | None

Optional list of paths to search for video files.

None
**kwargs

Additional arguments passed to the DLC project loader.

required

Returns:

Type Description
Labels

Parsed labels as a Labels instance.

Source code in sleap_io/io/main.py
def load_dlc_project(
    config: str | Path,
    video_search_paths: list[str | Path] | None = None,
    **kwargs,
) -> Labels:
    """Read an entire DeepLabCut project from its ``config.yaml``.

    All ``labeled-data/<video>/`` folders are loaded and merged into a single
    `Labels` sharing one `Skeleton` (with edges from the config) and one set of
    `Track`s, with each video linked back to its original via
    `Video.source_video`.

    Args:
        config: Path to a DLC project ``config.yaml`` (or the project directory
            containing one).
        video_search_paths: Optional list of paths to search for video files.
        **kwargs: Additional arguments passed to the DLC project loader.

    Returns:
        Parsed labels as a `Labels` instance.
    """
    from sleap_io.io import dlc

    return dlc.load_dlc_project(config, video_search_paths=video_search_paths, **kwargs)

Importing train/test splits

load_dlc_splits recovers the train/test splits created by DLC's create_training_dataset, reading the positional indices stored in the project's Documentation_data-*.pickle and reconstructing DLC's globally, lexicographically sorted merge of all per-video annotations. It returns a LabelsSet with "train" and "test" keys.

Splits require the labeled images to be present on disk. For non-zero-padded image filenames a warning is emitted, since DLC's lexicographic ordering (e.g. img10 < img2) differs from numeric ordering and could otherwise cause silent train/test mis-assignment. If a project has multiple training fractions or shuffles, pass train_fraction= and/or shuffle= to disambiguate.

splits = sio.load_dlc_splits("path/to/dlc_project", shuffle=1)
train, test = splits["train"], splits["test"]

sleap_io.io.main.load_dlc_splits(config, shuffle=None, train_fraction=None, iteration=None, video_search_paths=None)

Read DeepLabCut train/test splits from a project's Documentation pickle.

Parameters:

Name Type Description Default
config str | Path

Path to a DLC project config.yaml (or the project directory).

required
shuffle int | None

The shuffle index to load. Required if more than one exists.

None
train_fraction float | None

The training fraction to load (e.g. 0.95). Required if more than one exists.

None
iteration int | None

The project iteration. Defaults to cfg['iteration'].

None
video_search_paths list[str | Path] | None

Optional list of paths to search for video files.

None

Returns:

Type Description
LabelsSet

A LabelsSet with "train" and "test" keys.

Source code in sleap_io/io/main.py
def load_dlc_splits(
    config: str | Path,
    shuffle: int | None = None,
    train_fraction: float | None = None,
    iteration: int | None = None,
    video_search_paths: list[str | Path] | None = None,
) -> "LabelsSet":
    """Read DeepLabCut train/test splits from a project's Documentation pickle.

    Args:
        config: Path to a DLC project ``config.yaml`` (or the project directory).
        shuffle: The shuffle index to load. Required if more than one exists.
        train_fraction: The training fraction to load (e.g. ``0.95``). Required
            if more than one exists.
        iteration: The project iteration. Defaults to ``cfg['iteration']``.
        video_search_paths: Optional list of paths to search for video files.

    Returns:
        A `LabelsSet` with ``"train"`` and ``"test"`` keys.
    """
    from sleap_io.io import dlc

    return dlc.load_dlc_splits(
        config,
        shuffle=shuffle,
        train_fraction=train_fraction,
        iteration=iteration,
        video_search_paths=video_search_paths,
    )