Skip to content

Data formats

sleap-io provides a unified interface for reading and writing pose tracking data across multiple formats. The library automatically detects file formats and provides harmonized I/O operations.

Universal I/O Functions

sleap_io.io.main.load_file(filename, format=None, *, sniff=None, **kwargs)

Load a file and return the appropriate object.

Parameters:

Name Type Description Default
filename str | Path

Path to a file, or a URL (http, https, s3, gs, gcs, az, abfs). Google Drive file share links are also supported; the file is downloaded and its format detected from the content (pass an explicit format= to skip the detection download).

required
format str | None

Optional format to load as. If not provided, will be inferred from the file extension. Available formats are: "slp", "nwb", "geojson", "alphatracker", "labelstudio", "coco", "jabs", "analysis_h5", "dlc", "trackmate", "ultralytics", "leap", and "video".

None
sniff bool | None

Controls magic-byte sniffing for URLs with ambiguous extensions (.h5, .json, .csv). If True, fetch the first bytes via a Range request to disambiguate. If None (default), sniff only for URLs with ambiguous extensions (never for local paths, where opening the file is cheap). If False, never sniff; raise ValueError on an ambiguous URL extension when no explicit format is given.

None
**kwargs

Additional arguments passed to the format-specific loading function: - For "slp" format: No additional arguments. - For "nwb" format: No additional arguments. - For "alphatracker" format: No additional arguments. - For "leap" format: skeleton (Optional[Skeleton]): Skeleton to use if not defined in the file. - For "labelstudio" format: skeleton (Optional[Skeleton]): Skeleton to use for the labels. - For "coco" format: dataset_root (Optional[str]): Root directory of the dataset. grayscale (bool): If True, load images as grayscale (1 channel). If False, load as RGB (3 channels). Default is False. segmentation_format (str): How to represent polygon segmentation. "mask" (default) rasterizes polygons into SegmentationMask objects; "roi" keeps them as vector ROI objects. category_as_track (bool): If True, treat each COCO category as a persistent identity, creating one Track per category. Default is False. - For "jabs" format: skeleton (Optional[Skeleton]): Skeleton to use for the labels. - For "analysis_h5" format: video (Optional[Video | str]): Video to associate with data. If None, uses video_path stored in the file. - For "dlc" format: video_search_paths (Optional[List[str]]): Paths to search for video files. - For "ultralytics" format: See load_ultralytics for supported arguments. - For "video" format: See load_video for supported arguments.

required

Returns:

Type Description
Labels | Video

A Labels or Video object.

Source code in sleap_io/io/main.py
def load_file(
    filename: str | Path,
    format: str | None = None,
    *,
    sniff: bool | None = None,
    **kwargs,
) -> Labels | Video:
    """Load a file and return the appropriate object.

    Args:
        filename: Path to a file, or a URL (`http`, `https`, `s3`, `gs`, `gcs`,
            `az`, `abfs`). Google Drive file share links are also supported; the
            file is downloaded and its format detected from the content (pass an
            explicit `format=` to skip the detection download).
        format: Optional format to load as. If not provided, will be inferred from the
            file extension. Available formats are: "slp", "nwb", "geojson",
            "alphatracker", "labelstudio", "coco", "jabs", "analysis_h5", "dlc",
            "trackmate", "ultralytics", "leap", and "video".
        sniff: Controls magic-byte sniffing for URLs with ambiguous extensions
            (`.h5`, `.json`, `.csv`). If `True`, fetch the first bytes via a
            Range request to disambiguate. If `None` (default), sniff only for
            URLs with ambiguous extensions (never for local paths, where opening
            the file is cheap). If `False`, never sniff; raise `ValueError` on an
            ambiguous URL extension when no explicit `format` is given.
        **kwargs: Additional arguments passed to the format-specific loading function:
            - For "slp" format: No additional arguments.
            - For "nwb" format: No additional arguments.
            - For "alphatracker" format: No additional arguments.
            - For "leap" format: skeleton (Optional[Skeleton]): Skeleton to use if not
              defined in the file.
            - For "labelstudio" format: skeleton (Optional[Skeleton]): Skeleton to
              use for
              the labels.
            - For "coco" format: dataset_root (Optional[str]): Root directory of the
              dataset. grayscale (bool): If True, load images as grayscale (1 channel).
              If False, load as RGB (3 channels). Default is False.
              segmentation_format (str): How to represent polygon segmentation.
              "mask" (default) rasterizes polygons into `SegmentationMask` objects;
              "roi" keeps them as vector `ROI` objects. category_as_track (bool): If
              True, treat each COCO category as a persistent identity, creating one
              `Track` per category. Default is False.
            - For "jabs" format: skeleton (Optional[Skeleton]): Skeleton to use for
              the labels.
            - For "analysis_h5" format: video (Optional[Video | str]): Video to
              associate with data. If None, uses video_path stored in the file.
            - For "dlc" format: video_search_paths (Optional[List[str]]): Paths to
              search for video files.
            - For "ultralytics" format: See `load_ultralytics` for supported arguments.
            - For "video" format: See `load_video` for supported arguments.

    Returns:
        A `Labels` or `Video` object.
    """
    if isinstance(filename, Path):
        filename = filename.as_posix()

    from sleap_io.io import _remote

    if _remote._is_url(filename):
        return _load_file_url(filename, format=format, sniff=sniff, **kwargs)

    if format is None:
        if filename.lower().endswith(".slp"):
            format = "slp"
        elif filename.lower().endswith(".nwb"):
            format = "nwb"
        elif filename.lower().endswith(".mat"):
            format = "leap"
        elif filename.lower().endswith(".json"):
            # Detect JSON format: AlphaTracker, COCO, or Label Studio
            if _detect_alphatracker_format(filename):
                format = "alphatracker"
            elif _detect_coco_format(filename):
                format = "coco"
            else:
                format = "json"
        elif filename.lower().endswith(".h5"):
            # Check if this is Analysis HDF5 or JABS
            from sleap_io.io import analysis_h5

            if analysis_h5.is_analysis_h5_file(filename):
                format = "analysis_h5"
            else:
                format = "jabs"
        elif filename.lower().endswith(".geojson"):
            format = "geojson"
        elif filename.endswith("data.yaml") or (
            Path(filename).is_dir() and (Path(filename) / "data.yaml").exists()
        ):
            format = "ultralytics"
        elif filename.endswith("config.yaml") or Path(filename).is_dir():
            from sleap_io.io import dlc

            if dlc._is_dlc_project_path(filename):
                format = "dlc_project"
        elif filename.lower().endswith(".csv"):
            from sleap_io.io import dlc, trackmate

            if trackmate.is_trackmate_file(filename):
                format = "trackmate"
            elif dlc.is_dlc_file(filename):
                format = "dlc"
            else:
                format = "csv"
        else:
            for vid_ext in Video.EXTS:
                if filename.lower().endswith(vid_ext.lower()):
                    format = "video"
                    break
        if format is None:
            raise ValueError(f"Could not infer format from filename: '{filename}'.")

    if filename.lower().endswith(".slp"):
        return load_slp(filename, **kwargs)
    elif filename.lower().endswith(".nwb"):
        return load_nwb(filename, **kwargs)
    elif filename.lower().endswith(".mat"):
        return load_leap(filename, **kwargs)
    elif filename.lower().endswith(".json"):
        if format == "alphatracker":
            return load_alphatracker(filename, **kwargs)
        elif format == "coco":
            return load_coco(filename, **kwargs)
        else:
            return load_labelstudio(filename, **kwargs)
    elif filename.lower().endswith(".h5"):
        if format == "analysis_h5":
            return load_analysis_h5(filename, **kwargs)
        else:
            return load_jabs(filename, **kwargs)
    elif format == "dlc":
        return load_dlc(filename, **kwargs)
    elif format == "dlc_project":
        return load_dlc_project(filename, **kwargs)
    elif format == "csv":
        return load_csv(filename, **kwargs)
    elif format == "trackmate":
        return load_trackmate(filename, **kwargs)
    elif format == "ultralytics":
        return load_ultralytics(filename, **kwargs)
    elif format == "geojson":
        return Labels(rois=load_geojson(filename))
    elif format == "video":
        return load_video(filename, **kwargs)

sleap_io.io.main.save_file(labels, filename, format=None, verbose=True, progress_callback=None, **kwargs)

Save a file based on the extension.

Parameters:

Name Type Description Default
labels Labels

A SLEAP Labels object (see load_slp).

required
filename str | Path

Path to save labels to.

required
format str | None

Optional format to save as. If not provided, will be inferred from the file extension. Available formats are: "slp", "nwb", "labelstudio", "coco", "jabs", "analysis_h5", "ultralytics", and "geojson".

None
verbose bool

If True (the default), display a progress bar when embedding frames (only applies to the SLP format).

True
progress_callback Callable[[int, int, str], bool] | None

Optional callback function called during frame embedding (SLP format only) with (current, total, phase) arguments, where phase is "embed" or "write". If it returns False, the operation is cancelled and ExportCancelled is raised. The phase argument is a breaking change from the previous (current, total) signature.

None
**kwargs

Additional arguments passed to the format-specific saving function: - For "slp" format: embed (bool | str | list[tuple[Video, int]] | None): Frames to embed in the saved labels file. One of None, True, "all", "user", "suggestions", "user+suggestions", "source" or list of tuples of (video, frame_idx). If False (the default), no frames are embedded. embed_inplace (bool): If False (default), copy labels before embedding to avoid mutating the input. If True, modify labels in-place. - For "nwb" format: pose_estimation_metadata (dict): Metadata to store in the NWB file. append (bool): If True, append to existing NWB file. - For "labelstudio" format: No additional arguments. - For "coco" format: image_filenames (Optional[Union[str, List[str]]]): Image filenames to use. visibility_encoding (str): Either "binary" or "ternary" (default). - For "jabs" format: pose_version (int): JABS pose format version (1-6). root_folder (Optional[str]): Root folder for JABS project structure. - For "analysis_h5" format: See save_analysis_h5 for supported arguments. - For "ultralytics" format: See save_ultralytics for supported arguments.

required
Source code in sleap_io/io/main.py
def save_file(
    labels: Labels,
    filename: str | Path,
    format: str | None = None,
    verbose: bool = True,
    progress_callback: Callable[[int, int, str], bool] | None = None,
    **kwargs,
):
    """Save a file based on the extension.

    Args:
        labels: A SLEAP `Labels` object (see `load_slp`).
        filename: Path to save labels to.
        format: Optional format to save as. If not provided, will be inferred from the
            file extension. Available formats are: "slp", "nwb", "labelstudio", "coco",
            "jabs", "analysis_h5", "ultralytics", and "geojson".
        verbose: If `True` (the default), display a progress bar when embedding frames
            (only applies to the SLP format).
        progress_callback: Optional callback function called during frame embedding
            (SLP format only) with `(current, total, phase)` arguments, where
            ``phase`` is ``"embed"`` or ``"write"``. If it returns `False`, the
            operation is cancelled and `ExportCancelled` is raised. The ``phase``
            argument is a breaking change from the previous ``(current, total)``
            signature.
        **kwargs: Additional arguments passed to the format-specific saving function:
            - For "slp" format: embed (bool | str | list[tuple[Video, int]] |
              None): Frames
              to embed in the saved labels file. One of None, True, "all", "user",
              "suggestions", "user+suggestions", "source" or list of tuples of
              (video, frame_idx). If False (the default), no frames are embedded.
              embed_inplace (bool): If False (default), copy labels before embedding
              to avoid mutating the input. If True, modify labels in-place.
            - For "nwb" format: pose_estimation_metadata (dict): Metadata to store
              in the
              NWB file. append (bool): If True, append to existing NWB file.
            - For "labelstudio" format: No additional arguments.
            - For "coco" format: image_filenames (Optional[Union[str, List[str]]]):
              Image filenames to use. visibility_encoding (str): Either "binary" or
              "ternary" (default).
            - For "jabs" format: pose_version (int): JABS pose format version (1-6).
              root_folder (Optional[str]): Root folder for JABS project structure.
            - For "analysis_h5" format: See `save_analysis_h5` for supported arguments.
            - For "ultralytics" format: See `save_ultralytics` for supported arguments.
    """
    if isinstance(filename, Path):
        filename = str(filename)

    if format is None:
        if filename.lower().endswith(".slp"):
            format = "slp"
        elif filename.lower().endswith(".nwb"):
            format = "nwb"
        elif filename.lower().endswith(".json"):
            # Check if this should be COCO format based on kwargs
            if "visibility_encoding" in kwargs or "image_filenames" in kwargs:
                format = "coco"
            else:
                format = "labelstudio"
        elif filename.lower().endswith(".h5") or filename.lower().endswith(
            ".analysis.h5"
        ):
            # Analysis HDF5 can be detected by extension pattern or kwargs
            if "min_occupancy" in kwargs or filename.lower().endswith(".analysis.h5"):
                format = "analysis_h5"
            elif "pose_version" in kwargs:
                format = "jabs"
            else:
                # Default to analysis_h5 for .h5 extension without specific jabs kwargs
                format = "analysis_h5"
        elif filename.lower().endswith(".geojson"):
            format = "geojson"
        elif "pose_version" in kwargs:
            format = "jabs"
        elif "split_ratios" in kwargs or Path(filename).is_dir():
            format = "ultralytics"

    if format == "slp":
        save_slp(
            labels,
            filename,
            verbose=verbose,
            progress_callback=progress_callback,
            **kwargs,
        )
    elif format == "nwb":
        save_nwb(labels, filename, **kwargs)
    elif format == "labelstudio":
        save_labelstudio(labels, filename, **kwargs)
    elif format == "coco":
        save_coco(labels, filename, **kwargs)
    elif format == "jabs":
        pose_version = kwargs.pop("pose_version", 5)
        root_folder = kwargs.pop("root_folder", filename)
        save_jabs(labels, pose_version=pose_version, root_folder=root_folder)
    elif format == "analysis_h5":
        # Filter kwargs to those accepted by save_analysis_h5
        analysis_kwargs = {
            k: v
            for k, v in kwargs.items()
            if k
            in (
                "video",
                "labels_path",
                "all_frames",
                "min_occupancy",
                "preset",
                "frame_dim",
                "track_dim",
                "node_dim",
                "xy_dim",
                "save_metadata",
            )
        }
        save_analysis_h5(labels, filename, **analysis_kwargs)
    elif format == "ultralytics":
        save_ultralytics(labels, filename, **kwargs)
    elif format == "geojson":
        save_geojson(labels.rois, filename)
    elif format == "csv" or filename.lower().endswith(".csv"):
        csv_format = kwargs.pop("csv_format", "sleap")
        # Filter kwargs to only those accepted by save_csv
        csv_kwargs = {
            k: v
            for k, v in kwargs.items()
            if k in ("video", "include_score", "scorer", "save_metadata")
        }
        save_csv(labels, filename, format=csv_format, **csv_kwargs)
    else:
        raise ValueError(f"Unknown format '{format}' for filename: '{filename}'.")

Video I/O

sleap_io.io.main.load_video(filename, **kwargs)

Load a video file.

Remote media videos can be loaded from http/https URLs (see the filename argument). Only http/https URLs are supported for video (cloud schemes are not), and the av package is required (install with pip install 'sleap-io[pyav]').

Warning

Decoding a remote video streams bytes from the URL into FFmpeg (via pyav), whose demuxers/decoders are a large, historically vulnerability-prone attack surface. Load remote video only from trusted sources, and sandbox untrusted inputs (e.g. decode in an isolated container/VM with no credentials and a restricted network). sleap-io only passes http/https URLs through to the decoder.

Parameters:

Name Type Description Default
filename str

The filename(s) of the video. Supported extensions: "mp4", "avi", "mov", "mj2", "mkv", "h5", "hdf5", "slp", "png", "jpg", "jpeg", "tif", "tiff", "bmp", "seq". If the filename is a list, a list of image filenames are expected. If filename is a folder, it will be searched for images. May also be an http(s):// URL pointing to a remote media video (one of "mp4", "avi", "mov", "mj2", "mkv"). Remote videos are read with the pyav plugin, which is selected automatically for URLs; it requires the av package (install with pip install 'sleap-io[pyav]'). See the security warning above. Google Drive share links are not supported for video (Drive download links carry no file extension and reject the range requests video streaming relies on); download the video file first, then load it locally.

required
**kwargs

Additional arguments passed to Video.from_filename. Currently supports: - dataset: Name of dataset in HDF5 file. - grayscale: Whether to force grayscale. If None, autodetect on first frame load. - keep_open: Whether to keep the video reader open between calls to read frames. If False, will close the reader after each call. If True (the default), it will keep the reader open and cache it for subsequent calls which may enhance the performance of reading multiple frames. - source_video: Source video object if this is a proxy video. This is metadata and does not affect reading. - backend_metadata: Metadata to store on the video backend. This is useful for storing metadata that requires an open backend (e.g., shape information) without having to open the backend. - plugin: Video plugin to use for MediaVideo backend. One of "opencv", "FFMPEG", or "pyav". Also accepts aliases (case-insensitive): * opencv: "opencv", "cv", "cv2", "ocv" * FFMPEG: "FFMPEG", "ffmpeg", "imageio-ffmpeg", "imageio_ffmpeg" * pyav: "pyav", "av"

If not specified, uses the following priority: 1. Global default set via sio.set_default_video_plugin() 2. Auto-detection based on available packages

To set a global default:

import sleap_io as sio sio.set_default_video_plugin("opencv") video = sio.load_video("video.mp4") # Uses opencv - input_format: Format of the data in HDF5 datasets. One of "channels_last" (the default) in (frames, height, width, channels) order or "channels_first" in (frames, channels, width, height) order. - frame_map: Mapping from frame indices to indices in the HDF5 dataset. This is used to translate between frame indices of images within their source video and indices of images in the dataset. - source_filename: Path to the source video file for HDF5 embedded videos. - source_inds: Indices of frames in the source video file for HDF5 embedded videos. - image_format: Format of images in HDF5 embedded dataset.

required

Returns:

Type Description
Video

A Video object.

Raises:

Type Description
NotImplementedError

If filename is a Google Drive share link (Drive video loading is not supported; download the file first).

See Also

set_default_video_plugin: Set the default video plugin globally. get_default_video_plugin: Get the current default video plugin.

Source code in sleap_io/io/main.py
def load_video(filename: str, **kwargs) -> Video:
    """Load a video file.

    Remote media videos can be loaded from ``http``/``https`` URLs (see the
    ``filename`` argument). Only ``http``/``https`` URLs are supported for video
    (cloud schemes are not), and the ``av`` package is required (install with
    ``pip install 'sleap-io[pyav]'``).

    Warning:
        Decoding a remote video streams bytes from the URL into FFmpeg (via
        pyav), whose demuxers/decoders are a large, historically
        vulnerability-prone attack surface. Load remote video only from trusted
        sources, and sandbox untrusted inputs (e.g. decode in an isolated
        container/VM with no credentials and a restricted network). sleap-io
        only passes ``http``/``https`` URLs through to the decoder.

    Args:
        filename: The filename(s) of the video. Supported extensions: "mp4", "avi",
            "mov", "mj2", "mkv", "h5", "hdf5", "slp", "png", "jpg", "jpeg", "tif",
            "tiff", "bmp", "seq". If the filename is a list, a list of image filenames
            are expected. If filename is a folder, it will be searched for images.
            May also be an ``http(s)://`` URL pointing to a remote media video
            (one of "mp4", "avi", "mov", "mj2", "mkv"). Remote videos are read
            with the pyav plugin, which is selected automatically for URLs; it
            requires the ``av`` package (install with
            ``pip install 'sleap-io[pyav]'``). See the security warning above.
            Google Drive share links are **not** supported for video (Drive
            download links carry no file extension and reject the range
            requests video streaming relies on); download the video file first,
            then load it locally.
        **kwargs: Additional arguments passed to `Video.from_filename`.
            Currently supports:
            - dataset: Name of dataset in HDF5 file.
            - grayscale: Whether to force grayscale. If None, autodetect on first
              frame load.
            - keep_open: Whether to keep the video reader open between calls to read
              frames.
              If False, will close the reader after each call. If True (the
              default), it will
              keep the reader open and cache it for subsequent calls which may
              enhance the
              performance of reading multiple frames.
            - source_video: Source video object if this is a proxy video. This is
              metadata
              and does not affect reading.
            - backend_metadata: Metadata to store on the video backend. This is
              useful for
              storing metadata that requires an open backend (e.g., shape
              information) without
              having to open the backend.
            - plugin: Video plugin to use for MediaVideo backend. One of "opencv",
              "FFMPEG",
              or "pyav". Also accepts aliases (case-insensitive):
              * opencv: "opencv", "cv", "cv2", "ocv"
              * FFMPEG: "FFMPEG", "ffmpeg", "imageio-ffmpeg", "imageio_ffmpeg"
              * pyav: "pyav", "av"

              If not specified, uses the following priority:
              1. Global default set via `sio.set_default_video_plugin()`
              2. Auto-detection based on available packages

              To set a global default:
              >>> import sleap_io as sio
              >>> sio.set_default_video_plugin("opencv")
              >>> video = sio.load_video("video.mp4")  # Uses opencv
            - input_format: Format of the data in HDF5 datasets. One of
              "channels_last" (the
              default) in (frames, height, width, channels) order or "channels_first" in
              (frames, channels, width, height) order.
            - frame_map: Mapping from frame indices to indices in the HDF5 dataset.
              This is
              used to translate between frame indices of images within their source
              video
              and indices of images in the dataset.
            - source_filename: Path to the source video file for HDF5 embedded videos.
            - source_inds: Indices of frames in the source video file for HDF5
              embedded videos.
            - image_format: Format of images in HDF5 embedded dataset.

    Returns:
        A `Video` object.

    Raises:
        NotImplementedError: If ``filename`` is a Google Drive share link
            (Drive video loading is not supported; download the file first).

    See Also:
        set_default_video_plugin: Set the default video plugin globally.
        get_default_video_plugin: Get the current default video plugin.
    """
    return Video.from_filename(filename, **kwargs)

sleap_io.io.main.save_video(frames, filename, fps=30, pixelformat='yuv420p', codec='libx264', crf=25, preset='superfast', output_params=None)

Write a list of frames to a video file.

Parameters:

Name Type Description Default
frames ndarray | Video

Sequence of frames to write to video. Each frame should be a 2D or 3D numpy array with dimensions (height, width) or (height, width, channels).

required
filename str | Path

Path to output video file.

required
fps float

Frames per second. Defaults to 30.

30
pixelformat str

Pixel format for video. Defaults to "yuv420p".

'yuv420p'
codec str

Codec to use for encoding. Defaults to "libx264".

'libx264'
crf int

Constant rate factor to control lossiness of video. Values go from 2 to 32, with numbers in the 18 to 30 range being most common. Lower values mean less compressed/higher quality. Defaults to 25. No effect if codec is not "libx264".

25
preset str

H264 encoding preset. Defaults to "superfast". No effect if codec is not "libx264".

'superfast'
output_params list | None

Additional output parameters for FFMPEG. This should be a list of strings corresponding to command line arguments for FFMPEG and libx264. Use ffmpeg -h encoder=libx264 to see all options for libx264 output_params.

None

See also: sio.VideoWriter

Source code in sleap_io/io/main.py
def save_video(
    frames: np.ndarray | Video,
    filename: str | Path,
    fps: float = 30,
    pixelformat: str = "yuv420p",
    codec: str = "libx264",
    crf: int = 25,
    preset: str = "superfast",
    output_params: list | None = None,
):
    """Write a list of frames to a video file.

    Args:
        frames: Sequence of frames to write to video. Each frame should be a 2D or 3D
            numpy array with dimensions (height, width) or (height, width, channels).
        filename: Path to output video file.
        fps: Frames per second. Defaults to 30.
        pixelformat: Pixel format for video. Defaults to "yuv420p".
        codec: Codec to use for encoding. Defaults to "libx264".
        crf: Constant rate factor to control lossiness of video. Values go from 2 to 32,
            with numbers in the 18 to 30 range being most common. Lower values mean less
            compressed/higher quality. Defaults to 25. No effect if codec is not
            "libx264".
        preset: H264 encoding preset. Defaults to "superfast". No effect if codec is not
            "libx264".
        output_params: Additional output parameters for FFMPEG. This should be a list of
            strings corresponding to command line arguments for FFMPEG and libx264. Use
            `ffmpeg -h encoder=libx264` to see all options for libx264 output_params.

    See also: `sio.VideoWriter`
    """
    from sleap_io.io import video_writing

    if output_params is None:
        output_params = []

    with video_writing.VideoWriter(
        filename,
        fps=fps,
        pixelformat=pixelformat,
        codec=codec,
        crf=crf,
        preset=preset,
        output_params=output_params,
    ) as writer:
        for frame in frames:
            writer(frame)

Media videos can also be read from http/https URLs with load_video (requires the pyav extra; cloud schemes and Google Drive are not supported for video). See Remote video.

Norpix .seq Format

The .seq format is used by StreamPix / Norpix for high-speed video recording, commonly used in behavioral neuroscience. sleap-io provides native read support for .seq files via the SeqVideo backend.

video = sio.load_video("recording.seq")
frame = video[0]  # Read first frame

Supported codecs: uncompressed grayscale (monoraw), uncompressed BGR (raw), JPEG compressed (monojpg, jpg), and PNG compressed (monopng, png). Per-frame timestamps are accessible via the backend:

backend = video.backend  # SeqVideo instance
ts = backend.get_timestamp(0)    # Timestamp of first frame (seconds since epoch)
ts_all = backend.get_timestamps()  # All timestamps as numpy array

To convert .seq to MP4:

sio reencode recording.seq -o recording.mp4

Format-Specific Functions

SLEAP Native Format (.slp)

The native SLEAP format stores complete pose tracking projects including videos, skeletons, and annotations. SLP is the primary format with full round-trip support for bounding boxes (format 1.7+), regions of interest (ROIs), and segmentation masks (format 1.5+).

.slp and .pkg.slp files can also be loaded directly from http/https, cloud (s3://, gs://, az://), and Google Drive URLs with lazy range-based streaming via load_slp — see Loading from URLs.

Detailed Format Specification

For comprehensive documentation of the SLP file format including HDF5 layout, data structures, and version history, see the SLP File Format Reference.

sleap_io.io.main.load_slp(filename, open_videos=True, lazy=False, *, headers=None, stream_mode='auto', cache_storage=None, cache_expiry=None, block_size=1048576, max_blocks=32, retries=3, _file_like=None)

Load a SLEAP dataset from a local path or HTTP/cloud URL.

For local paths, all URL-specific keyword arguments are ignored.

Parameters:

Name Type Description Default
filename str | PathLike

Path to a SLEAP labels file (.slp), or a URL. Supported URL schemes: http, https, s3, gs, gcs, az, abfs. Cloud schemes require pip install 'sleap-io[cloud]'. Google Drive share links (https://drive.google.com/file/d/<ID>/view) are also supported and resolved to a direct download automatically (the file is fully downloaded into memory; folder links are not supported).

required
open_videos bool

If True (the default), attempt to open the video backend for I/O. If False, the backend will not be opened (useful for reading metadata when the video files are not available).

True
lazy bool

If True, defer instance materialization for faster loading. Lazy-loaded Labels support read operations and fast numpy/save. To modify, call labels.materialize() first. Default is False.

False
headers dict[str, str] | None

HTTP headers (e.g. {"Authorization": "Bearer ..."}) forwarded to fsspec for URL loads. Stripped on cross-origin redirect. Ignored for local paths.

None
stream_mode str

Remote streaming strategy (ignored for local paths). One of: "auto" (default; uses fsspec blockcache for lazy range reads), "blockcache", "cache" (full download via simplecache), "filecache" (download with ETag revalidation), or "download" (ephemeral full download into memory).

'auto'
cache_storage str | PathLike | None

Override fsspec's cache directory for cache/filecache modes. Ignored for local paths.

None
cache_expiry float | None

TTL (seconds) for filecache revalidation. Defaults to 3600 (1h) when not given. Ignored for other modes and local paths.

None
block_size int

Range block size in bytes for blockcache mode. Default: 1 MiB. Ignored for local paths.

1048576
max_blocks int

Max blocks kept in the in-memory LRU per open file. Default: 32 (32 MiB cap per open file). Ignored for local paths.

32
retries int

Retry count for transient HTTP errors. Default: 3. Ignored for local paths.

3

Returns:

Type Description
Labels

The dataset as a Labels object.

Raises:

Type Description
RemoteIOError

For HTTP errors against URLs (404, 416, 5xx after retries, connection failures).

ImportError

For cloud schemes when the corresponding extra is not installed.

ValueError

For an unrecognized stream_mode.

See Also

Labels.is_lazy: Check if Labels is lazy-loaded. Labels.materialize: Convert lazy Labels to eager.

Source code in sleap_io/io/main.py
def load_slp(
    filename: str | os.PathLike,
    open_videos: bool = True,
    lazy: bool = False,
    *,
    headers: dict[str, str] | None = None,
    stream_mode: str = "auto",
    cache_storage: str | os.PathLike | None = None,
    cache_expiry: float | None = None,
    block_size: int = 1 << 20,
    max_blocks: int = 32,
    retries: int = 3,
    _file_like: Any | None = None,
) -> Labels:
    """Load a SLEAP dataset from a local path or HTTP/cloud URL.

    For local paths, all URL-specific keyword arguments are ignored.

    Args:
        filename: Path to a SLEAP labels file (`.slp`), or a URL. Supported URL
            schemes: `http`, `https`, `s3`, `gs`, `gcs`, `az`, `abfs`. Cloud
            schemes require `pip install 'sleap-io[cloud]'`. Google Drive share
            links (`https://drive.google.com/file/d/<ID>/view`) are also
            supported and resolved to a direct download automatically (the file
            is fully downloaded into memory; folder links are not supported).
        open_videos: If `True` (the default), attempt to open the video backend for
            I/O. If `False`, the backend will not be opened (useful for reading metadata
            when the video files are not available).
        lazy: If `True`, defer instance materialization for faster loading.
            Lazy-loaded Labels support read operations and fast numpy/save.
            To modify, call `labels.materialize()` first. Default is `False`.
        headers: HTTP headers (e.g. `{"Authorization": "Bearer ..."}`) forwarded
            to fsspec for URL loads. Stripped on cross-origin redirect. Ignored
            for local paths.
        stream_mode: Remote streaming strategy (ignored for local paths). One of:
            `"auto"` (default; uses fsspec `blockcache` for lazy range reads),
            `"blockcache"`, `"cache"` (full download via `simplecache`),
            `"filecache"` (download with ETag revalidation), or `"download"`
            (ephemeral full download into memory).
        cache_storage: Override fsspec's cache directory for `cache`/`filecache`
            modes. Ignored for local paths.
        cache_expiry: TTL (seconds) for `filecache` revalidation. Defaults to
            3600 (1h) when not given. Ignored for other modes and local paths.
        block_size: Range block size in bytes for `blockcache` mode. Default:
            1 MiB. Ignored for local paths.
        max_blocks: Max blocks kept in the in-memory LRU per open file. Default:
            32 (32 MiB cap per open file). Ignored for local paths.
        retries: Retry count for transient HTTP errors. Default: 3. Ignored for
            local paths.

    Returns:
        The dataset as a `Labels` object.

    Raises:
        RemoteIOError: For HTTP errors against URLs (404, 416, 5xx after
            retries, connection failures).
        ImportError: For cloud schemes when the corresponding extra is not
            installed.
        ValueError: For an unrecognized `stream_mode`.

    See Also:
        Labels.is_lazy: Check if Labels is lazy-loaded.
        Labels.materialize: Convert lazy Labels to eager.
    """
    import h5py

    from sleap_io.io import _remote, slp

    if _remote._is_url(filename):
        url = os.fspath(filename) if isinstance(filename, os.PathLike) else filename
        # ``_file_like`` lets a caller hand in an already-resolved file-like
        # (private; used by the Google Drive auto-detect path to reuse the bytes
        # it had to download to sniff the format, rather than re-resolving the
        # link a second time against Drive's per-file download quota). When
        # provided, the caller owns closing it.
        owns_file_like = _file_like is None
        file_like = (
            _remote.open_url(
                url,
                headers=headers,
                stream_mode=stream_mode,
                cache_storage=cache_storage,
                cache_expiry=cache_expiry,
                block_size=block_size,
                max_blocks=max_blocks,
                retries=retries,
            )
            if owns_file_like
            else _file_like
        )
        resolved_mode = "blockcache" if stream_mode == "auto" else stream_mode

        # Google Drive resolves to a full in-memory BytesIO (no range support).
        # Capture its bytes once so the long-lived label-image reopen reuses them
        # instead of re-resolving (and re-downloading) the Drive link.
        from sleap_io.io._gdrive import _is_gdrive_url

        url_bytes = None
        if _is_gdrive_url(url) and hasattr(file_like, "getvalue"):
            url_bytes = file_like.getvalue()

        try:
            with h5py.File(file_like, "r") as f:
                reader = (
                    slp._read_labels_lazy_from_open_file
                    if lazy
                    else slp._read_labels_from_open_file
                )
                labels = reader(
                    url,
                    f,
                    open_videos=open_videos,
                    _url_headers=headers,
                    _url_stream_mode=resolved_mode,
                    _url_bytes=url_bytes,
                )
        finally:
            if owns_file_like:
                file_like.close()

        # The URL auth context (headers/resolved_mode) is threaded into each
        # video backend at construction time and persisted on the Video by
        # `make_video` (via `_read_labels_*_from_open_file` -> `read_videos`), so
        # the embedded HDF5Video probe is authenticated and later frame reads /
        # existence probes / reopens stay authenticated. No post-hoc backfill.
        return labels

    # Local path - UNCHANGED behaviour; URL-specific kwargs are no-ops.
    if lazy:
        return slp._read_labels_lazy(filename, open_videos=open_videos)
    return slp.read_labels(filename, open_videos=open_videos)

sleap_io.io.main.save_slp(labels, filename, embed=False, restore_original_videos=True, embed_inplace=False, verbose=True, plugin=None, progress_callback=None, prefer_metadata=True, preserve_unknown=False, save_embedding_vectors=False)

Save a SLEAP dataset to a .slp file.

Parameters:

Name Type Description Default
labels Labels

A SLEAP Labels object (see load_slp).

required
filename str

Path to save labels to ending with .slp.

required
embed bool | str | list[tuple[Video, int]] | None

Frames to embed in the saved labels file. One of None, True, "all", "user", "suggestions", "user+suggestions", "source" or list of tuples of (video, frame_idx).

If False is specified (the default), the source video will be restored if available, otherwise the embedded frames will be re-saved.

If True or "all", all labeled frames and suggested frames will be embedded.

If "source" is specified, no images will be embedded and the source video will be restored if available.

This argument is only valid for the SLP backend.

False
restore_original_videos bool

If True (default) and embed=False, use original video files. If False and embed=False, keep references to source .pkg.slp files. Only applies when embed=False.

True
embed_inplace bool

If False (default), a copy of the labels is made before embedding to avoid modifying the in-memory labels. If True, the labels will be modified in-place to point to the embedded videos, which is faster but mutates the input. Only applies when embedding.

False
verbose bool

If True (the default), display a progress bar when embedding frames.

True
plugin str | None

Image plugin to use for encoding embedded frames. One of "opencv" or "imageio". If None, uses the global default from get_default_image_plugin(). If no global default is set, auto-detects based on available packages (opencv preferred, then imageio).

None
progress_callback Callable[[int, int, str], bool] | None

Optional callback function called during embedding with (current, total, phase) arguments, where phase is "embed" or "write". If it returns False, the operation is cancelled and ExportCancelled is raised. When provided, tqdm progress bars are disabled in favor of the callback. The phase argument is a breaking change from the previous (current, total) signature.

None
prefer_metadata bool

If True (the default), serialize each uncropped video's shape/grayscale/fps from its backend_metadata when recorded there instead of querying the live backend. For an open MediaVideo this avoids decoding a frame (and leaving a resident decoder) just to recompute already-known metadata. Set to False to always read shape/grayscale/fps through the live backend.

True
preserve_unknown bool

If True, top-level HDF5 datasets/groups in the source file that sleap-io does not recognize are carried over into the saved file. This preserves additions from a newer sleap-io version across a load/save cycle. Default False. Best-effort (requires the source file to still exist and be readable HDF5). See write_labels.

False
save_embedding_vectors bool

If False (the default), skip the /embeddings group entirely -- appearance vectors are large on disk, so only the identity links are persisted by default (the vectors stay in memory, e.g. to build identity prototypes). This mirrors embed, which is also off by default for video frames. Set True to also write the /embeddings group. Identity links (/identity/links) are written regardless.

False
Source code in sleap_io/io/main.py
def save_slp(
    labels: Labels,
    filename: str,
    embed: bool | str | list[tuple[Video, int]] | None = False,
    restore_original_videos: bool = True,
    embed_inplace: bool = False,
    verbose: bool = True,
    plugin: str | None = None,
    progress_callback: Callable[[int, int, str], bool] | None = None,
    prefer_metadata: bool = True,
    preserve_unknown: bool = False,
    save_embedding_vectors: bool = False,
):
    """Save a SLEAP dataset to a `.slp` file.

    Args:
        labels: A SLEAP `Labels` object (see `load_slp`).
        filename: Path to save labels to ending with `.slp`.
        embed: Frames to embed in the saved labels file. One of `None`, `True`,
            `"all"`, `"user"`, `"suggestions"`, `"user+suggestions"`, `"source"` or list
            of tuples of `(video, frame_idx)`.

            If `False` is specified (the default), the source video will be restored
            if available, otherwise the embedded frames will be re-saved.

            If `True` or `"all"`, all labeled frames and suggested frames will be
            embedded.

            If `"source"` is specified, no images will be embedded and the source video
            will be restored if available.

            This argument is only valid for the SLP backend.
        restore_original_videos: If `True` (default) and `embed=False`, use original
            video files. If `False` and `embed=False`, keep references to source
            `.pkg.slp` files. Only applies when `embed=False`.
        embed_inplace: If `False` (default), a copy of the labels is made before
            embedding to avoid modifying the in-memory labels. If `True`, the
            labels will be modified in-place to point to the embedded videos,
            which is faster but mutates the input. Only applies when embedding.
        verbose: If `True` (the default), display a progress bar when embedding frames.
        plugin: Image plugin to use for encoding embedded frames. One of "opencv"
            or "imageio". If None, uses the global default from
            `get_default_image_plugin()`. If no global default is set, auto-detects
            based on available packages (opencv preferred, then imageio).
        progress_callback: Optional callback function called during embedding with
            `(current, total, phase)` arguments, where ``phase`` is ``"embed"`` or
            ``"write"``. If it returns `False`, the operation is cancelled and
            `ExportCancelled` is raised. When provided, tqdm progress bars are
            disabled in favor of the callback. The ``phase`` argument is a breaking
            change from the previous ``(current, total)`` signature.
        prefer_metadata: If `True` (the default), serialize each uncropped video's
            shape/grayscale/fps from its `backend_metadata` when recorded there
            instead of querying the live backend. For an open `MediaVideo` this
            avoids decoding a frame (and leaving a resident decoder) just to recompute
            already-known metadata. Set to `False` to always read shape/grayscale/fps
            through the live backend.
        preserve_unknown: If `True`, top-level HDF5 datasets/groups in the source
            file that sleap-io does not recognize are carried over into the saved
            file. This preserves additions from a newer sleap-io version across a
            load/save cycle. Default `False`. Best-effort (requires the source file
            to still exist and be readable HDF5). See `write_labels`.
        save_embedding_vectors: If `False` (the default), skip the `/embeddings`
            group entirely -- appearance vectors are large on disk, so only the
            identity *links* are persisted by default (the vectors stay in memory,
            e.g. to build identity prototypes). This mirrors `embed`, which is also
            off by default for video frames. Set `True` to also write the
            `/embeddings` group. Identity links (`/identity/links`) are written
            regardless.
    """
    from sleap_io.io import slp

    return slp.write_labels(
        filename,
        labels,
        embed=embed,
        restore_original_videos=restore_original_videos,
        embed_inplace=embed_inplace,
        verbose=verbose,
        plugin=plugin,
        progress_callback=progress_callback,
        prefer_metadata=prefer_metadata,
        preserve_unknown=preserve_unknown,
        save_embedding_vectors=save_embedding_vectors,
    )

Lazy Loading for Large Files

When working with large SLP files (hundreds of thousands of frames), loading can be slow due to the creation of many Python objects. sleap-io provides a lazy loading mode that defers object creation until needed, significantly speeding up common workflows.

When to Use Lazy Loading

Lazy loading is recommended when:

  • You only need to convert data to NumPy arrays (labels.numpy())
  • You're saving to a different file without modifications
  • You're accessing a small subset of frames
  • You want fast load times for large files
Basic Usage
import sleap_io as sio

# Load lazily (up to 90x faster than eager loading!)
labels = sio.load_slp("predictions.slp", lazy=True)

# Check if labels is lazy
print(labels.is_lazy)  # True

# Fast path: convert directly to NumPy (no object creation)
poses = labels.numpy()

# Fast path: save without materialization
sio.save_slp(labels, "copy.slp")
Accessing Frames

Lazy-loaded Labels support standard read operations:

# These work normally (frames materialized on-demand)
print(len(labels))  # Number of frames
first_frame = labels[0]  # Access single frame
last_frame = labels[-1]  # Negative indexing
subset = labels[10:20]  # Slicing

# Iteration (materializes each frame)
for lf in labels:
    print(f"Frame {lf.frame_idx}: {len(lf)} instances")
Modifying Lazy Labels

Lazy Labels are read-only. To make modifications, first materialize:

# This raises RuntimeError
labels.append(new_frame)  # Error: Cannot append on lazy-loaded Labels

# Materialize first to enable modifications
labels = labels.materialize()  # Creates eager copy
labels.append(new_frame)  # Now works
Performance Comparison
Operation Eager Lazy Speedup
Load only 0.47s 0.005s ~90x
Load + numpy() 0.86s 0.38s ~2x
Full iteration 0.0002s 0.41s Eager faster

Benchmarks on 18,000 frames with ~40,000 instances.

Lazy loading excels at avoiding unnecessary work. If you need to iterate over all frames, eager loading is faster.

API Reference

Labels properties and methods for lazy loading:

  • Labels.is_lazy - True if lazy-loaded
  • Labels.materialize() - Convert to eager Labels (returns self if already eager)
  • Labels.numpy() - Uses fast path when lazy (no object creation)
  • Labels.to_dataframe() - Uses fast path when lazy (no object creation)

Fast statistics (O(1) for lazy-loaded Labels):

  • Labels.n_user_instances - Total number of user-labeled instances
  • Labels.n_pred_instances - Total number of predicted instances
  • Labels.n_frames_per_video() - Dictionary mapping videos to frame counts
  • Labels.n_instances_per_track() - Dictionary mapping tracks to instance counts

NWB Format (.nwb)

Neurodata Without Borders (NWB) is a standardized neurophysiology format with full read/write support for pose tracking. See the NWB Format page for harmonized I/O, the nwb_format types, the advanced annotations API, multi-subject export, and metadata handling.

JABS Format (.h5)

JABS (JAX Animal Behavior System) format for behavior classification.

sleap_io.io.main.load_jabs(filename, skeleton=None, **kwargs)

Read JABS-style predictions from a file and return a Labels object.

Parameters:

Name Type Description Default
filename str

Path to the jabs h5 pose file.

required
skeleton Skeleton | None

An optional Skeleton object.

None
**kwargs

Additional loader keyword arguments forwarded by load_file (e.g. open_videos, lazy). They are accepted but ignored; this format does not use them.

required

Returns:

Type Description
Labels

Parsed labels as a Labels instance.

Source code in sleap_io/io/main.py
def load_jabs(filename: str, skeleton: Skeleton | None = None, **kwargs) -> Labels:
    """Read JABS-style predictions from a file and return a `Labels` object.

    Args:
        filename: Path to the jabs h5 pose file.
        skeleton: An optional `Skeleton` object.
        **kwargs: Additional loader keyword arguments forwarded by `load_file`
            (e.g. ``open_videos``, ``lazy``). They are accepted but ignored; this
            format does not use them.

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

    return jabs.read_labels(filename, skeleton=skeleton)

sleap_io.io.main.save_jabs(labels, pose_version, root_folder=None)

Save a SLEAP dataset to JABS pose file format.

Parameters:

Name Type Description Default
labels Labels

SLEAP Labels object.

required
pose_version int

The JABS pose version to write data out.

required
root_folder str | None

Optional root folder where the files should be saved.

None
Note

Filenames for JABS poses are based on video filenames.

Source code in sleap_io/io/main.py
def save_jabs(labels: Labels, pose_version: int, root_folder: str | None = None):
    """Save a SLEAP dataset to JABS pose file format.

    Args:
        labels: SLEAP `Labels` object.
        pose_version: The JABS pose version to write data out.
        root_folder: Optional root folder where the files should be saved.

    Note:
        Filenames for JABS poses are based on video filenames.
    """
    from sleap_io.io import jabs

    jabs.write_labels(labels, pose_version, root_folder)

SLEAP Analysis HDF5 Format (.h5)

The SLEAP Analysis HDF5 format is a portable format for exporting pose tracking predictions as dense numpy arrays. This is the format produced by SLEAP's "Export Analysis HDF5" feature, designed for easy loading in MATLAB and Python analysis pipelines.

sleap_io.io.main.load_analysis_h5(filename, video=None, **kwargs)

Load SLEAP Analysis HDF5 file.

Parameters:

Name Type Description Default
filename str

Path to Analysis HDF5 file.

required
video Video | str | None

Video to associate with data. If None, uses video_path stored in the file. Can be a Video object or path string.

None
**kwargs

Additional loader keyword arguments forwarded by load_file (e.g. open_videos, lazy). They are accepted but ignored; this format does not use them.

required

Returns:

Type Description
Labels

Labels object with loaded pose data.

Notes

If the file contains extended metadata (skeleton symmetries, video backend metadata, etc.), it will be used to reconstruct the full Labels context.

See Also

save_analysis_h5: Save Labels to Analysis HDF5 file.

Source code in sleap_io/io/main.py
def load_analysis_h5(
    filename: str,
    video: "Video | str | None" = None,
    **kwargs,
) -> Labels:
    """Load SLEAP Analysis HDF5 file.

    Args:
        filename: Path to Analysis HDF5 file.
        video: Video to associate with data. If None, uses video_path stored
            in the file. Can be a Video object or path string.
        **kwargs: Additional loader keyword arguments forwarded by `load_file`
            (e.g. ``open_videos``, ``lazy``). They are accepted but ignored; this
            format does not use them.

    Returns:
        Labels object with loaded pose data.

    Notes:
        If the file contains extended metadata (skeleton symmetries, video
        backend metadata, etc.), it will be used to reconstruct the full
        Labels context.

    See Also:
        save_analysis_h5: Save Labels to Analysis HDF5 file.
    """
    from sleap_io.io import analysis_h5

    return analysis_h5.read_labels(filename, video=video)

sleap_io.io.main.save_analysis_h5(labels, filename, *, video=None, labels_path=None, all_frames=True, min_occupancy=0.0, preset=None, frame_dim=None, track_dim=None, node_dim=None, xy_dim=None, save_metadata=True)

Save Labels to SLEAP Analysis HDF5 file.

Parameters:

Name Type Description Default
labels Labels

Labels to export.

required
filename str

Output file path.

required
video Video | int | None

Video to export. If None, uses first video. Can be a Video object or an integer index.

None
labels_path str | None

Source labels path (stored as metadata).

None
all_frames bool

Include all frames from 0 to the end of the video (falling back to the last labeled frame when the video length is unknown). Default True.

True
min_occupancy float

Minimum track occupancy ratio (0-1) to keep. 0 = keep all non-empty tracks (SLEAP default). 0.5 = keep tracks with >50% occupancy.

0.0
preset str | None

Axis ordering preset. Options: - "matlab" (default): SLEAP-compatible ordering for MATLAB. tracks shape: (n_tracks, 2, n_nodes, n_frames) - "standard": Intuitive Python ordering. tracks shape: (n_frames, n_tracks, n_nodes, 2) Mutually exclusive with explicit dimension parameters.

None
frame_dim int | None

Position of the frame dimension (0-3).

None
track_dim int | None

Position of the track dimension (0-3).

None
node_dim int | None

Position of the node dimension (0-3).

None
xy_dim int | None

Position of the xy dimension (0-3).

None
save_metadata bool

Store extended metadata for full round-trip. Default True.

True
See Also

load_analysis_h5: Load Labels from Analysis HDF5 file.

Source code in sleap_io/io/main.py
def save_analysis_h5(
    labels: Labels,
    filename: str,
    *,
    video: "Video | int | None" = None,
    labels_path: str | None = None,
    all_frames: bool = True,
    min_occupancy: float = 0.0,
    preset: str | None = None,
    frame_dim: int | None = None,
    track_dim: int | None = None,
    node_dim: int | None = None,
    xy_dim: int | None = None,
    save_metadata: bool = True,
) -> None:
    """Save Labels to SLEAP Analysis HDF5 file.

    Args:
        labels: Labels to export.
        filename: Output file path.
        video: Video to export. If None, uses first video. Can be a Video
            object or an integer index.
        labels_path: Source labels path (stored as metadata).
        all_frames: Include all frames from 0 to the end of the video (falling back
            to the last labeled frame when the video length is unknown).
            Default True.
        min_occupancy: Minimum track occupancy ratio (0-1) to keep.
            0 = keep all non-empty tracks (SLEAP default).
            0.5 = keep tracks with >50% occupancy.
        preset: Axis ordering preset. Options:
            - "matlab" (default): SLEAP-compatible ordering for MATLAB.
              tracks shape: (n_tracks, 2, n_nodes, n_frames)
            - "standard": Intuitive Python ordering.
              tracks shape: (n_frames, n_tracks, n_nodes, 2)
            Mutually exclusive with explicit dimension parameters.
        frame_dim: Position of the frame dimension (0-3).
        track_dim: Position of the track dimension (0-3).
        node_dim: Position of the node dimension (0-3).
        xy_dim: Position of the xy dimension (0-3).
        save_metadata: Store extended metadata for full round-trip.
            Default True.

    See Also:
        load_analysis_h5: Load Labels from Analysis HDF5 file.
    """
    from sleap_io.io import analysis_h5

    analysis_h5.write_labels(
        labels,
        filename,
        video=video,
        labels_path=labels_path,
        all_frames=all_frames,
        min_occupancy=min_occupancy,
        preset=preset,
        frame_dim=frame_dim,
        track_dim=track_dim,
        node_dim=node_dim,
        xy_dim=xy_dim,
        save_metadata=save_metadata,
    )

Axis Ordering Presets

The format supports configurable axis ordering via presets:

Preset Description tracks shape
matlab (default) SLEAP-compatible, optimized for MATLAB (tracks, 2, nodes, frames)
standard Python-native, intuitive indexing (frames, tracks, nodes, 2)
import sleap_io as sio

labels = sio.load_slp("predictions.slp")

# Default (MATLAB-compatible) - matches SLEAP's export
sio.save_analysis_h5(labels, "output.h5")

# Python-native ordering for easier numpy indexing
sio.save_analysis_h5(labels, "output.h5", preset="standard")

# Filter tracks with <50% occupancy
sio.save_analysis_h5(labels, "output.h5", min_occupancy=0.5)

# Load back
loaded = sio.load_analysis_h5("output.h5")

Self-Documenting Format

Each dataset stores its dimension names in the dims HDF5 attribute, making files self-documenting:

import h5py

with h5py.File("output.h5", "r") as f:
    print(f["tracks"].attrs["dims"])  # e.g., '["track", "xy", "node", "frame"]'
    print(f.attrs["preset"])  # "matlab", "standard", or "custom"

Label Studio Format (.json)

Label Studio is a multi-modal annotation platform. Export annotations from Label Studio and load them into SLEAP.

sleap_io.io.main.load_labelstudio(filename, skeleton=None, **kwargs)

Read Label Studio-style annotations from a file and return a Labels object.

Parameters:

Name Type Description Default
filename str

Path to the label-studio annotation file in JSON format.

required
skeleton Skeleton | list[str] | None

An optional Skeleton object or list of node names. If not provided (the default), skeleton will be inferred from the data. It may be useful to provide this so the keypoint label types can be filtered to just the ones in the skeleton.

None
**kwargs

Additional loader keyword arguments forwarded by load_file (e.g. open_videos, lazy). They are accepted but ignored; this format does not use them.

required

Returns:

Type Description
Labels

Parsed labels as a Labels instance.

Source code in sleap_io/io/main.py
def load_labelstudio(
    filename: str, skeleton: Skeleton | list[str] | None = None, **kwargs
) -> Labels:
    """Read Label Studio-style annotations from a file and return a `Labels` object.

    Args:
        filename: Path to the label-studio annotation file in JSON format.
        skeleton: An optional `Skeleton` object or list of node names. If not provided
            (the default), skeleton will be inferred from the data. It may be useful to
            provide this so the keypoint label types can be filtered to just the ones in
            the skeleton.
        **kwargs: Additional loader keyword arguments forwarded by `load_file`
            (e.g. ``open_videos``, ``lazy``). They are accepted but ignored; this
            format does not use them.

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

    return labelstudio.read_labels(filename, skeleton=skeleton)

sleap_io.io.main.save_labelstudio(labels, filename)

Save a SLEAP dataset to Label Studio format.

Parameters:

Name Type Description Default
labels Labels

A SLEAP Labels object (see load_slp).

required
filename str

Path to save labels to ending with .json.

required
Source code in sleap_io/io/main.py
def save_labelstudio(labels: Labels, filename: str):
    """Save a SLEAP dataset to Label Studio format.

    Args:
        labels: A SLEAP `Labels` object (see `load_slp`).
        filename: Path to save labels to ending with `.json`.
    """
    from sleap_io.io import labelstudio

    labelstudio.write_labels(labels, filename)

DeepLabCut Format (.csv)

Load annotations from DeepLabCut. See the DeepLabCut Format page for single-CSV loading with config.yaml discovery (skeleton edges + source-video links), whole-project imports with load_dlc_project, and train/test split recovery with load_dlc_splits.

CSV Format (.csv)

sleap-io provides comprehensive CSV support for reading and writing pose tracking data, enabling interoperability with spreadsheet tools, custom pipelines, and other pose estimation frameworks.

Supported CSV Formats

Format Description Use Case
sleap SLEAP Analysis CSV (default) Native SLEAP exports, one row per instance
dlc DeepLabCut format DLC compatibility, multi-header structure
points One row per point Most normalized, database-friendly
instances One row per instance Compact, analysis-friendly
frames One row per frame Wide format, all instances in columns

Basic Usage

import sleap_io as sio

# Load CSV (auto-detects format)
labels = sio.load_csv("predictions.csv")

# Save in SLEAP Analysis format (default)
sio.save_csv(labels, "output.csv")

# Save in DLC format
sio.save_csv(labels, "dlc_output.csv", format="dlc", scorer="MyModel")

# Save with metadata for full round-trip support
sio.save_csv(labels, "output.csv", save_metadata=True)
# Creates: output.csv + output.json (metadata)

Round-Trip with Metadata

CSV files cannot store all Labels information (skeleton edges, symmetries, suggestions). To enable full round-trip reconstruction, use save_metadata=True:

# Save with metadata sidecar file
sio.save_csv(labels, "data.csv", save_metadata=True)
# Creates: data.csv and data.json

# Load back with full metadata
labels = sio.load_csv("data.csv")
# Automatically loads data.json if present

The metadata JSON file contains:

  • Video paths and backend metadata
  • Skeleton definitions (nodes, edges, symmetries)
  • Track names
  • Suggested frames
  • Provenance information

Format-Specific Examples

SLEAP Analysis Format

The default format matches SLEAP's "Export Analysis CSV" output:

# Write SLEAP format
sio.save_csv(labels, "analysis.csv", format="sleap")

Output columns: track, frame_idx, instance.score, {node}.x, {node}.y, {node}.score, ...

DeepLabCut Format

For compatibility with DeepLabCut workflows:

# Write DLC format with custom scorer name
sio.save_csv(labels, "dlc_output.csv", format="dlc", scorer="MyNetwork")

# Multi-animal DLC format (auto-detected from tracks)
sio.save_csv(multi_animal_labels, "multi_dlc.csv", format="dlc")

DLC format uses multi-row headers (scorer, bodyparts, coords) and is compatible with DLC's analysis tools.

DataFrame Codec Formats

For custom analysis pipelines, use the normalized formats from the DataFrame codec:

# Points format: most normalized (one row per point)
sio.save_csv(labels, "points.csv", format="points")

# Instances format: one row per instance
sio.save_csv(labels, "instances.csv", format="instances")

# Frames format: one row per frame (wide format)
sio.save_csv(labels, "frames.csv", format="frames")

sleap_io.io.main.load_csv(filename, format='auto', video=None, skeleton=None, **kwargs)

Load pose data from a CSV file.

Parameters:

Name Type Description Default
filename str

Path to CSV file.

required
format str

CSV format. One of "auto", "sleap", "dlc", "points", "instances", "frames". Default "auto" detects format from file content.

'auto'
video Video | str | None

Video to associate with data. Can be Video object or path string.

None
skeleton Skeleton | None

Skeleton to use. If None, inferred from columns or metadata.

None
**kwargs

Additional loader keyword arguments forwarded by load_file (e.g. open_videos, lazy). They are accepted but ignored; this format does not use them.

required

Returns:

Type Description
Labels

Labels object.

Notes

If a metadata JSON file exists alongside the CSV (same base name with .json extension), it will be automatically loaded to restore full Labels context including skeleton edges, symmetries, and provenance.

See Also

save_csv: Save Labels to CSV file.

Source code in sleap_io/io/main.py
def load_csv(
    filename: str,
    format: str = "auto",
    video: "Video | str | None" = None,
    skeleton: "Skeleton | None" = None,
    **kwargs,
) -> "Labels":
    """Load pose data from a CSV file.

    Args:
        filename: Path to CSV file.
        format: CSV format. One of "auto", "sleap", "dlc", "points", "instances",
            "frames". Default "auto" detects format from file content.
        video: Video to associate with data. Can be Video object or path string.
        skeleton: Skeleton to use. If None, inferred from columns or metadata.
        **kwargs: Additional loader keyword arguments forwarded by `load_file`
            (e.g. ``open_videos``, ``lazy``). They are accepted but ignored; this
            format does not use them.

    Returns:
        Labels object.

    Notes:
        If a metadata JSON file exists alongside the CSV (same base name with
        .json extension), it will be automatically loaded to restore full
        Labels context including skeleton edges, symmetries, and provenance.

    See Also:
        save_csv: Save Labels to CSV file.
    """
    from sleap_io.io import csv

    return csv.read_labels(filename, format=format, video=video, skeleton=skeleton)

sleap_io.io.main.save_csv(labels, filename, format='sleap', video=None, include_score=True, include_empty=False, start_frame=None, end_frame=None, scorer='sleap-io', save_metadata=False, chunk_size=None, video_id='path')

Save pose data to a CSV file.

Parameters:

Name Type Description Default
labels Labels

Labels to save.

required
filename str

Output path.

required
format str

CSV format. One of "sleap" (default), "dlc", "points", "instances", "frames".

'sleap'
video Video | int | None

Video to filter to. Can be Video object or integer index. If None, includes all videos.

None
include_score bool

Include confidence scores in output. Default True.

True
include_empty bool

Include frames with no instances (filled with NaN values). Default False. Only applies to "frames" and "instances" formats.

False
start_frame int | None

Start frame index (inclusive) for output. If None, starts from 0 when include_empty=True, or from first labeled frame otherwise.

None
end_frame int | None

End frame index (exclusive) for output. If None, ends at the full video length when known, otherwise at last labeled frame + 1.

None
scorer str

Scorer name for DLC format. Default "sleap-io".

'sleap-io'
save_metadata bool

Save JSON metadata file alongside CSV that enables full round-trip reconstruction. Default False.

False
chunk_size int | None

Number of rows per chunk for memory-efficient writing. If None (default), writes entire DataFrame at once. Useful for large datasets. Not supported for DLC format.

None
video_id str

How to represent videos in the CSV. Options: "path" (default), "index", or "name".

'path'
See Also

load_csv: Load Labels from CSV file.

Source code in sleap_io/io/main.py
def save_csv(
    labels: "Labels",
    filename: str,
    format: str = "sleap",
    video: "Video | int | None" = None,
    include_score: bool = True,
    include_empty: bool = False,
    start_frame: int | None = None,
    end_frame: int | None = None,
    scorer: str = "sleap-io",
    save_metadata: bool = False,
    chunk_size: int | None = None,
    video_id: str = "path",
) -> None:
    """Save pose data to a CSV file.

    Args:
        labels: Labels to save.
        filename: Output path.
        format: CSV format. One of "sleap" (default), "dlc", "points",
            "instances", "frames".
        video: Video to filter to. Can be Video object or integer index.
            If None, includes all videos.
        include_score: Include confidence scores in output. Default True.
        include_empty: Include frames with no instances (filled with NaN values).
            Default False. Only applies to "frames" and "instances" formats.
        start_frame: Start frame index (inclusive) for output. If None, starts
            from 0 when include_empty=True, or from first labeled frame otherwise.
        end_frame: End frame index (exclusive) for output. If None, ends at the
            full video length when known, otherwise at last labeled frame + 1.
        scorer: Scorer name for DLC format. Default "sleap-io".
        save_metadata: Save JSON metadata file alongside CSV that enables
            full round-trip reconstruction. Default False.
        chunk_size: Number of rows per chunk for memory-efficient writing. If None
            (default), writes entire DataFrame at once. Useful for large datasets.
            Not supported for DLC format.
        video_id: How to represent videos in the CSV. Options: "path" (default),
            "index", or "name".

    See Also:
        load_csv: Load Labels from CSV file.
    """
    from sleap_io.io import csv

    csv.write_labels(
        labels,
        filename,
        format=format,
        video=video,
        include_score=include_score,
        include_empty=include_empty,
        start_frame=start_frame,
        end_frame=end_frame,
        scorer=scorer,
        save_metadata=save_metadata,
        chunk_size=chunk_size,
        video_id=video_id,
    )

AlphaTracker Format

Load predictions from AlphaTracker, a tracking system for socially-housed animals.

sleap_io.io.main.load_alphatracker(filename, **kwargs)

Read AlphaTracker annotations from a file and return a Labels object.

Parameters:

Name Type Description Default
filename str

Path to the AlphaTracker annotation file in JSON format.

required
**kwargs

Additional loader keyword arguments forwarded by load_file (e.g. open_videos, lazy). They are accepted but ignored; this format does not use them.

required

Returns:

Type Description
Labels

Parsed labels as a Labels instance.

Source code in sleap_io/io/main.py
def load_alphatracker(filename: str, **kwargs) -> Labels:
    """Read AlphaTracker annotations from a file and return a `Labels` object.

    Args:
        filename: Path to the AlphaTracker annotation file in JSON format.
        **kwargs: Additional loader keyword arguments forwarded by `load_file`
            (e.g. ``open_videos``, ``lazy``). They are accepted but ignored; this
            format does not use them.

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

    return alphatracker.read_labels(filename)

LEAP Format (.mat)

Load predictions from LEAP, a SLEAP predecessor. Requires scipy for .mat file support.

sleap_io.io.main.load_leap(filename, skeleton=None, **kwargs)

Load a LEAP dataset from a .mat file.

Parameters:

Name Type Description Default
filename str

Path to a LEAP .mat file.

required
skeleton Skeleton | None

An optional Skeleton object. If not provided, will be constructed from the data in the file.

None
**kwargs

Additional arguments (currently unused).

required

Returns:

Type Description
Labels

The dataset as a Labels object.

Source code in sleap_io/io/main.py
def load_leap(
    filename: str,
    skeleton: Skeleton | None = None,
    **kwargs,
) -> Labels:
    """Load a LEAP dataset from a .mat file.

    Args:
        filename: Path to a LEAP .mat file.
        skeleton: An optional `Skeleton` object. If not provided, will be constructed
            from the data in the file.
        **kwargs: Additional arguments (currently unused).

    Returns:
        The dataset as a `Labels` object.
    """
    from sleap_io.io import leap

    return leap.read_labels(filename, skeleton=skeleton)

COCO Format (.json)

COCO (Common Objects in Context) is widely used in computer vision and pose estimation. See the COCO Format page for full read/write support (compatible with mmpose, CVAT, and other COCO-based tools) and empty-frame handling.

TIFF Label Images (.tif, .tiff)

TIFF files store dense integer label images for instance segmentation, where each pixel value encodes which object occupies that location. This is the standard output of tools like Cellpose and StarDist.

sleap-io supports three TIFF layouts: single files, multi-page stacks, and directories of per-frame TIFFs. A JSON sidecar (.meta.json) is written alongside the TIFF to preserve track names and categories.

Detailed Format Documentation

For comprehensive documentation of TIFF label image I/O including file structures and sidecar metadata, see the TIFF Format Reference.

sleap_io.io.main.load_label_images(path, video=None, tracks=None, categories=None, pages_as='auto')

Load label images from TIFF file(s) or directory.

Parameters:

Name Type Description Default
path str | Path

Path to a TIFF file (single or multi-page stack) or a directory of per-frame TIFFs.

required
video Video | None

Video to associate with all frames.

None
tracks dict | None

Global {label_id: Track} mapping. If None, auto-creates one Track per unique ID found across all frames. Ignored for class-stacked layouts.

None
categories list[str] | dict[int, str] | None

Category strings.

  • dict[int, str] keyed by label ID (time mode).
  • list[str] positional, one per class (class mode).
  • None to read from sidecar if present.
None
pages_as str

How to interpret multi-page TIFFs.

  • "auto" (default): consult sidecar "axes", then TIFF metadata (OME-XML / ImageJ hyperstack). Falls back to "time" for plain multi-page files with a one-time warning.
  • "time": force each page to be one frame.
  • "classes": force pages to be per-class binary masks for a single frame (N pages -> 1 LabelImage with label IDs 1..N).
'auto'

Returns:

Type Description
list[LabelImage]

List of LabelImage, one per frame, sorted by frame index.

Source code in sleap_io/io/main.py
def load_label_images(
    path: str | Path,
    video: Video | None = None,
    tracks: dict | None = None,
    categories: list[str] | dict[int, str] | None = None,
    pages_as: str = "auto",
) -> list[LabelImage]:
    """Load label images from TIFF file(s) or directory.

    Args:
        path: Path to a TIFF file (single or multi-page stack) or a directory
            of per-frame TIFFs.
        video: Video to associate with all frames.
        tracks: Global ``{label_id: Track}`` mapping. If ``None``, auto-creates
            one Track per unique ID found across all frames. Ignored for
            class-stacked layouts.
        categories: Category strings.

            - ``dict[int, str]`` keyed by label ID (time mode).
            - ``list[str]`` positional, one per class (class mode).
            - ``None`` to read from sidecar if present.

        pages_as: How to interpret multi-page TIFFs.

            - ``"auto"`` (default): consult sidecar ``"axes"``, then TIFF
              metadata (OME-XML / ImageJ hyperstack). Falls back to
              ``"time"`` for plain multi-page files with a one-time warning.
            - ``"time"``: force each page to be one frame.
            - ``"classes"``: force pages to be per-class binary masks for a
              single frame (N pages -> 1 ``LabelImage`` with label IDs 1..N).

    Returns:
        List of ``LabelImage``, one per frame, sorted by frame index.
    """
    from sleap_io.io import tiff

    return tiff.read_label_images(
        path,
        video=video,
        tracks=tracks,
        categories=categories,
        pages_as=pages_as,
    )

sleap_io.io.main.save_label_images(path, label_images, stack=True)

Save label images to TIFF.

Parameters:

Name Type Description Default
path str | Path

Output path. If stack=True, writes a single multi-page TIFF. If stack=False, writes per-frame files to this directory.

required
label_images list[LabelImage]

LabelImage objects to write.

required
stack bool

Write as multi-page TIFF stack (True) or per-frame files in a directory (False).

True
Source code in sleap_io/io/main.py
def save_label_images(
    path: str | Path,
    label_images: list[LabelImage],
    stack: bool = True,
) -> None:
    """Save label images to TIFF.

    Args:
        path: Output path. If ``stack=True``, writes a single multi-page TIFF.
            If ``stack=False``, writes per-frame files to this directory.
        label_images: ``LabelImage`` objects to write.
        stack: Write as multi-page TIFF stack (``True``) or per-frame files in
            a directory (``False``).
    """
    from sleap_io.io import tiff

    tiff.write_label_images(path, label_images, stack=stack)

COCO Panoptic Segmentation

COCO panoptic format represents per-pixel segmentation using a JSON annotation file and per-frame PNG label images. Each pixel is encoded as R + G * 256 + B * 256^2. "Thing" segments (countable objects) get track identities; "stuff" segments (uncountable regions) do not.

from sleap_io.io.coco import read_coco_panoptic, write_coco_panoptic

# Read panoptic annotations
labels = read_coco_panoptic("panoptic.json", images_dir="panoptic_pngs/")

# Write panoptic annotations
write_coco_panoptic("output.json", labels, images_dir="output_pngs/")

sleap_io.io.coco.read_coco_panoptic(json_path, images_dir=None)

Read COCO panoptic segmentation format.

Reads the panoptic annotation JSON and per-frame PNG label images. Each segment_info entry becomes a LabelImage.Info with: - category from the COCO categories table - track from the segment id (isthing=True) or None (isthing=False)

Parameters:

Name Type Description Default
json_path str | Path

Path to the panoptic annotation JSON.

required
images_dir str | Path | None

Directory containing the panoptic PNG files. If None, inferred from the JSON path (same directory).

None

Returns:

Type Description
Labels

Labels object with label_images populated.

Source code in sleap_io/io/coco.py
def read_coco_panoptic(
    json_path: str | Path,
    images_dir: str | Path | None = None,
) -> Labels:
    """Read COCO panoptic segmentation format.

    Reads the panoptic annotation JSON and per-frame PNG label images.
    Each segment_info entry becomes a LabelImage.Info with:
    - category from the COCO categories table
    - track from the segment id (isthing=True) or None (isthing=False)

    Args:
        json_path: Path to the panoptic annotation JSON.
        images_dir: Directory containing the panoptic PNG files. If None,
            inferred from the JSON path (same directory).

    Returns:
        Labels object with label_images populated.
    """
    from PIL import Image

    from sleap_io.model.label_image import LabelImage, UserLabelImage

    json_path = Path(json_path)
    if images_dir is None:
        images_dir = json_path.parent
    else:
        images_dir = Path(images_dir)

    with open(json_path, "r") as f:
        data = json.load(f)

    # Build category lookup
    categories = {cat["id"]: cat for cat in data.get("categories", [])}

    # Track pool: shared across frames for thing segments
    track_pool: dict[int, Track] = {}

    # Build image_id -> file_name mapping
    image_filenames = []
    image_id_to_idx = {}
    for img in data.get("images", []):
        image_id_to_idx[img["id"]] = len(image_filenames)
        image_filenames.append(img.get("file_name", ""))

    # Create a single video from the image filenames
    video = Video(filename=image_filenames) if image_filenames else Video(filename="")

    labeled_frames = []
    frame_idx = 0
    for ann in data.get("annotations", []):
        png_filename = ann["file_name"]
        segments_info = ann.get("segments_info", [])

        # Read the panoptic PNG and decode to integer label image
        png_path = images_dir / png_filename
        if not png_path.exists():
            continue

        pil_img = Image.open(png_path).convert("RGB")
        rgb = np.array(pil_img, dtype=np.int32)
        # COCO panoptic encoding: pixel_value = R + G * 256 + B * 256^2
        label_data = rgb[:, :, 0] + rgb[:, :, 1] * 256 + rgb[:, :, 2] * 65536

        # Build objects dict from segments_info
        objects: dict[int, LabelImage.Info] = {}
        for seg in segments_info:
            seg_id = seg["id"]
            cat_id = seg["category_id"]
            cat = categories.get(cat_id, {})
            cat_name = cat.get("name", "")
            is_thing = bool(cat.get("isthing", 0))

            track = None
            if is_thing:
                if seg_id not in track_pool:
                    track_pool[seg_id] = Track(name=str(seg_id))
                track = track_pool[seg_id]

            objects[seg_id] = LabelImage.Info(
                track=track,
                category=cat_name,
            )

        li = UserLabelImage(
            data=label_data,
            objects=objects,
        )
        lf = LabeledFrame(video=video, frame_idx=frame_idx)
        lf.label_images.append(li)
        labeled_frames.append(lf)
        frame_idx += 1

    return Labels(labeled_frames=labeled_frames)

sleap_io.io.coco.write_coco_panoptic(path, labels, images_dir=None)

Write COCO panoptic segmentation format.

Writes a panoptic JSON and per-frame PNG label images.

Parameters:

Name Type Description Default
path str | Path

Path to save the panoptic annotation JSON.

required
labels Labels

Labels object with label_images populated.

required
images_dir str | Path | None

Directory to write panoptic PNG files. If None, creates a subdirectory next to the JSON named <json_stem>_panoptic.

None
Source code in sleap_io/io/coco.py
def write_coco_panoptic(
    path: str | Path,
    labels: Labels,
    images_dir: str | Path | None = None,
) -> None:
    """Write COCO panoptic segmentation format.

    Writes a panoptic JSON and per-frame PNG label images.

    Args:
        path: Path to save the panoptic annotation JSON.
        labels: Labels object with label_images populated.
        images_dir: Directory to write panoptic PNG files. If None,
            creates a subdirectory next to the JSON named
            ``<json_stem>_panoptic``.
    """
    from PIL import Image

    path = Path(path)
    path.parent.mkdir(parents=True, exist_ok=True)

    if images_dir is None:
        images_dir = path.parent / f"{path.stem}_panoptic"
    else:
        images_dir = Path(images_dir)
    images_dir.mkdir(parents=True, exist_ok=True)

    # Collect all unique categories across all label images
    category_name_to_id: dict[str, int] = {}
    # Track which categories are "thing" (have tracks) vs "stuff" (no track)
    category_is_thing: dict[str, bool] = {}
    cat_id_counter = 1

    for li in labels.label_images:
        for info in li.objects.values():
            cat_name = info.category if info.category else "unknown"
            if cat_name not in category_name_to_id:
                category_name_to_id[cat_name] = cat_id_counter
                # A category is "thing" if any object with it has a track
                category_is_thing[cat_name] = info.track is not None
                cat_id_counter += 1
            else:
                # If any object with this category has a track, it's a thing
                if info.track is not None:
                    category_is_thing[cat_name] = True

    # Build categories list
    coco_categories = []
    for cat_name, cat_id in category_name_to_id.items():
        coco_categories.append(
            {
                "id": cat_id,
                "name": cat_name,
                "isthing": 1 if category_is_thing.get(cat_name, False) else 0,
            }
        )

    # Build images and annotations
    coco_images = []
    coco_annotations = []

    for idx, li in enumerate(labels.label_images):
        image_id = idx + 1
        png_filename = f"panoptic_{image_id:06d}.png"

        # Image entry
        coco_images.append(
            {
                "id": image_id,
                "file_name": f"image_{image_id:06d}.jpg",
                "width": li.width,
                "height": li.height,
            }
        )

        # Encode label data as RGB PNG
        # R = id % 256, G = (id // 256) % 256, B = (id // 65536) % 256
        rgb = np.zeros((li.height, li.width, 3), dtype=np.uint8)
        rgb[:, :, 0] = (li.data % 256).astype(np.uint8)
        rgb[:, :, 1] = ((li.data // 256) % 256).astype(np.uint8)
        rgb[:, :, 2] = ((li.data // 65536) % 256).astype(np.uint8)

        pil_img = Image.fromarray(rgb)
        pil_img.save(images_dir / png_filename)

        # Build segments_info
        segments_info = []
        for seg_id, info in li.objects.items():
            cat_name = info.category if info.category else "unknown"
            cat_id = category_name_to_id[cat_name]
            seg_mask = li.data == seg_id
            area = int(np.sum(seg_mask))

            # Compute bounding box [x, y, width, height] per COCO spec
            ys, xs = np.where(seg_mask)
            if len(xs) > 0:
                bbox = [
                    int(xs.min()),
                    int(ys.min()),
                    int(xs.max() - xs.min()) + 1,
                    int(ys.max() - ys.min()) + 1,
                ]
            else:
                bbox = [0, 0, 0, 0]

            segments_info.append(
                {
                    "id": seg_id,
                    "category_id": cat_id,
                    "area": area,
                    "bbox": bbox,
                    "iscrowd": 0,
                }
            )

        coco_annotations.append(
            {
                "image_id": image_id,
                "file_name": png_filename,
                "segments_info": segments_info,
            }
        )

    coco_data = {
        "images": coco_images,
        "annotations": coco_annotations,
        "categories": coco_categories,
    }

    with open(path, "w") as f:
        json.dump(coco_data, f, indent=2)

Ultralytics YOLO Format

Read and write the Ultralytics YOLO pose format. See the Ultralytics YOLO Format page for details.

GeoJSON Format (.geojson)

GeoJSON (RFC 7946) stores ROIs as a human-readable, standalone format that interoperates with the movement library and the geospatial Python ecosystem (Shapely, GeoPandas, QGIS). See the GeoJSON Format page for the schema and examples.

Working with Multiple Datasets

Load Multiple Files

Load and combine multiple pose tracking files:

sleap_io.io.main.load_labels_set(path, format=None, open_videos=True, **kwargs)

Load a LabelsSet from multiple files.

Parameters:

Name Type Description Default
path str | Path | list[str | Path] | dict[str, str | Path]

Can be one of: - A directory path containing label files - A list of file paths - A dictionary mapping names to file paths

required
format str | None

Optional format specification. If None, will try to infer from path. Supported formats: "slp", "ultralytics"

None
open_videos bool

If True (the default), attempt to open video backends.

True
**kwargs

Additional format-specific arguments.

required

Returns:

Type Description
LabelsSet

A LabelsSet containing the loaded Labels objects.

Examples:

Load from SLP directory:

>>> labels_set = load_labels_set("path/to/splits/")

Load from list of SLP files:

>>> labels_set = load_labels_set(["train.slp", "val.slp"])

Load from Ultralytics dataset:

>>> labels_set = load_labels_set("path/to/yolo_dataset/", format="ultralytics")
Source code in sleap_io/io/main.py
def load_labels_set(
    path: str | Path | list[str | Path] | dict[str, str | Path],
    format: str | None = None,
    open_videos: bool = True,
    **kwargs,
) -> "LabelsSet":
    """Load a LabelsSet from multiple files.

    Args:
        path: Can be one of:
            - A directory path containing label files
            - A list of file paths
            - A dictionary mapping names to file paths
        format: Optional format specification. If None, will try to infer from path.
            Supported formats: "slp", "ultralytics"
        open_videos: If `True` (the default), attempt to open video backends.
        **kwargs: Additional format-specific arguments.

    Returns:
        A LabelsSet containing the loaded Labels objects.

    Examples:
        Load from SLP directory:
        >>> labels_set = load_labels_set("path/to/splits/")

        Load from list of SLP files:
        >>> labels_set = load_labels_set(["train.slp", "val.slp"])

        Load from Ultralytics dataset:
        >>> labels_set = load_labels_set("path/to/yolo_dataset/", format="ultralytics")
    """
    # Try to infer format if not specified
    if format is None:
        if isinstance(path, (str, Path)):
            path_obj = Path(path)
            if path_obj.is_dir():
                # Check for ultralytics structure
                if (path_obj / "data.yaml").exists() or any(
                    (path_obj / split).exists() for split in ["train", "val", "test"]
                ):
                    format = "ultralytics"
                else:
                    # Default to SLP for directories
                    format = "slp"
            else:
                # Single file path - check extension
                if path_obj.suffix == ".slp":
                    format = "slp"
        elif isinstance(path, list) and len(path) > 0:
            # Check first file in list
            first_path = Path(path[0])
            if first_path.suffix == ".slp":
                format = "slp"
        elif isinstance(path, dict):
            # Dictionary input defaults to SLP
            format = "slp"

    if format == "slp":
        from sleap_io.io import slp

        return slp.read_labels_set(path, open_videos=open_videos)
    elif format == "ultralytics":
        # Extract ultralytics-specific kwargs
        splits = kwargs.pop("splits", None)
        skeleton = kwargs.pop("skeleton", None)
        image_size = kwargs.pop("image_size", (480, 640))
        # Remove verbose from kwargs if present (for backward compatibility)
        kwargs.pop("verbose", None)

        if not isinstance(path, (str, Path)):
            raise ValueError(
                "Ultralytics format requires a directory path, "
                f"got {type(path).__name__}"
            )

        from sleap_io.io import ultralytics

        return ultralytics.read_labels_set(
            str(path),
            splits=splits,
            skeleton=skeleton,
            image_size=image_size,
        )
    else:
        raise ValueError(
            f"Unknown format: {format}. Supported formats: 'slp', 'ultralytics'"
        )

Skeleton Files

Load and save skeleton definitions separately:

sleap_io.io.main.load_skeleton(filename)

Load skeleton(s) from a JSON, YAML, or SLP file.

Parameters:

Name Type Description Default
filename str | Path

Path to a skeleton file. Supported formats: - JSON: Standalone skeleton or training config with embedded skeletons - YAML: Simplified skeleton format - SLP: SLEAP project file

required

Returns:

Type Description
Skeleton | list[Skeleton]

A single Skeleton or list of Skeleton objects.

Notes

This function loads skeletons from various file types: - JSON files: Can be standalone skeleton files (jsonpickle format) or training config files with embedded skeletons - YAML files: Use a simplified human-readable format - SLP files: Extracts skeletons from SLEAP project files The format is detected based on the file extension and content.

Source code in sleap_io/io/main.py
def load_skeleton(filename: str | Path) -> Skeleton | list[Skeleton]:
    """Load skeleton(s) from a JSON, YAML, or SLP file.

    Args:
        filename: Path to a skeleton file. Supported formats:
            - JSON: Standalone skeleton or training config with embedded skeletons
            - YAML: Simplified skeleton format
            - SLP: SLEAP project file

    Returns:
        A single `Skeleton` or list of `Skeleton` objects.

    Notes:
        This function loads skeletons from various file types:
        - JSON files: Can be standalone skeleton files (jsonpickle format) or training
          config files with embedded skeletons
        - YAML files: Use a simplified human-readable format
        - SLP files: Extracts skeletons from SLEAP project files
        The format is detected based on the file extension and content.
    """
    if isinstance(filename, Path):
        filename = str(filename)

    # Detect format based on extension
    if filename.lower().endswith(".slp"):
        # SLP format - extract skeletons from SLEAP file
        from sleap_io.io.slp import read_skeletons

        return read_skeletons(filename)
    elif filename.lower().endswith((".yaml", ".yml")):
        # YAML format
        with open(filename, "r") as f:
            yaml_data = f.read()
        return decode_yaml_skeleton(yaml_data)
    else:
        # JSON format (default) - could be standalone or training config
        with open(filename, "r") as f:
            json_data = f.read()
        return load_skeleton_from_json(json_data)

sleap_io.io.main.save_skeleton(skeleton, filename)

Save skeleton(s) to a JSON or YAML file.

Parameters:

Name Type Description Default
skeleton Skeleton | list[Skeleton]

A single Skeleton or list of Skeleton objects to save.

required
filename str | Path

Path to save the skeleton file.

required
Notes

This function saves skeletons in either JSON or YAML format based on the file extension. JSON files use the jsonpickle format compatible with SLEAP, while YAML files use a simplified human-readable format.

Source code in sleap_io/io/main.py
def save_skeleton(skeleton: Skeleton | list[Skeleton], filename: str | Path):
    """Save skeleton(s) to a JSON or YAML file.

    Args:
        skeleton: A single `Skeleton` or list of `Skeleton` objects to save.
        filename: Path to save the skeleton file.

    Notes:
        This function saves skeletons in either JSON or YAML format based on the
        file extension. JSON files use the jsonpickle format compatible with SLEAP,
        while YAML files use a simplified human-readable format.
    """
    if isinstance(filename, Path):
        filename = str(filename)

    # Detect format based on extension
    if filename.lower().endswith((".yaml", ".yml")):
        # YAML format
        yaml_data = encode_yaml_skeleton(skeleton)
        with open(filename, "w") as f:
            f.write(yaml_data)
    else:
        # JSON format (default)
        json_data = encode_skeleton(skeleton)
        with open(filename, "w") as f:
            f.write(json_data)

Format Detection

sleap-io automatically detects file formats based on:

  1. File extension: .slp, .nwb, .h5, .json, .geojson, .mat, .csv, .tif, .tiff
  2. File content: For ambiguous extensions like .h5 (JABS vs Analysis HDF5) or .json (Label Studio vs COCO)
  3. Explicit format: Pass format parameter to override auto-detection

For URLs, ambiguous extensions (.h5, .json, .csv) are disambiguated with a magic-byte sniff via a Range request, controllable with load_file's sniff= argument. See Loading from URLs.

Format Conversion Examples

Convert Between Formats

import sleap_io as sio

# Load from any supported format
labels = sio.load_file("data.slp")

# Save to different formats
labels.save("data.nwb")  # NWB format
labels.save("data.labelstudio.json")  # Label Studio
labels.save("data_yolo/")  # Ultralytics YOLO

Batch Conversion

import sleap_io as sio
from pathlib import Path

# Convert all SLEAP files to NWB
for slp_file in Path("data/").glob("*.slp"):
    labels = sio.load_file(slp_file)
    nwb_file = slp_file.with_suffix(".nwb")
    labels.save(nwb_file)

Round-Trip Preservation

Most formats preserve data during round-trip conversion:

import sleap_io as sio

# Load original
labels_original = sio.load_file("data.slp")

# Save and reload
labels_original.save("temp.nwb")
labels_reloaded = sio.load_file("temp.nwb")

# Data is preserved
assert len(labels_original) == len(labels_reloaded)
assert labels_original.skeleton == labels_reloaded.skeleton

Format Limitations

Different formats have varying capabilities:

Format Read Write Videos Skeletons Tracks Confidence User/Predicted BBoxes ROIs/Masks Label Images
SLEAP (.slp)
NWB (.nwb) ✅*
JABS (.h5)
Analysis HDF5
Label Studio
CSV (.csv) ✅**
DeepLabCut
TrackMate (.csv) ✅*******
AlphaTracker
LEAP (.mat)
COCO (.json) ✅***
COCO Panoptic ✅****
TIFF (.tif) ✅*****
Ultralytics ✅******
GeoJSON (.geojson)

*NWB can embed videos with annotations_export format **CSV skeleton edges/symmetries preserved via optional metadata JSON sidecar ***COCO tracks are stored via attributes.object_id (CVAT-compatible) ****COCO panoptic tracks for "thing" segments only *****TIFF tracks via .meta.json sidecar ******Ultralytics segmentation polygons stored as ROIs *******TrackMate auto-detects sibling .tif/.tiff video files

Remote URL loading

Loading from a URL is currently supported only for SLEAP .slp/.pkg.slp (labels) and http/https media video; all other labels formats raise NotImplementedError over a URL — download the file locally first. See Loading from URLs.

See Also

  • Data Model: Understanding the core data structures
  • Examples: More usage examples and recipes
  • Merging: Combining data from multiple sources