Skip to content

deeplabcut.utils

Modules:

Name Description
auxfun_models

DeepLabCut2.0 Toolbox (deeplabcut.org)

auxfun_multianimal

DeepLabCut2.0 Toolbox (deeplabcut.org)

auxfun_videos

DeepLabCut2.0 Toolbox (deeplabcut.org)

auxiliaryfunctions

DeepLabCut2.0 Toolbox (deeplabcut.org)

auxiliaryfunctions_3d

DeepLabCut2.0 Toolbox (deeplabcut.org)

conversioncode
core_config
crossvalutils
frameselectiontools

DeepLabCut2.0 Toolbox (deeplabcut.org)

make_labeled_video

DeepLabCut2.0 Toolbox (deeplabcut.org)

multiprocessing

DeepLabCut2.2 Toolbox (deeplabcut.org)

pandas_future_mode

Opt-in pandas 2.3 future-behavior checks for CI/local DLC test runs.

plotting

DeepLabCut2.0 Toolbox (deeplabcut.org)

pseudo_label
skeleton

DeepLabCut2.2 Toolbox (deeplabcut.org)

video_processor

Author: Hao Wu

visualization

DeepLabCut2.0 Toolbox (deeplabcut.org)

Classes:

Name Description
DLCDeprecationWarning

Project-specific deprecation warning. Helps with filtering.

PoseConfig

Main configuration class for DeepLabCut pose estimation models.

VideoProcessor

Abstract base class for video reading and writing.

VideoProcessorCV

OpenCV-backed video reader and writer.

VideoWriter
vp

OpenCV-backed video reader and writer.

Functions:

Name Description
CreateVideo

Creating individual frames with labeled body parts and making a video.

CreateVideoSlow

Creating individual frames with labeled body parts and making a video.

CropVideo

Auxiliary function to crop a video and output it to the same folder with

DownSampleVideo

Auxiliary function to downsample a video and output it to the same folder with

IntersectionofIndividualsandOnesGivenbyUser

Returns all individuals when set to 'all', otherwise all bpts that are in the

KmeansbasedFrameselection

This code downsamples the video to a width of resizewidth.

KmeansbasedFrameselectioncv2

This code downsamples the video to a width of resizewidth. The video is extracted

LoadFullMultiAnimalData

Load predicted data and metadata from pickle files created by predict_videos.py.

PlottingResults

Plots poses vs time; pose x vs pose y; histogram of differences and

SaveFullMultiAnimalData

Save predicted data as h5 file and metadata as pickle file; created by

ShortenVideo

Auxiliary function to shorten video and output with outsuffix appended to the

UniformFrames

Temporally uniformly sampling frames in interval (start,stop). Visual information

UniformFramescv2

Temporally uniformly sampling frames in interval (start,stop). Visual information

adapt_labeled_data_to_new_project

Given the config.yaml file, this function will convert the labels of an ancient

analyze_videos_converth5_to_csv

By default the output poses (when running analyze_videos) are stored as

analyze_videos_converth5_to_nwb

Convert all h5 output data files in video_folder to NWB format.

attempt_to_make_folder

Attempts to create a folder with specified name.

check_if_post_processing

Checks if filtered/bone lengths were already calculated.

collect_video_paths

Collects video paths from a given set of data paths: directories, files, or a mix

convert2_maDLC

Convert a single-animal annotation file into a multianimal annotation file.

convert_single2multiplelegacyAM

Convert multi animal to single animal code and vice versa.

convertcsv2h5

Convert annotation files in labeled-data from csv to h5.

create_labeled_video

Labels the bodyparts in a video.

create_video_with_all_detections

Create a video labeled with all the detections stored in a '*_full.pickle' file.

deprecated

Mark a function as deprecated.

filter_files_by_patterns

Filters files in a folder based on start, contain, and end patterns.

filter_unwanted_paf_connections

Get rid of skeleton connections between multi and unique body parts.

find_analyzed_data

Find potential data files from the hints given to the function.

find_video_metadata

For backward compatibility, let us search the substring 'meta'.

get_bodyparts

Get the bodyparts.

get_data_and_metadata_filenames

Paths to data and metadata files relative to the project root.

get_deeplabcut_path

Get path of where deeplabcut is currently running.

get_evaluation_folder

Get the evaluation folder.

get_model_folder

Get the model folder.

get_scorer_name

Extract the scorer/network name for a particular shuffle, training fraction, etc.

get_snapshots_from_folder

Returns an ordered list of existing snapshot names in the train folder, sorted by

get_training_set_folder

Training Set folder for config file based on parameters.

get_unique_bodyparts

Get the unique bodyparts.

get_video_list

Get list of videos in a path (if filetype == all), otherwise just a specific

getpafgraph

Auxiliary function that turns skeleton (list of connected bodypart pairs) into a

grab_files_in_folder

Return the paths of files with extension ext present in folder.

imread

Read image either with skimage or cv2.

intersection_of_body_parts_and_ones_given_by_user

Returns all body parts when comparisonbodyparts=='all', otherwise all bpts that

merge_windowsannotationdataONlinuxsystem

If a project was created on Windows (and labeled there,) but ran on unix then the

plot_edge_affinity_distributions

Display the distribution of affinity costs of within- and between-animal edges.

plot_trajectories

Plots the trajectories of various bodyparts across the video.

proc_video

Helper function for create_labeled_video.

read_inferencecfg

Load inferencecfg or initialize it.

read_pickle

Read the pickle file.

read_plainconfig

Load a YAML config (alias for read_config_as_dict). See deeplabcut.core.config.

renamed_parameter

Support a renamed keyword argument while warning callers to update.

reorder_individuals_in_df

Reorders data of df to match the order given in a list.

returnlabelingdata

Returns a specific labeleing data set -- the user will be asked which one.

rotate_video

Auxiliary function to rotate a video and output it to the same folder with

safe_resolve

Return a resolved Path that is safe to use with str-based I/O.

save_data

Save predicted data as h5 file and metadata as pickle file; created by

write_pickle

Write the pickle file.

write_plainconfig

Write a config dict to YAML (alias for write_config). See deeplabcut.core.config.

DLCDeprecationWarning

Bases: DeprecationWarning

Project-specific deprecation warning. Helps with filtering.

Source code in deeplabcut/core/deprecation.py
class DLCDeprecationWarning(DeprecationWarning):
    """Project-specific deprecation warning. Helps with filtering."""

PoseConfig

Bases: DLCVersionedConfig

Main configuration class for DeepLabCut pose estimation models.

This is the top-level configuration that brings together all the different configuration domains (project, model, data, training, etc.).

Attributes:

Name Type Description
net_type NetType

Network architecture type (e.g., resnet_50, hrnet_w32, dlcrnet_stride16_ms5)

method MethodType

Method type (bu=Bottom-Up, td=Top-Down, ctd=Conditional Top-Down)

device str

Device configuration (auto, cpu, cuda)

project str

Project configuration (skeleton, individuals, etc.)

model ModelConfig

Model configuration (backbone, heads, etc.)

detector DetectorConfig | None

Detector configuration (for top-down models)

data DataConfig

Data configuration (loaders, transforms, etc.)

training DataConfig

Training configuration (runner, optimizer, etc.)

inference InferenceConfig

Inference configuration (multithreading, compilation, etc.)

logger CSVLoggerConfig | WandbLoggerConfig | None

Logger configuration (e.g., WandB or CSV logger)

with_center_keypoints bool

Whether to include center keypoints (for DEKR models)

Methods:

Name Description
build

Build a typed PoseConfig for a project

Source code in deeplabcut/pose_estimation_pytorch/config/pose.py
class PoseConfig(DLCVersionedConfig):
    """Main configuration class for DeepLabCut pose estimation models.

    This is the top-level configuration that brings together all the different
    configuration domains (project, model, data, training, etc.).

    Attributes:
        net_type: Network architecture type (e.g., resnet_50, hrnet_w32, dlcrnet_stride16_ms5)
        method: Method type (bu=Bottom-Up, td=Top-Down, ctd=Conditional Top-Down)
        device: Device configuration (auto, cpu, cuda)
        project: Project configuration (skeleton, individuals, etc.)
        model: Model configuration (backbone, heads, etc.)
        detector: Detector configuration (for top-down models)
        data: Data configuration (loaders, transforms, etc.)
        training: Training configuration (runner, optimizer, etc.)
        inference: Inference configuration (multithreading, compilation, etc.)
        logger: Logger configuration (e.g., WandB or CSV logger)
        with_center_keypoints: Whether to include center keypoints (for DEKR models)
    """

    model: ModelConfig = Field(default_factory=ModelConfig)
    net_type: NetType = NetType.RESNET_50
    method: MethodType = MethodType.BOTTOM_UP
    device: str = "auto"
    metadata: PoseMetadata
    data: DataConfig
    runner: RunnerConfig
    train_settings: TrainSettingsConfig
    inference: InferenceConfig = Field(default_factory=InferenceConfig)
    logger: CSVLoggerConfig | WandbLoggerConfig | None = Field(default=None, discriminator="type")
    with_center_keypoints: bool = False
    detector: DetectorConfig | None = None
    resume_training_from: str | None = None

    @field_validator("net_type", mode="before")
    @classmethod
    def _coerce_net_type(cls, v: object) -> NetType:
        if isinstance(v, NetType):
            return v
        net_type, _ = NetType.from_alias(str(v))
        return net_type

    @classmethod
    def build(
        cls,
        project_config: ProjectConfig | dict | Path | str,
        pose_config_path: str | Path,
        *,
        top_down: bool,
        multi_animal: bool | None = None,
        net_type: NetType | str | None = None,
        detector_type: DetectorType | str | None = None,
        weight_init: WeightInitialization | dict | Path | str | None = None,
        ctd_conditions: int | str | Path | tuple[int, str] | tuple[int, int] | None = None,
        save: bool = False,
    ) -> Self:
        """Build a typed PoseConfig for a project

        Args:
            project_config (ProjectConfig | dict | Path | str): The project configuration.
            pose_config_path (str | Path): The path to the pose configuration.
            top_down (bool): Whether to use a top-down backbone.
            net_type (NetType | str | None, optional): The network architecture type (without 'top_down_' prefix).
                If None, the default net type from the project config will be used.
            detector_type (DetectorType | str | None, optional): The detector architecture. Required for td models.
            weight_init (WeightInitialization | None, optional): The weight initialization object or path.
            ctd_conditions (int | str | Path | tuple[int, str] | tuple[int, int] | None, optional):
                The conditional top-down conditions. Only required for CTD models.
                A predictions file path is evaluation-only; shuffle refs work for
                both evaluation and live analyze.
            save (bool, optional): Whether to save the pose configuration.

        Note:
            For generic backbone models, ``method`` is resolved from ``top_down``. For non-backbone models,
            the ``top_down`` is ignored and ``method`` is resolved from the default config.
        """
        from deeplabcut.core.weight_init import WeightInitialization

        # Normalize input parameters + set defaults if not provided
        project_config = ProjectConfig.from_any(project_config)
        if multi_animal is None:
            multi_animal = project_config.multianimalproject
        net_type, task = resolve_net_type_and_task(
            net_type,
            default=project_config.default_net_type,
            top_down=top_down,
        )
        if detector_type is not None:
            detector_type = DetectorType(detector_type)
        if weight_init is not None:
            weight_init = WeightInitialization.from_any(weight_init)

        # Build related configurations (PoseMetadata, PAFParameters, DetectorConfig)
        metadata = PoseMetadata.build(project_config, pose_config_path=pose_config_path)
        paf_parameters = None
        detector_config = None
        if task == Task.BOTTOM_UP and multi_animal:
            paf_parameters = PAFParameters.build(project_config)
        elif task == Task.TOP_DOWN:
            detector_config = DetectorConfig.build(metadata.num_individuals, detector_type)

        # Build the pose model config
        defaults: dict = build_pose_config_defaults(
            net_type=net_type,
            metadata=metadata,
            paf_parameters=paf_parameters,
            weight_init=weight_init,
            task=task,
            multi_animal=multi_animal,
            detector_config=detector_config,
            ctd_conditions=ctd_conditions,
        )
        pose_config = cls.from_dict(defaults)

        # Save if needed
        if save:
            pose_config.to_yaml(pose_config_path, overwrite=True)

        return pose_config

    @classmethod
    def build_for_superanimal_inference(
        cls,
        super_animal: str,
        *,
        model_name: str,
        detector_name: str | None = None,
        max_individuals: int = 30,
        device: str | None = None,
    ) -> Self:
        from deeplabcut.pose_estimation_pytorch.modelzoo.config import build_superanimal_inference_config

        metadata = PoseMetadata.build_for_superanimal(
            super_animal=super_animal, model_name=model_name, max_individuals=max_individuals
        )
        return cls.from_dict(
            build_superanimal_inference_config(
                super_animal=super_animal,
                model_name=model_name,
                detector_name=detector_name,
                metadata=metadata,
                device=device,
            )
        )

    @classmethod
    def build_for_superanimal_finetune(
        cls,
        project_config: ProjectConfig | dict | Path | str,
        *,
        model_name: str,
        detector_name: str | None,
        pose_config_path: str | Path,
        weight_init: WeightInitialization,
        inference_config: InferenceConfig | dict | Path | str | None = None,
        save: bool = False,
    ) -> Self:
        from deeplabcut.pose_estimation_pytorch.modelzoo.config import build_superanimal_finetune_config

        # Normalize input parameters + build related configurations
        project_config = ProjectConfig.from_any(project_config)
        if inference_config is None:
            inference_config = InferenceConfig()
        else:
            inference_config = InferenceConfig.from_any(inference_config)
        metadata = PoseMetadata.build(project_config, pose_config_path=pose_config_path)

        # Input validation
        if weight_init.dataset is None:
            raise ValueError("`WeightInitialization.dataset` is required for fine-tuning SuperAnimal models.")

        if not weight_init.with_decoder:
            raise ValueError(
                "`weight_init.with_decoder=True` is required for fine-tuning SuperAnimal models."
                "Please set `with_decoder=True` to fine-tune a model, or create a transfer learning config instead."
            )

        # Build the pose configuration
        pose_config = cls.from_dict(
            build_superanimal_finetune_config(
                weight_init,
                metadata,
                model_name,
                detector_name,
                inference_config=inference_config,
            )
        )

        if save:
            pose_config.to_yaml(metadata.pose_config_path, overwrite=True)
        return pose_config

build classmethod

build(
    project_config: ProjectConfig | dict | Path | str,
    pose_config_path: str | Path,
    *,
    top_down: bool,
    multi_animal: bool | None = None,
    net_type: NetType | str | None = None,
    detector_type: DetectorType | str | None = None,
    weight_init: WeightInitialization | dict | Path | str | None = None,
    ctd_conditions: int | str | Path | tuple[int, str] | tuple[int, int] | None = None,
    save: bool = False
) -> Self

Build a typed PoseConfig for a project

Parameters:

Name Type Description Default

project_config

ProjectConfig | dict | Path | str

The project configuration.

required

pose_config_path

str | Path

The path to the pose configuration.

required

top_down

bool

Whether to use a top-down backbone.

required

net_type

NetType | str | None

The network architecture type (without 'top_down_' prefix). If None, the default net type from the project config will be used.

None

detector_type

DetectorType | str | None

The detector architecture. Required for td models.

None

weight_init

WeightInitialization | None

The weight initialization object or path.

None

ctd_conditions

int | str | Path | tuple[int, str] | tuple[int, int] | None

The conditional top-down conditions. Only required for CTD models. A predictions file path is evaluation-only; shuffle refs work for both evaluation and live analyze.

None

save

bool

Whether to save the pose configuration.

False
Note

For generic backbone models, method is resolved from top_down. For non-backbone models, the top_down is ignored and method is resolved from the default config.

Source code in deeplabcut/pose_estimation_pytorch/config/pose.py
@classmethod
def build(
    cls,
    project_config: ProjectConfig | dict | Path | str,
    pose_config_path: str | Path,
    *,
    top_down: bool,
    multi_animal: bool | None = None,
    net_type: NetType | str | None = None,
    detector_type: DetectorType | str | None = None,
    weight_init: WeightInitialization | dict | Path | str | None = None,
    ctd_conditions: int | str | Path | tuple[int, str] | tuple[int, int] | None = None,
    save: bool = False,
) -> Self:
    """Build a typed PoseConfig for a project

    Args:
        project_config (ProjectConfig | dict | Path | str): The project configuration.
        pose_config_path (str | Path): The path to the pose configuration.
        top_down (bool): Whether to use a top-down backbone.
        net_type (NetType | str | None, optional): The network architecture type (without 'top_down_' prefix).
            If None, the default net type from the project config will be used.
        detector_type (DetectorType | str | None, optional): The detector architecture. Required for td models.
        weight_init (WeightInitialization | None, optional): The weight initialization object or path.
        ctd_conditions (int | str | Path | tuple[int, str] | tuple[int, int] | None, optional):
            The conditional top-down conditions. Only required for CTD models.
            A predictions file path is evaluation-only; shuffle refs work for
            both evaluation and live analyze.
        save (bool, optional): Whether to save the pose configuration.

    Note:
        For generic backbone models, ``method`` is resolved from ``top_down``. For non-backbone models,
        the ``top_down`` is ignored and ``method`` is resolved from the default config.
    """
    from deeplabcut.core.weight_init import WeightInitialization

    # Normalize input parameters + set defaults if not provided
    project_config = ProjectConfig.from_any(project_config)
    if multi_animal is None:
        multi_animal = project_config.multianimalproject
    net_type, task = resolve_net_type_and_task(
        net_type,
        default=project_config.default_net_type,
        top_down=top_down,
    )
    if detector_type is not None:
        detector_type = DetectorType(detector_type)
    if weight_init is not None:
        weight_init = WeightInitialization.from_any(weight_init)

    # Build related configurations (PoseMetadata, PAFParameters, DetectorConfig)
    metadata = PoseMetadata.build(project_config, pose_config_path=pose_config_path)
    paf_parameters = None
    detector_config = None
    if task == Task.BOTTOM_UP and multi_animal:
        paf_parameters = PAFParameters.build(project_config)
    elif task == Task.TOP_DOWN:
        detector_config = DetectorConfig.build(metadata.num_individuals, detector_type)

    # Build the pose model config
    defaults: dict = build_pose_config_defaults(
        net_type=net_type,
        metadata=metadata,
        paf_parameters=paf_parameters,
        weight_init=weight_init,
        task=task,
        multi_animal=multi_animal,
        detector_config=detector_config,
        ctd_conditions=ctd_conditions,
    )
    pose_config = cls.from_dict(defaults)

    # Save if needed
    if save:
        pose_config.to_yaml(pose_config_path, overwrite=True)

    return pose_config

VideoProcessor

Bases: ABC

Abstract base class for video reading and writing.

Subclasses implement backend-specific video loading, metadata extraction, output video creation, frame reading, frame writing, and cleanup.

Parameters:

Name Type Description Default

fname

str

Path to the input video. If empty, no input video is opened.

''

sname

str

Path to the output video. If empty, no output video is created.

''

nframes

int

Number of frames to process. -1 means all frames.

-1

fps

float | None

Optional FPS override.

None

codec

str

FourCC codec string used for output videos.

'X264'

sh

int | Literal[''] | None

Output video height. "" and None mean use the input height.

''

sw

int | Literal[''] | None

Output video width. "" and None mean use the input width.

''

Attributes:

Name Type Description
fname str

Input video path.

sname str

Output video path.

nframes int

Number of frames to process.

video_fps float | None

Video frame rate.

FPS float | None

Legacy alias for video_fps.

h int

Input video height.

w int

Input video width.

nc int

Number of channels.

i int

Number of successfully loaded frames.

vid

Backend-specific input video object.

svid

Backend-specific output video object.

sh int

Output video height.

sw int

Output video width.

Notes

height(), width(), fps(), counter(), and frame_count() are retained as methods for backwards compatibility.

Methods:

Name Description
close

Implement your own.

create_video

Implement your own.

get_info

Implement your own.

save_frame

Implement your own.

Source code in deeplabcut/utils/video_processor.py
class VideoProcessor(ABC):
    """Abstract base class for video reading and writing.

    Subclasses implement backend-specific video loading, metadata extraction,
    output video creation, frame reading, frame writing, and cleanup.

    Args:
        fname (str): Path to the input video. If empty, no input video is opened.
        sname (str): Path to the output video. If empty, no output video is created.
        nframes (int): Number of frames to process. ``-1`` means all frames.
        fps (float | None): Optional FPS override.
        codec (str): FourCC codec string used for output videos.
        sh (int | Literal[""] | None): Output video height. ``""`` and ``None``
            mean use the input height.
        sw (int | Literal[""] | None): Output video width. ``""`` and ``None``
            mean use the input width.

    Attributes:
        fname (str): Input video path.
        sname (str): Output video path.
        nframes (int): Number of frames to process.
        video_fps (float | None): Video frame rate.
        FPS (float | None): Legacy alias for ``video_fps``.
        h (int): Input video height.
        w (int): Input video width.
        nc (int): Number of channels.
        i (int): Number of successfully loaded frames.
        vid: Backend-specific input video object.
        svid: Backend-specific output video object.
        sh (int): Output video height.
        sw (int): Output video width.

    Notes:
        ``height()``, ``width()``, ``fps()``, ``counter()``, and
        ``frame_count()`` are retained as methods for backwards compatibility.
    """

    def __init__(
        self,
        fname: str = "",
        sname: str = "",
        nframes: int = -1,
        fps: float | None = None,
        codec: str = "X264",
        sh: int | Literal[""] | None = "",
        sw: int | Literal[""] | None = "",
    ):
        self._fname = None
        self._sname = None
        self._fps = None
        self._nframes = None
        self._h = 0
        self._w = 0
        self.vid = None
        self.svid = None
        self.sh = 0
        self.sw = 0
        self.fname = fname
        self.sname = sname
        self.codec = codec
        self.nframes = nframes
        self.nc = 3
        self.i = 0

        try:
            if self.fname != "":
                self.vid = self.get_video()
                self.get_info()
                self.sh = 0
                self.sw = 0
            if self.sname != "":
                if sh in ("", None) and sw in (None, ""):
                    self.sh = self._h
                    self.sw = self._w
                else:
                    self.sw = sw
                    self.sh = sh
                self.svid = self.create_video()

        except Exception as ex:
            logger.exception("VideoProcessor initialization failed: %s", ex)

        if fps is not None:  # Overwrite the video's FPS
            # NOTE @C-Achard 2026-06-09 improving checks here might break old API
            # same for raising on missing FPS
            self.fps = fps

    def load_frame(self):
        frame = self._read_frame()
        if frame is not None:
            self.i += 1
        return frame

    @property
    def fname(self):
        return self._fname

    @fname.setter
    def fname(self, value):
        self._fname = "" if value in (None, "") else str(value)

    @property
    def sname(self):
        return self._sname

    @sname.setter
    def sname(self, value):
        self._sname = "" if value in (None, "") else str(value)

    @property
    def height(self):
        return self._h

    @property
    def width(self):
        return self._w

    @height.setter
    def height(self, value):
        self._h = int(value)

    @width.setter
    def width(self, value):
        self._w = int(value)

    @property
    def fps(self):
        return self._fps

    @fps.setter
    def fps(self, value):
        self._fps = None if value is None else float(value)

    @property
    def nframes(self):
        return self._nframes

    @nframes.setter
    def nframes(self, value):
        self._nframes = int(value)

    @abstractmethod
    def get_video(self):

        raise NotImplementedError("Implement your own get_video method.")

    @abstractmethod
    def get_info(self):
        """Implement your own."""

    @abstractmethod
    def create_video(self):
        """Implement your own."""

    @abstractmethod
    def _read_frame(self):
        """Implement your own."""

    @abstractmethod
    def save_frame(self, frame):
        """Implement your own."""

    @abstractmethod
    def close(self):
        """Implement your own."""

close abstractmethod

close()

Implement your own.

Source code in deeplabcut/utils/video_processor.py
@abstractmethod
def close(self):
    """Implement your own."""

create_video abstractmethod

create_video()

Implement your own.

Source code in deeplabcut/utils/video_processor.py
@abstractmethod
def create_video(self):
    """Implement your own."""

get_info abstractmethod

get_info()

Implement your own.

Source code in deeplabcut/utils/video_processor.py
@abstractmethod
def get_info(self):
    """Implement your own."""

save_frame abstractmethod

save_frame(frame)

Implement your own.

Source code in deeplabcut/utils/video_processor.py
@abstractmethod
def save_frame(self, frame):
    """Implement your own."""

VideoProcessorCV

Bases: VideoProcessor

OpenCV-backed video reader and writer.

This implementation uses cv2.VideoCapture for reading videos and cv2.VideoWriter for writing videos. Frames returned by load_frame are converted from OpenCV's native BGR channel order to RGB channel order. Frames passed to save_frame are expected to be in RGB order and are converted back to BGR before writing.

Attributes:

Name Type Description
fname str

Path to the input video. If empty, no input video is opened.

sname str

Path to the output video. If empty, no output video is created.

nframes int

Number of frames to process. If initialized as -1, it is replaced by the total number of frames reported by OpenCV.

codec str

FourCC codec string used when creating the output video.

h int

Input video height in pixels.

w int

Input video width in pixels.

nc int

Number of channels. This implementation uses 3.

i int

Number of frames successfully loaded through load_frame().

FPS float

Frames per second reported by OpenCV, or the user-provided override.

sh int

Output video height in pixels.

sw int

Output video width in pixels.

vid VideoCapture | None

OpenCV video reader.

svid VideoWriter | None

OpenCV video writer.

Methods:

Name Description
close

Release OpenCV reader and writer resources.

create_video

Create an OpenCV video writer.

get_info

Populate metadata from the OpenCV video reader.

get_video

Open the input video with OpenCV.

save_frame

Write one RGB frame to the output video.

Source code in deeplabcut/utils/video_processor.py
class VideoProcessorCV(VideoProcessor):
    """OpenCV-backed video reader and writer.

    This implementation uses cv2.VideoCapture for reading videos and
    cv2.VideoWriter for writing videos. Frames returned by
    `load_frame` are converted from OpenCV's native BGR channel order to
    RGB channel order. Frames passed to `save_frame` are expected to be in
    RGB order and are converted back to BGR before writing.

    Attributes:
        fname (str): Path to the input video. If empty, no input video is opened.
        sname (str): Path to the output video. If empty, no output video is created.
        nframes (int): Number of frames to process. If initialized as ``-1``,
            it is replaced by the total number of frames reported by OpenCV.
        codec (str): FourCC codec string used when creating the output video.
        h (int): Input video height in pixels.
        w (int): Input video width in pixels.
        nc (int): Number of channels. This implementation uses ``3``.
        i (int): Number of frames successfully loaded through ``load_frame()``.
        FPS (float): Frames per second reported by OpenCV, or the user-provided
            override.
        sh (int): Output video height in pixels.
        sw (int): Output video width in pixels.
        vid (cv2.VideoCapture | None): OpenCV video reader.
        svid (cv2.VideoWriter | None): OpenCV video writer.
    """

    def get_video(self):
        """Open the input video with OpenCV.

        Returns:
            cv2.VideoCapture: OpenCV video capture object for ``self.fname``.
        """
        return cv2.VideoCapture(self.fname)

    def get_info(self):
        """Populate metadata from the OpenCV video reader.

        Sets:
            self.w: Frame width in pixels.
            self.h: Frame height in pixels.
            self.nframes: Number of frames to process.
            self.FPS: Frames per second reported by OpenCV.
            self.nc: Number of channels, always ``3``.

        Notes:
            If ``self.nframes`` is ``-1`` or greater than the total number of
            frames reported by OpenCV, it is replaced by OpenCV's frame count.
        """
        self.width = int(self.vid.get(cv2.CAP_PROP_FRAME_WIDTH))
        self.height = int(self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT))
        all_frames = int(self.vid.get(cv2.CAP_PROP_FRAME_COUNT))
        self.fps = self.vid.get(cv2.CAP_PROP_FPS)
        self.nc = 3

        if self.nframes == -1 or self.nframes > all_frames:
            self.nframes = all_frames

    def create_video(self):
        """Create an OpenCV video writer.

        Returns:
            cv2.VideoWriter: OpenCV video writer for ``self.sname``.

        Notes:
            ``self.sw`` and ``self.sh`` are expected to be set by the base class
            before this method is called. The codec is interpreted as a FourCC
            string, preserving the historical OpenCV behavior.
        """
        fourcc = cv2.VideoWriter_fourcc(*self.codec)
        return cv2.VideoWriter(self.sname, fourcc, self.fps, (self.sw, self.sh), True)

    def _read_frame(self):
        """Read the next video frame.

        Returns:
            numpy.ndarray | None: The next frame in RGB channel order, or
            ``None`` if no frame could be read.

        Notes:
            OpenCV returns BGR frames. This method converts them to RGB using
            ``np.flip(frame, 2)`` to preserve legacy behavior.
        """
        if self.vid is None:
            return None

        success, frame = self.vid.read()
        if not success:
            return frame

        return np.flip(frame, 2)

    def save_frame(self, frame):
        """Write one RGB frame to the output video.

        Args:
            frame (numpy.ndarray | None): RGB frame to write. ``None`` is ignored.

        Notes:
            This method preserves the historical behavior of silently ignoring
            ``None`` frames. Non-``None`` frames are converted from RGB to BGR
            before being passed to OpenCV.
        """
        if frame is None:
            return
        if self.svid is not None:
            self.svid.write(np.flip(frame, 2))
        else:
            logger.warning(f"Could not write video because no output video writer is open for {self.sname}")

    def close(self):
        """Release OpenCV reader and writer resources.

        This method is safe to call multiple times. After release, ``self.svid``
        and ``self.vid`` are set to ``None`` to avoid accidental reuse of closed
        OpenCV handles.
        """
        if hasattr(self, "svid") and self.svid is not None:
            self.svid.release()
            self.svid = None

        if hasattr(self, "vid") and self.vid is not None:
            self.vid.release()
            self.vid = None

close

close()

Release OpenCV reader and writer resources.

This method is safe to call multiple times. After release, self.svid and self.vid are set to None to avoid accidental reuse of closed OpenCV handles.

Source code in deeplabcut/utils/video_processor.py
def close(self):
    """Release OpenCV reader and writer resources.

    This method is safe to call multiple times. After release, ``self.svid``
    and ``self.vid`` are set to ``None`` to avoid accidental reuse of closed
    OpenCV handles.
    """
    if hasattr(self, "svid") and self.svid is not None:
        self.svid.release()
        self.svid = None

    if hasattr(self, "vid") and self.vid is not None:
        self.vid.release()
        self.vid = None

create_video

create_video()

Create an OpenCV video writer.

Returns:

Type Description

cv2.VideoWriter: OpenCV video writer for self.sname.

Notes

self.sw and self.sh are expected to be set by the base class before this method is called. The codec is interpreted as a FourCC string, preserving the historical OpenCV behavior.

Source code in deeplabcut/utils/video_processor.py
def create_video(self):
    """Create an OpenCV video writer.

    Returns:
        cv2.VideoWriter: OpenCV video writer for ``self.sname``.

    Notes:
        ``self.sw`` and ``self.sh`` are expected to be set by the base class
        before this method is called. The codec is interpreted as a FourCC
        string, preserving the historical OpenCV behavior.
    """
    fourcc = cv2.VideoWriter_fourcc(*self.codec)
    return cv2.VideoWriter(self.sname, fourcc, self.fps, (self.sw, self.sh), True)

get_info

get_info()

Populate metadata from the OpenCV video reader.

Sets

self.w: Frame width in pixels. self.h: Frame height in pixels. self.nframes: Number of frames to process. self.FPS: Frames per second reported by OpenCV. self.nc: Number of channels, always 3.

Notes

If self.nframes is -1 or greater than the total number of frames reported by OpenCV, it is replaced by OpenCV's frame count.

Source code in deeplabcut/utils/video_processor.py
def get_info(self):
    """Populate metadata from the OpenCV video reader.

    Sets:
        self.w: Frame width in pixels.
        self.h: Frame height in pixels.
        self.nframes: Number of frames to process.
        self.FPS: Frames per second reported by OpenCV.
        self.nc: Number of channels, always ``3``.

    Notes:
        If ``self.nframes`` is ``-1`` or greater than the total number of
        frames reported by OpenCV, it is replaced by OpenCV's frame count.
    """
    self.width = int(self.vid.get(cv2.CAP_PROP_FRAME_WIDTH))
    self.height = int(self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT))
    all_frames = int(self.vid.get(cv2.CAP_PROP_FRAME_COUNT))
    self.fps = self.vid.get(cv2.CAP_PROP_FPS)
    self.nc = 3

    if self.nframes == -1 or self.nframes > all_frames:
        self.nframes = all_frames

get_video

get_video()

Open the input video with OpenCV.

Returns:

Type Description

cv2.VideoCapture: OpenCV video capture object for self.fname.

Source code in deeplabcut/utils/video_processor.py
def get_video(self):
    """Open the input video with OpenCV.

    Returns:
        cv2.VideoCapture: OpenCV video capture object for ``self.fname``.
    """
    return cv2.VideoCapture(self.fname)

save_frame

save_frame(frame)

Write one RGB frame to the output video.

Parameters:

Name Type Description Default

frame

ndarray | None

RGB frame to write. None is ignored.

required
Notes

This method preserves the historical behavior of silently ignoring None frames. Non-None frames are converted from RGB to BGR before being passed to OpenCV.

Source code in deeplabcut/utils/video_processor.py
def save_frame(self, frame):
    """Write one RGB frame to the output video.

    Args:
        frame (numpy.ndarray | None): RGB frame to write. ``None`` is ignored.

    Notes:
        This method preserves the historical behavior of silently ignoring
        ``None`` frames. Non-``None`` frames are converted from RGB to BGR
        before being passed to OpenCV.
    """
    if frame is None:
        return
    if self.svid is not None:
        self.svid.write(np.flip(frame, 2))
    else:
        logger.warning(f"Could not write video because no output video writer is open for {self.sname}")

VideoWriter

Bases: VideoReader

Methods:

Name Description
shorten

Shorten the video from start to end.

split

Split a video into several shorter ones of equal duration.

Source code in deeplabcut/utils/auxfun_videos.py
class VideoWriter(VideoReader):
    def __init__(self, video_path, codec="h264", dpi=100, fps=None):
        super().__init__(video_path)
        self.codec = codec
        self.dpi = dpi
        if fps:
            self.fps = fps

    def shorten(self, start, end, suffix="short", dest_folder=None, validate_inputs=True):
        """Shorten the video from start to end.

        Args:
            start (str): Time formatted in hours:minutes:seconds, where shortened video
                shall start.
            end (str): Time formatted in hours:minutes:seconds, where shortened video
                shall end.
            suffix (str, optional): String added to the name of the shortened video.
                Defaults to 'short'.
            dest_folder (str, optional): Folder the video is saved into. By default,
                same as the original video.

        Returns:
            str: Full path to the shortened video.
        """

        def validate_timestamp(stamp):
            if not isinstance(stamp, str):
                raise ValueError("Timestamp should be a string formatted as hours:minutes:seconds.")
            time = datetime.datetime.strptime(stamp, "%H:%M:%S").time()
            # The above already raises a ValueError if formatting is wrong
            seconds = (time.hour * 60 + time.minute) * 60 + time.second
            if seconds > self.calc_duration():
                raise ValueError("Timestamps must not exceed the video duration.")

        if validate_inputs:
            for stamp in start, end:
                validate_timestamp(stamp)

        output_path = self.make_output_path(suffix, dest_folder)
        command = f'ffmpeg -n -i "{self.video_path}" -ss {start} -to {end} -c:a copy "{output_path}"'
        subprocess.call(command, shell=True)
        return output_path

    def split(self, n_splits, suffix="split", dest_folder=None):
        """Split a video into several shorter ones of equal duration.

        Args:
            n_splits (int): Number of shorter videos to produce.
            suffix (str, optional): String added to the name of the splits.
                Defaults to 'split'.
            dest_folder (str, optional): Folder the video splits are saved into. By
                default, same as the original video.

        Returns:
            list: Paths of the video splits.
        """
        if not n_splits > 1:
            raise ValueError("The video should at least be split in half.")
        chunk_dur = self.calc_duration() / n_splits
        splits = np.arange(n_splits + 1) * chunk_dur

        def time_formatter(val):
            return str(datetime.timedelta(seconds=val))

        clips = []
        for n, (start, end) in enumerate(zip(splits, splits[1:], strict=False), start=1):
            clips.append(
                self.shorten(
                    time_formatter(start),
                    time_formatter(end),
                    f"{suffix}{n}",
                    dest_folder,
                    validate_inputs=False,
                )
            )
        return clips

    def crop(self, suffix="crop", dest_folder=None):
        x1, _, y1, _ = self.get_bbox()
        output_path = self.make_output_path(suffix, dest_folder)
        command = (
            f'ffmpeg -n -i "{self.video_path}" '
            f"-filter:v crop={self.width}:{self.height}:{x1}:{y1} "
            f'-c:a copy "{output_path}"'
        )
        subprocess.call(command, shell=True)
        return output_path

    def rotate(self, angle, rotatecw="Arbitrary", suffix="rotated", dest_folder=None):
        output_path = self.make_output_path(suffix, dest_folder)
        command = f'ffmpeg -n -i "{self.video_path}" -vf '
        if rotatecw == "Arbitrary":
            angle = np.deg2rad(angle)
            command += f"rotate={angle} "
        elif rotatecw == "Yes":
            command += "transpose=1 "
        else:
            raise ValueError("Unknown rotation direction.")

        command += f'-c:a copy "{output_path}"'
        subprocess.call(command, shell=True)
        return output_path

    def rescale(
        self,
        width,
        height=-1,
        rotatecw="No",
        angle=0.0,
        suffix="rescale",
        dest_folder=None,
    ):
        output_path = self.make_output_path(suffix, dest_folder)
        command = f'ffmpeg -n -i "{self.video_path}" -filter:v "scale={width}:{height}{{}}" -c:a copy "{output_path}"'
        # Rotate, see: https://stackoverflow.com/questions/3937387/rotating-videos-with-ffmpeg
        # interesting option to just update metadata.
        if rotatecw == "Arbitrary":
            angle = np.deg2rad(angle)
            command = command.format(f", rotate={angle}")
        elif rotatecw == "Yes":
            command = command.format(", transpose=1")
        else:
            command = command.format("")
        subprocess.call(command, shell=True)
        return output_path

    @staticmethod
    def write_frame(frame, where):
        cv2.imwrite(where, frame[..., ::-1])

    def make_output_path(self, suffix, dest_folder):
        if not dest_folder:
            dest_folder = self.directory
        return str(Path(dest_folder) / f"{self.name}{suffix}{self.format}")

shorten

shorten(start, end, suffix='short', dest_folder=None, validate_inputs=True)

Shorten the video from start to end.

Parameters:

Name Type Description Default

start

str

Time formatted in hours:minutes:seconds, where shortened video shall start.

required

end

str

Time formatted in hours:minutes:seconds, where shortened video shall end.

required

suffix

str

String added to the name of the shortened video. Defaults to 'short'.

'short'

dest_folder

str

Folder the video is saved into. By default, same as the original video.

None

Returns:

Name Type Description
str

Full path to the shortened video.

Source code in deeplabcut/utils/auxfun_videos.py
def shorten(self, start, end, suffix="short", dest_folder=None, validate_inputs=True):
    """Shorten the video from start to end.

    Args:
        start (str): Time formatted in hours:minutes:seconds, where shortened video
            shall start.
        end (str): Time formatted in hours:minutes:seconds, where shortened video
            shall end.
        suffix (str, optional): String added to the name of the shortened video.
            Defaults to 'short'.
        dest_folder (str, optional): Folder the video is saved into. By default,
            same as the original video.

    Returns:
        str: Full path to the shortened video.
    """

    def validate_timestamp(stamp):
        if not isinstance(stamp, str):
            raise ValueError("Timestamp should be a string formatted as hours:minutes:seconds.")
        time = datetime.datetime.strptime(stamp, "%H:%M:%S").time()
        # The above already raises a ValueError if formatting is wrong
        seconds = (time.hour * 60 + time.minute) * 60 + time.second
        if seconds > self.calc_duration():
            raise ValueError("Timestamps must not exceed the video duration.")

    if validate_inputs:
        for stamp in start, end:
            validate_timestamp(stamp)

    output_path = self.make_output_path(suffix, dest_folder)
    command = f'ffmpeg -n -i "{self.video_path}" -ss {start} -to {end} -c:a copy "{output_path}"'
    subprocess.call(command, shell=True)
    return output_path

split

split(n_splits, suffix='split', dest_folder=None)

Split a video into several shorter ones of equal duration.

Parameters:

Name Type Description Default

n_splits

int

Number of shorter videos to produce.

required

suffix

str

String added to the name of the splits. Defaults to 'split'.

'split'

dest_folder

str

Folder the video splits are saved into. By default, same as the original video.

None

Returns:

Name Type Description
list

Paths of the video splits.

Source code in deeplabcut/utils/auxfun_videos.py
def split(self, n_splits, suffix="split", dest_folder=None):
    """Split a video into several shorter ones of equal duration.

    Args:
        n_splits (int): Number of shorter videos to produce.
        suffix (str, optional): String added to the name of the splits.
            Defaults to 'split'.
        dest_folder (str, optional): Folder the video splits are saved into. By
            default, same as the original video.

    Returns:
        list: Paths of the video splits.
    """
    if not n_splits > 1:
        raise ValueError("The video should at least be split in half.")
    chunk_dur = self.calc_duration() / n_splits
    splits = np.arange(n_splits + 1) * chunk_dur

    def time_formatter(val):
        return str(datetime.timedelta(seconds=val))

    clips = []
    for n, (start, end) in enumerate(zip(splits, splits[1:], strict=False), start=1):
        clips.append(
            self.shorten(
                time_formatter(start),
                time_formatter(end),
                f"{suffix}{n}",
                dest_folder,
                validate_inputs=False,
            )
        )
    return clips

vp

Bases: VideoProcessor

OpenCV-backed video reader and writer.

This implementation uses cv2.VideoCapture for reading videos and cv2.VideoWriter for writing videos. Frames returned by load_frame are converted from OpenCV's native BGR channel order to RGB channel order. Frames passed to save_frame are expected to be in RGB order and are converted back to BGR before writing.

Attributes:

Name Type Description
fname str

Path to the input video. If empty, no input video is opened.

sname str

Path to the output video. If empty, no output video is created.

nframes int

Number of frames to process. If initialized as -1, it is replaced by the total number of frames reported by OpenCV.

codec str

FourCC codec string used when creating the output video.

h int

Input video height in pixels.

w int

Input video width in pixels.

nc int

Number of channels. This implementation uses 3.

i int

Number of frames successfully loaded through load_frame().

FPS float

Frames per second reported by OpenCV, or the user-provided override.

sh int

Output video height in pixels.

sw int

Output video width in pixels.

vid VideoCapture | None

OpenCV video reader.

svid VideoWriter | None

OpenCV video writer.

Methods:

Name Description
close

Release OpenCV reader and writer resources.

create_video

Create an OpenCV video writer.

get_info

Populate metadata from the OpenCV video reader.

get_video

Open the input video with OpenCV.

save_frame

Write one RGB frame to the output video.

Source code in deeplabcut/utils/video_processor.py
class VideoProcessorCV(VideoProcessor):
    """OpenCV-backed video reader and writer.

    This implementation uses cv2.VideoCapture for reading videos and
    cv2.VideoWriter for writing videos. Frames returned by
    `load_frame` are converted from OpenCV's native BGR channel order to
    RGB channel order. Frames passed to `save_frame` are expected to be in
    RGB order and are converted back to BGR before writing.

    Attributes:
        fname (str): Path to the input video. If empty, no input video is opened.
        sname (str): Path to the output video. If empty, no output video is created.
        nframes (int): Number of frames to process. If initialized as ``-1``,
            it is replaced by the total number of frames reported by OpenCV.
        codec (str): FourCC codec string used when creating the output video.
        h (int): Input video height in pixels.
        w (int): Input video width in pixels.
        nc (int): Number of channels. This implementation uses ``3``.
        i (int): Number of frames successfully loaded through ``load_frame()``.
        FPS (float): Frames per second reported by OpenCV, or the user-provided
            override.
        sh (int): Output video height in pixels.
        sw (int): Output video width in pixels.
        vid (cv2.VideoCapture | None): OpenCV video reader.
        svid (cv2.VideoWriter | None): OpenCV video writer.
    """

    def get_video(self):
        """Open the input video with OpenCV.

        Returns:
            cv2.VideoCapture: OpenCV video capture object for ``self.fname``.
        """
        return cv2.VideoCapture(self.fname)

    def get_info(self):
        """Populate metadata from the OpenCV video reader.

        Sets:
            self.w: Frame width in pixels.
            self.h: Frame height in pixels.
            self.nframes: Number of frames to process.
            self.FPS: Frames per second reported by OpenCV.
            self.nc: Number of channels, always ``3``.

        Notes:
            If ``self.nframes`` is ``-1`` or greater than the total number of
            frames reported by OpenCV, it is replaced by OpenCV's frame count.
        """
        self.width = int(self.vid.get(cv2.CAP_PROP_FRAME_WIDTH))
        self.height = int(self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT))
        all_frames = int(self.vid.get(cv2.CAP_PROP_FRAME_COUNT))
        self.fps = self.vid.get(cv2.CAP_PROP_FPS)
        self.nc = 3

        if self.nframes == -1 or self.nframes > all_frames:
            self.nframes = all_frames

    def create_video(self):
        """Create an OpenCV video writer.

        Returns:
            cv2.VideoWriter: OpenCV video writer for ``self.sname``.

        Notes:
            ``self.sw`` and ``self.sh`` are expected to be set by the base class
            before this method is called. The codec is interpreted as a FourCC
            string, preserving the historical OpenCV behavior.
        """
        fourcc = cv2.VideoWriter_fourcc(*self.codec)
        return cv2.VideoWriter(self.sname, fourcc, self.fps, (self.sw, self.sh), True)

    def _read_frame(self):
        """Read the next video frame.

        Returns:
            numpy.ndarray | None: The next frame in RGB channel order, or
            ``None`` if no frame could be read.

        Notes:
            OpenCV returns BGR frames. This method converts them to RGB using
            ``np.flip(frame, 2)`` to preserve legacy behavior.
        """
        if self.vid is None:
            return None

        success, frame = self.vid.read()
        if not success:
            return frame

        return np.flip(frame, 2)

    def save_frame(self, frame):
        """Write one RGB frame to the output video.

        Args:
            frame (numpy.ndarray | None): RGB frame to write. ``None`` is ignored.

        Notes:
            This method preserves the historical behavior of silently ignoring
            ``None`` frames. Non-``None`` frames are converted from RGB to BGR
            before being passed to OpenCV.
        """
        if frame is None:
            return
        if self.svid is not None:
            self.svid.write(np.flip(frame, 2))
        else:
            logger.warning(f"Could not write video because no output video writer is open for {self.sname}")

    def close(self):
        """Release OpenCV reader and writer resources.

        This method is safe to call multiple times. After release, ``self.svid``
        and ``self.vid`` are set to ``None`` to avoid accidental reuse of closed
        OpenCV handles.
        """
        if hasattr(self, "svid") and self.svid is not None:
            self.svid.release()
            self.svid = None

        if hasattr(self, "vid") and self.vid is not None:
            self.vid.release()
            self.vid = None

close

close()

Release OpenCV reader and writer resources.

This method is safe to call multiple times. After release, self.svid and self.vid are set to None to avoid accidental reuse of closed OpenCV handles.

Source code in deeplabcut/utils/video_processor.py
def close(self):
    """Release OpenCV reader and writer resources.

    This method is safe to call multiple times. After release, ``self.svid``
    and ``self.vid`` are set to ``None`` to avoid accidental reuse of closed
    OpenCV handles.
    """
    if hasattr(self, "svid") and self.svid is not None:
        self.svid.release()
        self.svid = None

    if hasattr(self, "vid") and self.vid is not None:
        self.vid.release()
        self.vid = None

create_video

create_video()

Create an OpenCV video writer.

Returns:

Type Description

cv2.VideoWriter: OpenCV video writer for self.sname.

Notes

self.sw and self.sh are expected to be set by the base class before this method is called. The codec is interpreted as a FourCC string, preserving the historical OpenCV behavior.

Source code in deeplabcut/utils/video_processor.py
def create_video(self):
    """Create an OpenCV video writer.

    Returns:
        cv2.VideoWriter: OpenCV video writer for ``self.sname``.

    Notes:
        ``self.sw`` and ``self.sh`` are expected to be set by the base class
        before this method is called. The codec is interpreted as a FourCC
        string, preserving the historical OpenCV behavior.
    """
    fourcc = cv2.VideoWriter_fourcc(*self.codec)
    return cv2.VideoWriter(self.sname, fourcc, self.fps, (self.sw, self.sh), True)

get_info

get_info()

Populate metadata from the OpenCV video reader.

Sets

self.w: Frame width in pixels. self.h: Frame height in pixels. self.nframes: Number of frames to process. self.FPS: Frames per second reported by OpenCV. self.nc: Number of channels, always 3.

Notes

If self.nframes is -1 or greater than the total number of frames reported by OpenCV, it is replaced by OpenCV's frame count.

Source code in deeplabcut/utils/video_processor.py
def get_info(self):
    """Populate metadata from the OpenCV video reader.

    Sets:
        self.w: Frame width in pixels.
        self.h: Frame height in pixels.
        self.nframes: Number of frames to process.
        self.FPS: Frames per second reported by OpenCV.
        self.nc: Number of channels, always ``3``.

    Notes:
        If ``self.nframes`` is ``-1`` or greater than the total number of
        frames reported by OpenCV, it is replaced by OpenCV's frame count.
    """
    self.width = int(self.vid.get(cv2.CAP_PROP_FRAME_WIDTH))
    self.height = int(self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT))
    all_frames = int(self.vid.get(cv2.CAP_PROP_FRAME_COUNT))
    self.fps = self.vid.get(cv2.CAP_PROP_FPS)
    self.nc = 3

    if self.nframes == -1 or self.nframes > all_frames:
        self.nframes = all_frames

get_video

get_video()

Open the input video with OpenCV.

Returns:

Type Description

cv2.VideoCapture: OpenCV video capture object for self.fname.

Source code in deeplabcut/utils/video_processor.py
def get_video(self):
    """Open the input video with OpenCV.

    Returns:
        cv2.VideoCapture: OpenCV video capture object for ``self.fname``.
    """
    return cv2.VideoCapture(self.fname)

save_frame

save_frame(frame)

Write one RGB frame to the output video.

Parameters:

Name Type Description Default

frame

ndarray | None

RGB frame to write. None is ignored.

required
Notes

This method preserves the historical behavior of silently ignoring None frames. Non-None frames are converted from RGB to BGR before being passed to OpenCV.

Source code in deeplabcut/utils/video_processor.py
def save_frame(self, frame):
    """Write one RGB frame to the output video.

    Args:
        frame (numpy.ndarray | None): RGB frame to write. ``None`` is ignored.

    Notes:
        This method preserves the historical behavior of silently ignoring
        ``None`` frames. Non-``None`` frames are converted from RGB to BGR
        before being passed to OpenCV.
    """
    if frame is None:
        return
    if self.svid is not None:
        self.svid.write(np.flip(frame, 2))
    else:
        logger.warning(f"Could not write video because no output video writer is open for {self.sname}")

CreateVideo

CreateVideo(
    clip,
    Dataframe,
    pcutoff,
    dotsize,
    colormap,
    bodyparts2plot,
    trailpoints,
    cropping,
    x1,
    x2,
    y1,
    y2,
    bodyparts2connect,
    skeleton_color,
    draw_skeleton,
    displaycropped,
    color_by,
    confidence_to_alpha=None,
    plot_bboxes=True,
    bboxes_list=None,
    bboxes_pcutoff=0.6,
    bboxes_color: tuple | None = None,
)

Creating individual frames with labeled body parts and making a video.

Source code in deeplabcut/utils/make_labeled_video.py
def CreateVideo(
    clip,
    Dataframe,
    pcutoff,
    dotsize,
    colormap,
    bodyparts2plot,
    trailpoints,
    cropping,
    x1,
    x2,
    y1,
    y2,
    bodyparts2connect,
    skeleton_color,
    draw_skeleton,
    displaycropped,
    color_by,
    confidence_to_alpha=None,
    plot_bboxes=True,
    bboxes_list=None,
    bboxes_pcutoff=0.6,
    bboxes_color: tuple | None = None,
):
    """Creating individual frames with labeled body parts and making a video."""
    bpts = Dataframe.columns.get_level_values("bodyparts")
    all_bpts = bpts.values[::3]
    if draw_skeleton:
        color_for_skeleton = (np.array(mcolors.to_rgba(skeleton_color))[:3] * 255).astype(np.uint8)
        # recode the bodyparts2connect into indices for df_x and df_y for speed
        bpts2connect = get_segment_indices(bodyparts2connect, all_bpts)

    if displaycropped:
        ny, nx = y2 - y1, x2 - x1
    else:
        ny, nx = clip.height, clip.width

    fps = clip.fps
    if isinstance(fps, float):
        if fps * 1000 > 65535:
            fps = round(fps)
    nframes = clip.nframes
    duration = nframes / fps

    print(f"Duration of video [s]: {round(duration, 2)}, recorded with {round(fps, 2)} fps!")
    print(f"Overall # of frames: {nframes} with cropped frame dimensions: {nx} {ny}")
    print("Generating frames and creating video.")

    df_x, df_y, df_likelihood = Dataframe.values.reshape((len(Dataframe), -1, 3)).T

    if cropping and not displaycropped:
        df_x += x1
        df_y += y1
    colorclass = plt.cm.ScalarMappable(cmap=colormap)

    bplist = bpts.unique().to_list()
    nbodyparts = len(bplist)
    if Dataframe.columns.nlevels == 3:
        nindividuals = int(len(all_bpts) / len(set(all_bpts)))
        map2bp = list(np.repeat(list(range(len(set(all_bpts)))), nindividuals))
        map2id = list(range(nindividuals)) * len(set(all_bpts))
    else:
        nindividuals = len(Dataframe.columns.get_level_values("individuals").unique())
        map2bp = [bplist.index(bp) for bp in all_bpts]
        nbpts_per_ind = Dataframe.T.groupby(level="individuals").size().values // 3
        map2id = []
        for i, j in enumerate(nbpts_per_ind):
            map2id.extend([i] * j)
    keep = np.flatnonzero(np.isin(all_bpts, bodyparts2plot))
    bpts2color = [(ind, map2bp[ind], map2id[ind]) for ind in keep]

    if color_by == "bodypart":
        C = colorclass.to_rgba(np.linspace(0, 1, nbodyparts))
    else:
        C = colorclass.to_rgba(np.linspace(0, 1, nindividuals))
    colors = (C[:, :3] * 255).astype(np.uint8)

    if bboxes_color is None:
        bboxes_color = (255, 0, 0)

    with np.errstate(invalid="ignore"):
        for index in trange(min(nframes, len(Dataframe))):
            image = clip.load_frame()
            if displaycropped:
                image = image[y1:y2, x1:x2]

            # Draw bounding boxes if required and present
            if plot_bboxes and bboxes_list:
                bboxes = bboxes_list[index]["bboxes"]
                bbox_scores = bboxes_list[index].get("bbox_scores")
                n_bboxes = len(bboxes)
                for i in range(n_bboxes):
                    bbox = bboxes[i]
                    x, y = bbox[0], bbox[1]
                    x += x1
                    y += y1
                    w, h = bbox[2], bbox[3]
                    if bbox_scores is not None and bbox_scores[i] < bboxes_pcutoff:
                        continue
                    rect_coords = rectangle_perimeter(start=(y, x), extent=(h, w))

                    set_color(
                        image,
                        rect_coords,
                        bboxes_color,
                    )

            # Draw the skeleton for specific bodyparts to be connected as
            # specified in the config file
            if draw_skeleton:
                for bpt1, bpt2 in bpts2connect:
                    if np.all(df_likelihood[[bpt1, bpt2], index] > pcutoff) and not (
                        np.any(np.isnan(df_x[[bpt1, bpt2], index])) or np.any(np.isnan(df_y[[bpt1, bpt2], index]))
                    ):
                        rr, cc, val = line_aa(
                            int(np.clip(df_y[bpt1, index], 0, ny - 1)),
                            int(np.clip(df_x[bpt1, index], 0, nx - 1)),
                            int(np.clip(df_y[bpt2, index], 1, ny - 1)),
                            int(np.clip(df_x[bpt2, index], 1, nx - 1)),
                        )
                        image[rr, cc] = color_for_skeleton

            for ind, num_bp, num_ind in bpts2color:
                if df_likelihood[ind, index] > pcutoff:
                    if color_by == "bodypart":
                        color = colors[num_bp]
                    else:
                        color = colors[num_ind]
                    if trailpoints > 0:
                        for k in range(1, min(trailpoints, index + 1)):
                            rr, cc = disk(
                                (df_y[ind, index - k], df_x[ind, index - k]),
                                dotsize,
                                shape=(ny, nx),
                            )
                            image[rr, cc] = color
                    rr, cc = disk((df_y[ind, index], df_x[ind, index]), dotsize, shape=(ny, nx))
                    alpha = 1
                    if confidence_to_alpha is not None:
                        alpha = confidence_to_alpha(df_likelihood[ind, index])

                    set_color(image, (rr, cc), color, alpha)

            clip.save_frame(image)
    clip.close()

CreateVideoSlow

CreateVideoSlow(
    videooutname,
    clip,
    Dataframe,
    tmpfolder,
    dotsize,
    colormap,
    alphavalue,
    pcutoff,
    trailpoints,
    cropping,
    x1,
    x2,
    y1,
    y2,
    save_frames,
    bodyparts2plot,
    outputframerate,
    Frames2plot,
    bodyparts2connect,
    skeleton_color,
    draw_skeleton,
    displaycropped,
    color_by,
    plot_bboxes=True,
    bboxes_list=None,
    bboxes_pcutoff=0.6,
    bboxes_color: str | None = None,
)

Creating individual frames with labeled body parts and making a video.

Source code in deeplabcut/utils/make_labeled_video.py
def CreateVideoSlow(
    videooutname,
    clip,
    Dataframe,
    tmpfolder,
    dotsize,
    colormap,
    alphavalue,
    pcutoff,
    trailpoints,
    cropping,
    x1,
    x2,
    y1,
    y2,
    save_frames,
    bodyparts2plot,
    outputframerate,
    Frames2plot,
    bodyparts2connect,
    skeleton_color,
    draw_skeleton,
    displaycropped,
    color_by,
    plot_bboxes=True,
    bboxes_list=None,
    bboxes_pcutoff=0.6,
    bboxes_color: str | None = None,
):
    """Creating individual frames with labeled body parts and making a video."""
    if displaycropped:
        ny, nx = y2 - y1, x2 - x1
    else:
        ny, nx = clip.height, clip.width

    fps = clip.fps
    if outputframerate is None:  # by def. same as input rate.
        outputframerate = fps

    nframes = clip.nframes
    duration = nframes / fps

    print(f"Duration of video [s]: {round(duration, 2)}, recorded with {round(fps, 2)} fps!")
    print(f"Overall # of frames: {nframes} with cropped frame dimensions: {nx} {ny}")
    print("Generating frames and creating video.")
    df_x, df_y, df_likelihood = Dataframe.values.reshape((len(Dataframe), -1, 3)).T
    if cropping and not displaycropped:
        df_x += x1
        df_y += y1

    bpts = Dataframe.columns.get_level_values("bodyparts")
    all_bpts = bpts.values[::3]
    if draw_skeleton:
        bpts2connect = get_segment_indices(bodyparts2connect, all_bpts)

    bplist = bpts.unique().to_list()
    nbodyparts = len(bplist)
    if Dataframe.columns.nlevels == 3:
        nindividuals = int(len(all_bpts) / len(set(all_bpts)))
        map2bp = list(np.repeat(list(range(len(set(all_bpts)))), nindividuals))
        map2id = list(range(nindividuals)) * len(set(all_bpts))
    else:
        nindividuals = len(Dataframe.columns.get_level_values("individuals").unique())
        map2bp = [bplist.index(bp) for bp in all_bpts]
        nbpts_per_ind = Dataframe.T.groupby(level="individuals").size().values // 3
        map2id = []
        for i, j in enumerate(nbpts_per_ind):
            map2id.extend([i] * j)
    keep = np.flatnonzero(np.isin(all_bpts, bodyparts2plot))
    bpts2color = [(ind, map2bp[ind], map2id[ind]) for ind in keep]
    if color_by == "individual":
        colors = visualization.get_cmap(nindividuals, name=colormap)
    else:
        colors = visualization.get_cmap(nbodyparts, name=colormap)

    if bboxes_color is None:
        bboxes_color = "red"

    nframes_digits = int(np.ceil(np.log10(nframes)))
    if nframes_digits > 9:
        raise Exception("Your video has more than 10**9 frames, we recommend chopping it up.")

    if Frames2plot is None:
        Index = set(range(nframes))
    else:
        Index = {int(k) for k in Frames2plot if 0 <= k < nframes}

    # Prepare figure
    prev_backend = plt.get_backend()
    plt.switch_backend("agg")
    dpi = 100
    fig = plt.figure(frameon=False, figsize=(nx / dpi, ny / dpi))
    ax = fig.add_subplot(111)

    writer = FFMpegWriter(fps=outputframerate, codec="h264")
    with writer.saving(fig, videooutname, dpi=dpi), np.errstate(invalid="ignore"):
        for index in trange(min(nframes, len(Dataframe))):
            imagename = Path(tmpfolder) / f"file{index:0{nframes_digits}d}.png"
            image = img_as_ubyte(clip.load_frame())
            if index in Index:  # then extract the frame!
                if cropping and displaycropped:
                    image = image[y1:y2, x1:x2]
                ax.imshow(image)

                # Draw bounding boxes of required and present
                if plot_bboxes and bboxes_list:
                    bboxes = bboxes_list[index]["bboxes"]
                    bbox_scores = bboxes_list[index].get("bbox_scores")
                    n_bboxes = len(bboxes)
                    for i in range(n_bboxes):
                        bbox = bboxes[i]
                        bbox_origin = (bbox[0], bbox[1])
                        (bbox_width, bbox_height) = (bbox[2], bbox[3])
                        if bbox_scores is not None and bbox_scores[i] < bboxes_pcutoff:
                            continue
                        rectangle = patches.Rectangle(
                            bbox_origin,
                            bbox_width,
                            bbox_height,
                            linewidth=1,
                            edgecolor=bboxes_color,
                            facecolor="none",
                        )
                        ax.add_patch(rectangle)

                # Draw skeleton
                if draw_skeleton:
                    for bpt1, bpt2 in bpts2connect:
                        if np.all(df_likelihood[[bpt1, bpt2], index] > pcutoff):
                            ax.plot(
                                [df_x[bpt1, index], df_x[bpt2, index]],
                                [df_y[bpt1, index], df_y[bpt2, index]],
                                color=skeleton_color,
                                alpha=alphavalue,
                            )

                # Draw bodyparts
                for ind, num_bp, num_ind in bpts2color:
                    if df_likelihood[ind, index] > pcutoff:
                        if color_by == "bodypart":
                            color = colors(num_bp)
                        else:
                            color = colors(num_ind)
                        if trailpoints > 0:
                            ax.scatter(
                                df_x[ind][max(0, index - trailpoints) : index],
                                df_y[ind][max(0, index - trailpoints) : index],
                                s=dotsize**2,
                                color=color,
                                alpha=alphavalue * 0.75,
                            )
                        ax.scatter(
                            df_x[ind, index],
                            df_y[ind, index],
                            s=dotsize**2,
                            color=color,
                            alpha=alphavalue,
                        )
                ax.set_xlim(0, nx)
                ax.set_ylim(0, ny)
                ax.axis("off")
                ax.invert_yaxis()
                fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)
                if save_frames:
                    fig.savefig(imagename)
                writer.grab_frame()
                ax.clear()

    print(f"Labeled video {videooutname} successfully created.")
    plt.switch_backend(prev_backend)

CropVideo

CropVideo(vname, width=256, height=256, origin_x=0, origin_y=0, outsuffix='cropped', outpath=None, useGUI=False)

Auxiliary function to crop a video and output it to the same folder with "outsuffix" appended in its name. Width and height will control the new dimensions.

ffmpeg -i in.mp4 -filter:v "crop=out_w:out_h:x:y" out.mp4

Parameters:

Name Type Description Default

vname

string

A string containing the full path of the video.

required

width

int

Width of output video.

256

height

int

Height of output video.

256

origin_x

int

X-axis origin of bounding box for cropping.

0

origin_y

int

Y-axis origin of bounding box for cropping.

0

outsuffix

str

Suffix for output videoname (see example).

'cropped'

outpath

str

Output path for saving video to (by default, same folder as the video).

None

Returns:

Name Type Description
str

The full path to the cropped video.

Examples:

Linux/MacOs:

deeplabcut.CropVideo("/data/videos/mouse1.avi")

Crops the video using default values and saves it in /data/videos as mouse1cropped.avi

Windows:

deeplabcut.CropVideo(
    "C:\yourusername\rig-95\Videos\reachingvideo1.avi",
    width=220,
    height=320,
    outsuffix="cropped",
)

Crops the video to a width of 220 and height of 320 starting at the origin (top left) and saves it in C:\yourusername\rig-95\Videos as reachingvideo1cropped.avi

Source code in deeplabcut/utils/auxfun_videos.py
def CropVideo(
    vname,
    width=256,
    height=256,
    origin_x=0,
    origin_y=0,
    outsuffix="cropped",
    outpath=None,
    useGUI=False,
):
    """Auxiliary function to crop a video and output it to the same folder with
    "outsuffix" appended in its name. Width and height will control the new dimensions.

    ffmpeg -i in.mp4 -filter:v "crop=out_w:out_h:x:y" out.mp4

    Args:
        vname (string): A string containing the full path of the video.
        width (int): Width of output video.
        height (int): Height of output video.
        origin_x (int): X-axis origin of bounding box for cropping.
        origin_y (int): Y-axis origin of bounding box for cropping.
        outsuffix (str): Suffix for output videoname (see example).
        outpath (str): Output path for saving video to (by default, same folder as the
            video).

    Returns:
        str: The full path to the cropped video.

    Examples:
        Linux/MacOs:

            deeplabcut.CropVideo("/data/videos/mouse1.avi")

        Crops the video using default values and saves it in /data/videos as
        mouse1cropped.avi

        Windows:

            deeplabcut.CropVideo(
                "C:\\yourusername\\rig-95\\Videos\\reachingvideo1.avi",
                width=220,
                height=320,
                outsuffix="cropped",
            )

        Crops the video to a width of 220 and height of 320 starting at the origin (top
        left) and saves it in C:\\yourusername\\rig-95\\Videos as reachingvideo1cropped.avi
    """
    writer = VideoWriter(vname)

    if useGUI:
        print("Please, select your coordinates (draw from top left to bottom right ...)")
        coords = draw_bbox(vname)

        if not coords:
            return
        origin_x, origin_y = coords[:2]
        width = int(coords[2]) - int(coords[0])
        height = int(coords[3]) - int(coords[1])

    writer.set_bbox(origin_x, origin_x + width, origin_y, origin_y + height)
    return writer.crop(outsuffix, outpath)

DownSampleVideo

DownSampleVideo(vname, width=-1, height=200, outsuffix='downsampled', outpath=None, rotatecw='No', angle=0.0)

Auxiliary function to downsample a video and output it to the same folder with "outsuffix" appended in its name. Width and height will control the new dimensions. You can also pass only height or width and set the other one to -1, this will keep the aspect ratio identical.

Parameters:

Name Type Description Default

vname

string

A string containing the full path of the video.

required

width

int

Width of output video.

-1

height

int

Height of output video.

200

outsuffix

str

Suffix for output videoname (see example).

'downsampled'

outpath

str

Output path for saving video to (by default, same folder as the video).

None

rotatecw

str

Default "No", rotates clockwise if "Yes", "Arbitrary" for arbitrary rotation by specified angle.

'No'

angle

float

Angle to rotate by in degrees, default 0.0. Negative values rotate counter-clockwise.

0.0

Returns:

Name Type Description
str

The full path to the downsampled video.

Examples:

Linux/MacOs:

deeplabcut.DownSampleVideo("/data/videos/mouse1.avi")

Downsamples the video using default values and saves it in /data/videos as mouse1downsampled.avi

Windows:

shortenedvideoname = deeplabcut.DownSampleVideo(
    "C:\yourusername\rig-95\Videos\reachingvideo1.avi",
    width=220,
    height=320,
    outsuffix="cropped",
)

Downsamples the video to a width of 220 and height of 320 and saves it in C:\yourusername\rig-95\Videos as reachingvideo1cropped.avi

Source code in deeplabcut/utils/auxfun_videos.py
def DownSampleVideo(
    vname,
    width=-1,
    height=200,
    outsuffix="downsampled",
    outpath=None,
    rotatecw="No",
    angle=0.0,
):
    """Auxiliary function to downsample a video and output it to the same folder with
    "outsuffix" appended in its name. Width and height will control the new dimensions.
    You can also pass only height or width and set the other one to -1, this will keep
    the aspect ratio identical.

    Args:
        vname (string): A string containing the full path of the video.
        width (int): Width of output video.
        height (int): Height of output video.
        outsuffix (str): Suffix for output videoname (see example).
        outpath (str): Output path for saving video to (by default, same folder as the
            video).
        rotatecw (str): Default "No", rotates clockwise if "Yes", "Arbitrary" for
            arbitrary rotation by specified angle.
        angle (float): Angle to rotate by in degrees, default 0.0. Negative values
            rotate counter-clockwise.

    Returns:
        str: The full path to the downsampled video.

    Examples:
        Linux/MacOs:

            deeplabcut.DownSampleVideo("/data/videos/mouse1.avi")

        Downsamples the video using default values and saves it in /data/videos as
        mouse1downsampled.avi

        Windows:

            shortenedvideoname = deeplabcut.DownSampleVideo(
                "C:\\yourusername\\rig-95\\Videos\\reachingvideo1.avi",
                width=220,
                height=320,
                outsuffix="cropped",
            )

        Downsamples the video to a width of 220 and height of 320 and saves it in
        C:\\yourusername\\rig-95\\Videos as reachingvideo1cropped.avi
    """
    writer = VideoWriter(vname)
    return writer.rescale(width, height, rotatecw, angle, outsuffix, outpath)

IntersectionofIndividualsandOnesGivenbyUser

IntersectionofIndividualsandOnesGivenbyUser(cfg, individuals)

Returns all individuals when set to 'all', otherwise all bpts that are in the intersection of comparisonbodyparts and the actual bodyparts.

Source code in deeplabcut/utils/auxfun_multianimal.py
def IntersectionofIndividualsandOnesGivenbyUser(cfg, individuals):
    """Returns all individuals when set to 'all', otherwise all bpts that are in the
    intersection of comparisonbodyparts and the actual bodyparts.
    """
    if "individuals" not in cfg:  # Not a multi-animal project...
        return [""]
    all_indivs = extractindividualsandbodyparts(cfg)[0]
    if individuals == "all":
        return all_indivs
    else:  # take only items in list that are actually bodyparts...
        return [ind for ind in individuals if ind in all_indivs]

KmeansbasedFrameselection

KmeansbasedFrameselection(
    clip, numframes2pick, start, stop, Index=None, step=1, resizewidth=30, batchsize=100, max_iter=50, color=False
)

This code downsamples the video to a width of resizewidth.

The video is extracted as a numpy array, which is then clustered with kmeans, whereby each frames is treated as a vector. Frames from different clusters are then selected for labeling. This procedure makes sure that the frames "look different", i.e. different postures etc. On large videos this code is slow.

Consider not extracting the frames from the whole video but rather set start and stop to a period around interesting behavior.

Note: this method can return fewer images than numframes2pick.

Source code in deeplabcut/utils/frameselectiontools.py
def KmeansbasedFrameselection(
    clip,
    numframes2pick,
    start,
    stop,
    Index=None,
    step=1,
    resizewidth=30,
    batchsize=100,
    max_iter=50,
    color=False,
):
    """This code downsamples the video to a width of resizewidth.

    The video is extracted as a numpy array, which is then clustered with kmeans, whereby each frames is treated as a
    vector.
    Frames from different clusters are then selected for labeling. This procedure makes sure that the frames "look
    different",
    i.e. different postures etc. On large videos this code is slow.

    Consider not extracting the frames from the whole video but rather set start and stop to a period around interesting
    behavior.

    Note: this method can return fewer images than numframes2pick.
    """
    print(
        "Kmeans-quantization based extracting of frames from",
        round(start * clip.duration, 2),
        " seconds to",
        round(stop * clip.duration, 2),
        " seconds.",
    )
    startindex = int(np.floor(clip.fps * clip.duration * start))
    stopindex = int(np.ceil(clip.fps * clip.duration * stop))

    if Index is None:
        Index = np.arange(startindex, stopindex, step)
    else:
        Index = np.array(Index)
        Index = Index[(Index > startindex) * (Index < stopindex)]  # crop to range!

    nframes = len(Index)
    if batchsize > nframes:
        batchsize = int(nframes / 2)

    if len(Index) >= numframes2pick:
        clipresized = clip.resize(width=resizewidth)
        ny, nx = clipresized.size
        frame0 = img_as_ubyte(clip.get_frame(0))
        if np.ndim(frame0) == 3:
            ncolors = np.shape(frame0)[2]
        else:
            ncolors = 1
        print("Extracting and downsampling...", nframes, " frames from the video.")

        if color and ncolors > 1:
            DATA = np.zeros((nframes, nx * 3, ny))
            for counter, index in tqdm(enumerate(Index)):
                image = img_as_ubyte(clipresized.get_frame(index * 1.0 / clipresized.fps))
                DATA[counter, :, :] = np.vstack([image[:, :, 0], image[:, :, 1], image[:, :, 2]])
        else:
            DATA = np.zeros((nframes, nx, ny))
            for counter, index in tqdm(enumerate(Index)):
                if ncolors == 1:
                    DATA[counter, :, :] = img_as_ubyte(clipresized.get_frame(index * 1.0 / clipresized.fps))
                else:  # attention: averages over color channels to keep size small
                    # / perhaps you want to use color information?
                    DATA[counter, :, :] = img_as_ubyte(
                        np.array(
                            np.mean(clipresized.get_frame(index * 1.0 / clipresized.fps), 2),
                            dtype=np.uint8,
                        )
                    )

        print("Kmeans clustering ... (this might take a while)")
        data = DATA - DATA.mean(axis=0)
        data = data.reshape(nframes, -1)  # stacking

        kmeans = MiniBatchKMeans(n_clusters=numframes2pick, tol=1e-3, batch_size=batchsize, max_iter=max_iter)
        kmeans.fit(data)
        frames2pick = []
        for clusterid in range(numframes2pick):  # pick one frame per cluster
            clusterids = np.where(clusterid == kmeans.labels_)[0]

            numimagesofcluster = len(clusterids)
            if numimagesofcluster > 0:
                frames2pick.append(Index[clusterids[np.random.randint(numimagesofcluster)]])

        clipresized.close()
        del clipresized
        return list(np.array(frames2pick))
    else:
        return list(Index)

KmeansbasedFrameselectioncv2

KmeansbasedFrameselectioncv2(
    cap, numframes2pick, start, stop, Index=None, step=1, resizewidth=30, batchsize=100, max_iter=50, color=False
)

This code downsamples the video to a width of resizewidth. The video is extracted as a numpy array, which is then clustered with kmeans, whereby each frames is treated as a vector. Frames from different clusters are then selected for labeling. This procedure makes sure that the frames "look different", i.e. different postures etc. On large videos this code is slow.

Consider not extracting the frames from the whole video but rather set start and stop to a period around interesting behavior.

Note: this method can return fewer images than numframes2pick.

Attention: the flow of commands was not optimized for readability, but rather speed. This is why it might appear tedious and repetitive.

Source code in deeplabcut/utils/frameselectiontools.py
def KmeansbasedFrameselectioncv2(
    cap,
    numframes2pick,
    start,
    stop,
    Index=None,
    step=1,
    resizewidth=30,
    batchsize=100,
    max_iter=50,
    color=False,
):
    """This code downsamples the video to a width of resizewidth. The video is extracted
    as a numpy array, which is then clustered with kmeans, whereby each frames is
    treated as a vector. Frames from different clusters are then selected for labeling.
    This procedure makes sure that the frames "look different", i.e. different postures
    etc. On large videos this code is slow.

    Consider not extracting the frames from the whole video but rather set start and stop to a period around interesting
    behavior.

    Note: this method can return fewer images than numframes2pick.

    Attention: the flow of commands was not optimized for readability, but rather speed. This is why it might appear
    tedious and repetitive.
    """
    nframes = len(cap)
    nx, ny = cap.dimensions
    ratio = resizewidth * 1.0 / nx
    if ratio > 1:
        raise Exception("Choice of resizewidth actually upsamples!")

    print(
        "Kmeans-quantization based extracting of frames from",
        round(start * nframes * 1.0 / cap.fps, 2),
        " seconds to",
        round(stop * nframes * 1.0 / cap.fps, 2),
        " seconds.",
    )
    startindex = int(np.floor(nframes * start))
    stopindex = int(np.ceil(nframes * stop))

    if Index is None:
        Index = np.arange(startindex, stopindex, step)
    else:
        Index = np.array(Index)
        Index = Index[(Index > startindex) * (Index < stopindex)]  # crop to range!

    nframes = len(Index)
    if batchsize > nframes:
        batchsize = nframes // 2

    ny_ = np.round(ny * ratio).astype(int)
    nx_ = np.round(nx * ratio).astype(int)
    DATA = np.empty((nframes, ny_, nx_ * 3 if color else nx_))
    if len(Index) >= numframes2pick:
        if (
            np.mean(np.diff(Index)) > 1
        ):  # then non-consecutive indices are present, thus cap.set is required (which slows everything down!)
            print("Extracting and downsampling...", nframes, " frames from the video.")
            if color:
                for counter, index in tqdm(enumerate(Index)):
                    cap.set_to_frame(index)  # extract a particular frame
                    frame = cap.read_frame(crop=True)
                    if frame is not None:
                        image = img_as_ubyte(
                            cv2.resize(
                                frame,
                                None,
                                fx=ratio,
                                fy=ratio,
                                interpolation=cv2.INTER_NEAREST,
                            )
                        )  # color trafo not necessary; lack thereof improves speed.
                        DATA[counter, :, :] = np.hstack([image[:, :, 0], image[:, :, 1], image[:, :, 2]])
            else:
                for counter, index in tqdm(enumerate(Index)):
                    cap.set_to_frame(index)  # extract a particular frame
                    frame = cap.read_frame(crop=True)
                    if frame is not None:
                        image = img_as_ubyte(
                            cv2.resize(
                                frame,
                                None,
                                fx=ratio,
                                fy=ratio,
                                interpolation=cv2.INTER_NEAREST,
                            )
                        )  # color trafo not necessary; lack thereof improves speed.
                        DATA[counter, :, :] = np.mean(image, 2)
        else:
            print("Extracting and downsampling...", nframes, " frames from the video.")
            if color:
                for counter, index in tqdm(enumerate(Index)):
                    frame = cap.read_frame(crop=True)
                    if frame is not None:
                        image = img_as_ubyte(
                            cv2.resize(
                                frame,
                                None,
                                fx=ratio,
                                fy=ratio,
                                interpolation=cv2.INTER_NEAREST,
                            )
                        )  # color trafo not necessary; lack thereof improves speed.
                        DATA[counter, :, :] = np.hstack([image[:, :, 0], image[:, :, 1], image[:, :, 2]])
            else:
                for counter, index in tqdm(enumerate(Index)):
                    frame = cap.read_frame(crop=True)
                    if frame is not None:
                        image = img_as_ubyte(
                            cv2.resize(
                                frame,
                                None,
                                fx=ratio,
                                fy=ratio,
                                interpolation=cv2.INTER_NEAREST,
                            )
                        )  # color trafo not necessary; lack thereof improves speed.
                        DATA[counter, :, :] = np.mean(image, 2)

        print("Kmeans clustering ... (this might take a while)")
        data = DATA - DATA.mean(axis=0)
        data = data.reshape(nframes, -1)  # stacking

        kmeans = MiniBatchKMeans(n_clusters=numframes2pick, tol=1e-3, batch_size=batchsize, max_iter=max_iter)
        kmeans.fit(data)
        frames2pick = []
        for clusterid in range(numframes2pick):  # pick one frame per cluster
            clusterids = np.where(clusterid == kmeans.labels_)[0]

            numimagesofcluster = len(clusterids)
            if numimagesofcluster > 0:
                frames2pick.append(Index[clusterids[np.random.randint(numimagesofcluster)]])
        # cap.release() >> still used in frame_extraction!
        return list(np.array(frames2pick))
    else:
        return list(Index)

LoadFullMultiAnimalData

LoadFullMultiAnimalData(dataname)

Load predicted data and metadata from pickle files created by predict_videos.py.

Source code in deeplabcut/utils/auxfun_multianimal.py
def LoadFullMultiAnimalData(dataname):
    """Load predicted data and metadata from pickle files created by predict_videos.py."""
    dataname = Path(dataname)
    data_file = dataname.with_name(dataname.stem + "_full.pickle")
    metadata_file = dataname.with_name(dataname.stem + "_meta.pickle")
    try:
        with data_file.open("rb") as handle:
            data = pickle.load(handle)
    except (pickle.UnpicklingError, FileNotFoundError):
        data = shelve.open(data_file, flag="r")
    with metadata_file.open("rb") as handle:
        metadata = pickle.load(handle)
    return data, metadata

PlottingResults

PlottingResults(
    tmpfolder,
    Dataframe,
    cfg,
    bodyparts2plot,
    individuals2plot,
    showfigures=False,
    suffix=".png",
    resolution=100,
    linewidth=1.0,
)

Plots poses vs time; pose x vs pose y; histogram of differences and likelihoods.

Source code in deeplabcut/utils/plotting.py
def PlottingResults(
    tmpfolder,
    Dataframe,
    cfg,
    bodyparts2plot,
    individuals2plot,
    showfigures=False,
    suffix=".png",
    resolution=100,
    linewidth=1.0,
):
    """Plots poses vs time; pose x vs pose y; histogram of differences and
    likelihoods.
    """
    pcutoff = cfg["pcutoff"]
    colors = visualization.get_cmap(len(bodyparts2plot), name=cfg["colormap"])
    alphavalue = cfg["alphavalue"]
    if individuals2plot:
        Dataframe = Dataframe.loc(axis=1)[:, individuals2plot]
    animal_bpts = Dataframe.columns.get_level_values("bodyparts")
    # Close previous figures before plotting
    plt.close("all")

    # Pose X vs pose Y
    fig1 = plt.figure(figsize=(8, 6))
    ax1 = fig1.add_subplot(111)
    ax1.set_xlabel("X position in pixels")
    ax1.set_ylabel("Y position in pixels")
    ax1.invert_yaxis()

    # Poses vs time
    fig2 = plt.figure(figsize=(10, 3))
    ax2 = fig2.add_subplot(111)
    ax2.set_xlabel("Frame Index")
    ax2.set_ylabel("X-(dashed) and Y- (solid) position in pixels")

    # Likelihoods
    fig3 = plt.figure(figsize=(10, 3))
    ax3 = fig3.add_subplot(111)
    ax3.set_xlabel("Frame Index")
    ax3.set_ylabel("Likelihood (use to set pcutoff)")

    # Histograms
    fig4 = plt.figure()
    ax4 = fig4.add_subplot(111)
    ax4.set_ylabel("Count")
    ax4.set_xlabel("DeltaX and DeltaY")
    bins = np.linspace(0, np.amax(Dataframe.max()), 100)

    with np.errstate(invalid="ignore"):
        for bpindex, bp in enumerate(bodyparts2plot):
            if bp in animal_bpts:  # Avoid 'unique' bodyparts only present in the 'single' animal
                prob = Dataframe.xs((bp, "likelihood"), level=(-2, -1), axis=1).values.squeeze()
                mask = prob < pcutoff
                temp_x = np.ma.array(
                    Dataframe.xs((bp, "x"), level=(-2, -1), axis=1).values.squeeze(),
                    mask=mask,
                )
                temp_y = np.ma.array(
                    Dataframe.xs((bp, "y"), level=(-2, -1), axis=1).values.squeeze(),
                    mask=mask,
                )
                ax1.plot(temp_x, temp_y, ".", color=colors(bpindex), alpha=alphavalue)

                ax2.plot(
                    temp_x,
                    "--",
                    color=colors(bpindex),
                    linewidth=linewidth,
                    alpha=alphavalue,
                )
                ax2.plot(
                    temp_y,
                    "-",
                    color=colors(bpindex),
                    linewidth=linewidth,
                    alpha=alphavalue,
                )

                ax3.plot(
                    prob,
                    "-",
                    color=colors(bpindex),
                    linewidth=linewidth,
                    alpha=alphavalue,
                )

                Histogram(temp_x, colors(bpindex), bins, ax4, linewidth=linewidth)
                Histogram(temp_y, colors(bpindex), bins, ax4, linewidth=linewidth)

    sm = plt.cm.ScalarMappable(
        cmap=plt.get_cmap(cfg["colormap"]),
        norm=plt.Normalize(vmin=0, vmax=len(bodyparts2plot) - 1),
    )
    sm._A = []
    for ax in ax1, ax2, ax3, ax4:
        cbar = plt.colorbar(sm, ax=ax, ticks=range(len(bodyparts2plot)))
        cbar.set_ticklabels(bodyparts2plot)

    fig1.savefig(
        Path(tmpfolder) / ("trajectory" + suffix),
        bbox_inches="tight",
        dpi=resolution,
    )
    fig2.savefig(Path(tmpfolder) / ("plot" + suffix), bbox_inches="tight", dpi=resolution)
    fig3.savefig(
        Path(tmpfolder) / ("plot-likelihood" + suffix),
        bbox_inches="tight",
        dpi=resolution,
    )
    fig4.savefig(Path(tmpfolder) / ("hist" + suffix), bbox_inches="tight", dpi=resolution)

    if showfigures:
        plt.show()

SaveFullMultiAnimalData

SaveFullMultiAnimalData(data, metadata, dataname, suffix='_full')

Save predicted data as h5 file and metadata as pickle file; created by predict_videos.py.

Source code in deeplabcut/utils/auxfun_multianimal.py
def SaveFullMultiAnimalData(data, metadata, dataname, suffix="_full"):
    """Save predicted data as h5 file and metadata as pickle file; created by
    predict_videos.py.
    """
    dataname = Path(dataname)
    data_path = dataname.with_name(dataname.stem + suffix + ".pickle")
    metadata_path = dataname.with_name(dataname.stem + "_meta.pickle")

    data_path.parent.mkdir(parents=True, exist_ok=True)
    with data_path.open("wb") as f:
        pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
    with metadata_path.open("wb") as f:
        pickle.dump(metadata, f, pickle.HIGHEST_PROTOCOL)
    return data_path, metadata_path

ShortenVideo

ShortenVideo(vname, start='00:00:01', stop='00:01:00', outsuffix='short', outpath=None)

Auxiliary function to shorten video and output with outsuffix appended to the same folder from start (hours:minutes:seconds) to stop (hours:minutes:seconds).

Parameters:

Name Type Description Default

vname

string

A string containing the full path of the video.

required

start

str

Time formatted in hours:minutes:seconds, where shortened video shall start.

'00:00:01'

stop

str

Time formatted in hours:minutes:seconds, where shortened video shall end.

'00:01:00'

outsuffix

str

Suffix for output videoname (see example).

'short'

outpath

str

Output path for saving video to (by default, same folder as the video).

None

Returns:

Name Type Description
str

The full path to the shortened video.

Examples:

Linux/MacOs:

deeplabcut.ShortenVideo("/data/videos/mouse1.avi")

Extracts (sub)video from 1st second to 1st minutes (default values) and saves it in /data/videos as mouse1short.avi

Windows:

deeplabcut.ShortenVideo(
    "C:\yourusername\rig-95\Videos\reachingvideo1.avi",
    start="00:17:00",
    stop="00:22:00",
    outsuffix="brief",
)

Extracts (sub)video from minute 17 to 22 and saves it in C:\yourusername\rig-95\Videos as reachingvideo1brief.avi

Source code in deeplabcut/utils/auxfun_videos.py
def ShortenVideo(vname, start="00:00:01", stop="00:01:00", outsuffix="short", outpath=None):
    """Auxiliary function to shorten video and output with outsuffix appended to the
    same folder from start (hours:minutes:seconds) to stop (hours:minutes:seconds).

    Args:
        vname (string): A string containing the full path of the video.
        start (str): Time formatted in hours:minutes:seconds, where shortened video shall
            start.
        stop (str): Time formatted in hours:minutes:seconds, where shortened video shall
            end.
        outsuffix (str): Suffix for output videoname (see example).
        outpath (str): Output path for saving video to (by default, same folder as the
            video).

    Returns:
        str: The full path to the shortened video.

    Examples:
        Linux/MacOs:

            deeplabcut.ShortenVideo("/data/videos/mouse1.avi")

        Extracts (sub)video from 1st second to 1st minutes (default values) and saves
        it in /data/videos as mouse1short.avi

        Windows:

            deeplabcut.ShortenVideo(
                "C:\\yourusername\\rig-95\\Videos\\reachingvideo1.avi",
                start="00:17:00",
                stop="00:22:00",
                outsuffix="brief",
            )

        Extracts (sub)video from minute 17 to 22 and saves it in
        C:\\yourusername\\rig-95\\Videos as reachingvideo1brief.avi
    """
    writer = VideoWriter(vname)
    return writer.shorten(start, stop, outsuffix, outpath)

UniformFrames

UniformFrames(clip, numframes2pick, start, stop, Index=None)

Temporally uniformly sampling frames in interval (start,stop). Visual information of video is irrelevant for this method. This code is fast and sufficient (to extract distinct frames), when behavioral videos naturally covers many states.

The variable Index allows to pass on a subindex for the frames.

Source code in deeplabcut/utils/frameselectiontools.py
def UniformFrames(clip, numframes2pick, start, stop, Index=None):
    """Temporally uniformly sampling frames in interval (start,stop). Visual information
    of video is irrelevant for this method. This code is fast and sufficient (to extract
    distinct frames), when behavioral videos naturally covers many states.

    The variable Index allows to pass on a subindex for the frames.
    """
    print(
        "Uniformly extracting of frames from",
        round(start * clip.duration, 2),
        " seconds to",
        round(stop * clip.duration, 2),
        " seconds.",
    )
    if Index is None:
        if start == 0:
            frames2pick = np.random.choice(
                math.ceil(clip.duration * clip.fps * stop),
                size=numframes2pick,
                replace=False,
            )
        else:
            frames2pick = np.random.choice(
                range(
                    math.floor(start * clip.duration * clip.fps),
                    math.ceil(clip.duration * clip.fps * stop),
                ),
                size=numframes2pick,
                replace=False,
            )
        return frames2pick
    else:
        startindex = int(np.floor(clip.fps * clip.duration * start))
        stopindex = int(np.ceil(clip.fps * clip.duration * stop))
        Index = np.array(Index, dtype=int)
        Index = Index[(Index > startindex) * (Index < stopindex)]  # crop to range!
        if len(Index) >= numframes2pick:
            return list(np.random.permutation(Index)[:numframes2pick])
        else:
            return list(Index)

UniformFramescv2

UniformFramescv2(cap, numframes2pick, start, stop, Index=None)

Temporally uniformly sampling frames in interval (start,stop). Visual information of video is irrelevant for this method. This code is fast and sufficient (to extract distinct frames), when behavioral videos naturally covers many states.

The variable Index allows to pass on a subindex for the frames.

Source code in deeplabcut/utils/frameselectiontools.py
def UniformFramescv2(cap, numframes2pick, start, stop, Index=None):
    """Temporally uniformly sampling frames in interval (start,stop). Visual information
    of video is irrelevant for this method. This code is fast and sufficient (to extract
    distinct frames), when behavioral videos naturally covers many states.

    The variable Index allows to pass on a subindex for the frames.
    """
    nframes = len(cap)
    print(
        "Uniformly extracting of frames from",
        round(start * nframes * 1.0 / cap.fps, 2),
        " seconds to",
        round(stop * nframes * 1.0 / cap.fps, 2),
        " seconds.",
    )

    if Index is None:
        if start == 0:
            frames2pick = np.random.choice(math.ceil(nframes * stop), size=numframes2pick, replace=False)
        else:
            frames2pick = np.random.choice(
                range(math.floor(nframes * start), math.ceil(nframes * stop)),
                size=numframes2pick,
                replace=False,
            )
        return frames2pick
    else:
        startindex = int(np.floor(nframes * start))
        stopindex = int(np.ceil(nframes * stop))
        Index = np.array(Index, dtype=int)
        Index = Index[(Index > startindex) * (Index < stopindex)]  # crop to range!
        if len(Index) >= numframes2pick:
            return list(np.random.permutation(Index)[:numframes2pick])
        else:
            return list(Index)

adapt_labeled_data_to_new_project

adapt_labeled_data_to_new_project(
    config_path: str | Path, remove_old_bodyparts=False, other_scorer=False, userfeedback=False
)

Given the config.yaml file, this function will convert the labels of an ancient project to a new project. For this, the labeled data must be in the project folder, under the labeled-data folder and with the same configuration as all deeplabcut projects.

Parameters:

Name Type Description Default

config_path

str

The path to the config.yaml file.

required

remove_old_bodyparts

bool

If True, old bodyparts not in the new project are removed from the dataframe. Defaults to False.

False

other_scorer

bool

If True, the labels will be converted to the new scorer. Defaults to False.

False

userfeedback

bool

If true the user will be asked specifically for each folder in labeled-data if the containing csv shall be converted to hdf format. Defaults to True.

False
Source code in deeplabcut/utils/conversioncode.py
def adapt_labeled_data_to_new_project(
    config_path: str | Path,
    remove_old_bodyparts=False,
    other_scorer=False,
    userfeedback=False,
):
    """Given the config.yaml file, this function will convert the labels of an ancient
    project to a new project. For this, the labeled data must be in the project folder,
    under the labeled-data folder and with the same configuration as all deeplabcut
    projects.

    Args:
        config_path (str): The path to the config.yaml file.
        remove_old_bodyparts (bool): If True, old bodyparts not in the new project are
            removed from the dataframe. Defaults to False.
        other_scorer (bool): If True, the labels will be converted to the new scorer. Defaults to False.
        userfeedback (bool): If true the user will be asked specifically
            for each folder in labeled-data if the containing csv
            shall be converted to hdf format. Defaults to True.
    """
    # Load the config file
    cfg = dlc.auxiliaryfunctions.read_config(config_path)

    # Get the Project path
    project_path = cfg["project_path"]

    # Get the bodyparts
    bodyparts = cfg["multianimalbodyparts"]
    print("New Bodyparts:", bodyparts)

    # Iterate over each labeled data video

    # Use tqdm for a progress bar
    for video in tqdm.tqdm(cfg["video_sets"]):
        print("Video:", video)

        video_name = video.split("\\")[-1]
        # discard the file extension
        video_name = video_name.split(".")[0]
        # Load the csv file
        label_path = Path(project_path) / "labeled-data" / video_name
        csv_files = [f.name for f in label_path.iterdir() if f.name.endswith(".csv")]
        if not csv_files:
            print("No csv file in the folder:", label_path)
        else:
            csv_path = str(label_path / csv_files[0])
            df = pd.read_csv(csv_path, header=None)

            # get the scorer
            if other_scorer:
                scorer = cfg["scorer"]
                # Change the scorer in the dataframe
                df.iloc[0, 3:] = pd.Series([scorer] * len(df.columns[3:]))

            else:
                scorer = df.iloc[0, 3]

            # Get the individuals
            individuals = np.unique(df.iloc[1, 3:])

            # Get the old bodyparts
            old_bodyparts = np.unique(df.iloc[2, 3:])
            print("Old bodyparts:", old_bodyparts)

            # Get the unmber of old bodyparts
            num_of_old_bodyparts = len(old_bodyparts)

            # Bodyparts to add
            print("Bodyparts to add:", set(bodyparts) - set(old_bodyparts))

            # If a bodypart is missing, add it to the dataframe
            for index, bodypart in enumerate(bodyparts):
                if bodypart not in old_bodyparts:
                    num_of_old_bodyparts += 1
                    for i, individual in enumerate(individuals):
                        # create the columns for the bodypart, concatenate, the individual, the bodypart, and nan values
                        x_column = pd.concat(
                            [
                                pd.Series(scorer),
                                pd.Series(individual),
                                pd.Series(bodypart),
                                pd.Series("x"),
                                pd.Series(np.nan, index=df.index),
                            ],
                            axis=0,
                            ignore_index=True,
                        )
                        y_column = pd.concat(
                            [
                                pd.Series(scorer),
                                pd.Series(individual),
                                pd.Series(bodypart),
                                pd.Series("y"),
                                pd.Series(np.nan, index=df.index),
                            ],
                            axis=0,
                            ignore_index=True,
                        )
                        # Insert the columns in the dataframe
                        df.insert(
                            i * 2 * num_of_old_bodyparts + index * 2 + 3,
                            "insert_" + bodypart + "_x" + individual,
                            x_column,
                        )
                        df.insert(
                            i * 2 * num_of_old_bodyparts + index * 2 + 4,
                            "insert" + bodypart + "_y" + individual,
                            y_column,
                        )

            # If the old bodyparts are not in the new project, remove them
            if remove_old_bodyparts:
                for bodypart in old_bodyparts:
                    if bodypart not in bodyparts:
                        df = df.drop(df.columns[df.iloc[2, :] == bodypart], axis=1)

            # Save the dataframe
            df.to_csv(csv_path, index=False, header=False)

    # Create/Update the h5 file
    convertcsv2h5(config_path, userfeedback=userfeedback)

analyze_videos_converth5_to_csv

analyze_videos_converth5_to_csv(video_folder, videotype='.mp4', listofvideos=False)

By default the output poses (when running analyze_videos) are stored as MultiIndex Pandas Array, which contains the name of the network, body part name, (x, y) label position in pixels, and the likelihood for each frame per body part. These arrays are stored in an efficient Hierarchical Data Format (HDF) in the same directory, where the video is stored. This functions converts hdf (h5) files to the comma-separated values format (.csv), which in turn can be imported in many programs, such as MATLAB, R, Prism, etc.

Parameters:

Name Type Description Default

video_folder

string

Absolute path of a folder containing videos and the corresponding h5 data files.

required

videotype

string

Only videos with this extension are screened. Defaults to .mp4.

'.mp4'

Examples:

Converts all pose-output files belonging to mp4 videos in the folder '/media/alex/experimentaldata/cheetahvideos' to csv files:

deeplabcut.analyze_videos_converth5_to_csv(
    "/media/alex/experimentaldata/cheetahvideos",
    ".mp4",
)
Source code in deeplabcut/utils/conversioncode.py
def analyze_videos_converth5_to_csv(video_folder, videotype=".mp4", listofvideos=False):
    """By default the output poses (when running analyze_videos) are stored as
    MultiIndex Pandas Array, which contains the name of the network, body part name, (x,
    y) label position in pixels, and the likelihood for each frame per body part.
    These arrays are stored in an efficient Hierarchical Data Format (HDF) in the
    same directory, where the video is stored. This functions converts hdf (h5) files to
    the comma-separated values format (.csv), which in turn can be imported in many
    programs, such as MATLAB, R, Prism, etc.

    Args:
        video_folder (string): Absolute path of a folder containing videos and the corresponding h5 data files.
        videotype (string, optional): Only videos with this extension are screened. Defaults to .mp4.

    Examples:
        Converts all pose-output files belonging to mp4 videos in the folder
        '/media/alex/experimentaldata/cheetahvideos' to csv files:

            deeplabcut.analyze_videos_converth5_to_csv(
                "/media/alex/experimentaldata/cheetahvideos",
                ".mp4",
            )
    """
    if listofvideos:  # can also be called with a list of videos (from GUI)
        videos = video_folder  # GUI gives a list of videos
        if len(videos) > 0:
            h5_files = collect_video_paths(Path(videos[0]).parent, extensions=".h5")
        else:
            h5_files = []
    else:
        h5_files = collect_video_paths(video_folder, extensions=".h5")
        videos = collect_video_paths(video_folder, extensions=videotype)

    _convert_h5_files_to("csv", None, h5_files, videos)

analyze_videos_converth5_to_nwb

analyze_videos_converth5_to_nwb(config: str | Path, video_folder: str | Path, videotype='.mp4', listofvideos=False)

Convert all h5 output data files in video_folder to NWB format.

Parameters:

Name Type Description Default

config

string

Absolute path to the project YAML config file.

required

video_folder

string

Absolute path of a folder containing videos and the corresponding h5 data files.

required

videotype

string

Only videos with this extension are screened. Defaults to .mp4.

'.mp4'

Examples:

Converts all pose-output files belonging to mp4 videos in the folder '/media/alex/experimentaldata/cheetahvideos' to NWB files:

deeplabcut.analyze_videos_converth5_to_nwb(
    config,
    "/media/alex/experimentaldata/cheetahvideos",
    ".mp4",
)
Source code in deeplabcut/utils/conversioncode.py
def analyze_videos_converth5_to_nwb(
    config: str | Path,
    video_folder: str | Path,
    videotype=".mp4",
    listofvideos=False,
):
    """Convert all h5 output data files in `video_folder` to NWB format.

    Args:
        config (string): Absolute path to the project YAML config file.
        video_folder (string): Absolute path of a folder containing videos and the corresponding h5 data files.
        videotype (string, optional): Only videos with this extension are screened. Defaults to .mp4.

    Examples:
        Converts all pose-output files belonging to mp4 videos in the folder
        '/media/alex/experimentaldata/cheetahvideos' to NWB files:

            deeplabcut.analyze_videos_converth5_to_nwb(
                config,
                "/media/alex/experimentaldata/cheetahvideos",
                ".mp4",
            )
    """
    if listofvideos:  # can also be called with a list of videos (from GUI)
        videos = video_folder  # GUI gives a list of videos
        if len(videos) > 0:
            h5_files = collect_video_paths(Path(videos[0]).parent, extensions=".h5")
        else:
            h5_files = []
    else:
        h5_files = collect_video_paths(video_folder, extensions=".h5")
        videos = collect_video_paths(video_folder, extensions=videotype)

    _convert_h5_files_to("nwb", config, h5_files, videos)

attempt_to_make_folder

attempt_to_make_folder(foldername, recursive=False)

Attempts to create a folder with specified name.

Does nothing if it already exists.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def attempt_to_make_folder(foldername, recursive=False):
    """Attempts to create a folder with specified name.

    Does nothing if it already exists.
    """
    foldername = Path(foldername)

    if foldername.is_dir():
        return

    if recursive:
        foldername.mkdir(parents=True, exist_ok=True)
    else:
        foldername.mkdir(exist_ok=True, parents=True)

check_if_post_processing

check_if_post_processing(folder, vname, DLCscorer, DLCscorerlegacy, suffix='filtered')

Checks if filtered/bone lengths were already calculated.

If not, figures out if data was already analyzed (either with legacy scorer name or new one!)

Source code in deeplabcut/utils/auxiliaryfunctions.py
def check_if_post_processing(folder, vname, DLCscorer, DLCscorerlegacy, suffix="filtered"):
    """Checks if filtered/bone lengths were already calculated.

    If not, figures out if data was already analyzed (either with legacy scorer name or
    new one!)
    """
    folder = Path(folder)
    outdataname = str(folder / (vname + DLCscorer + suffix + ".h5"))
    sourcedataname = str(folder / (vname + DLCscorer + ".h5"))
    if Path(outdataname).is_file():  # was data already processed?
        if suffix == "filtered":
            print("Video already filtered...", outdataname)
        elif suffix == "_skeleton":
            print("Skeleton in video already processed...", outdataname)

        return False, outdataname, sourcedataname, DLCscorer
    else:
        odn = str(folder / (vname + DLCscorerlegacy + suffix + ".h5"))
        if Path(odn).is_file():  # was it processed by DLC <2.1 project?
            if suffix == "filtered":
                print("Video already filtered...(with DLC<2.1)!", odn)
            elif suffix == "_skeleton":
                print("Skeleton in video already processed... (with DLC<2.1)!", odn)
            return False, odn, odn, DLCscorerlegacy
        else:
            sdn = str(folder / (vname + DLCscorerlegacy + ".h5"))
            tracks = sourcedataname.replace(".h5", "tracks.h5")
            if Path(sourcedataname).is_file():  # Was the video already analyzed?
                return True, outdataname, sourcedataname, DLCscorer
            elif Path(sdn).is_file():  # was it analyzed with DLC<2.1?
                return True, odn, sdn, DLCscorerlegacy
            elif Path(tracks).is_file():  # May be a MA project with tracklets
                return True, tracks.replace(".h5", f"{suffix}.h5"), tracks, DLCscorer
            else:
                print("Video not analyzed -- Run analyze_videos first.")
                return False, outdataname, sourcedataname, DLCscorer

collect_video_paths

collect_video_paths(
    data_path: str | Path | list[str | Path],
    extensions: str | Sequence[str] | None = None,
    shuffle: bool = False,
    exclude_patterns: Sequence[str] = DEFAULT_EXCLUDE_PATTERNS,
) -> list[Path]

Collects video paths from a given set of data paths: directories, files, or a mix of both. Directories are scanned one level deep (non-recursively).

Files and directories are treated differently with respect to extension filtering: - File paths are accepted as-is when extensions is None; only filtered when extensions is explicitly set. - Directory contents are always filtered by extension: by SUPPORTED_VIDEOS when extensions is None, or by the given value(s) otherwise. - exclude_patterns are always applied to both files and directory contents.

Parameters:

Name Type Description Default

data_path

str | Path | list[str | Path]

Path or list of paths to folders containing videos, or individual video files. Can be a mix of directories and files.

required

extensions

str | Sequence[str] | None

Controls extension filtering for collected video files. - None (default): file paths are accepted without extension filtering; directories are scanned for files with a recognized video extension. - str or Sequence[str] (e.g. "mp4" or ["mp4", "avi"]): both file paths and directory contents are filtered to only include files matching the given extension(s). - Empty str "" is treated as None (deprecated, keep for backwards compatibility).

None

shuffle

bool

Whether to shuffle the order of videos. If False, videos are returned in sorted order for deterministic behavior.

False

exclude_patterns

Sequence[str]

Patterns to exclude from the collection. Defaults to DEFAULT_EXCLUDE_PATTERNS. Set to [] to disable pattern exclusion.

DEFAULT_EXCLUDE_PATTERNS

Returns:

Type Description
list[Path]

The paths of videos to analyze. Duplicate paths are removed.

Raises:

Type Description
FileNotFoundError

If any path in data_path does not exist.

ValueError

If extensions is an empty sequence.

Source code in deeplabcut/utils/auxfun_videos.py
def collect_video_paths(
    data_path: str | Path | list[str | Path],
    extensions: str | Sequence[str] | None = None,
    shuffle: bool = False,
    exclude_patterns: Sequence[str] = DEFAULT_EXCLUDE_PATTERNS,
) -> list[Path]:
    """
    Collects video paths from a given set of data paths: directories, files, or a mix
    of both. Directories are scanned one level deep (non-recursively).

    Files and directories are treated differently with respect to extension filtering:
    - File paths are accepted as-is when ``extensions`` is ``None``; only filtered when
      ``extensions`` is explicitly set.
    - Directory contents are always filtered by extension: by ``SUPPORTED_VIDEOS`` when
      ``extensions`` is ``None``, or by the given value(s) otherwise.
    - ``exclude_patterns`` are always applied to both files and directory contents.

    Args:
        data_path: Path or list of paths to folders containing videos, or individual
            video files. Can be a mix of directories and files.
        extensions: Controls extension filtering for collected video files.
            - ``None`` (default): file paths are accepted without extension filtering;
              directories are scanned for files with a recognized video extension.
            - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``):
              both file paths and directory contents are filtered to only include files
              matching the given extension(s).
            - Empty ``str`` ``""`` is treated as ``None`` (deprecated, keep for backwards
              compatibility).
        shuffle: Whether to shuffle the order of videos. If ``False``, videos are
            returned in sorted order for deterministic behavior.
        exclude_patterns: Patterns to exclude from the collection. Defaults to
            ``DEFAULT_EXCLUDE_PATTERNS``. Set to ``[]`` to disable pattern exclusion.

    Returns:
        The paths of videos to analyze. Duplicate paths are removed.

    Raises:
        FileNotFoundError: If any path in ``data_path`` does not exist.
        ValueError: If ``extensions`` is an empty sequence.
    """
    if isinstance(data_path, (str, Path)):
        data_path = [data_path]

    def _coerce_extensions(extensions: str | Sequence[str] | None) -> set[str] | None:
        """Coerce the extensions argument to a set of dot-prefixed suffixes, or None."""
        if extensions is None:
            return None

        if extensions in ["", ("",), [""], {""}]:
            warnings.warn(
                "Passing an empty string for filtering video type extensions is deprecated; pass None instead.",
                DLCDeprecationWarning,
                stacklevel=3,
            )
            return None

        if isinstance(extensions, str):
            return {f".{extensions.lstrip('.').lower()}"}

        if not isinstance(extensions, Sequence):
            raise TypeError(f"extensions must be a string, a sequence or None, got {type(extensions)}")

        if len(extensions) == 0:
            raise ValueError("Video type extensions filter needs to be a non-empty sequence.")
        return {f".{e.lstrip('.').lower()}" for e in extensions}

    explicit_suffixes = _coerce_extensions(extensions)
    implicit_suffixes = {f".{ext.lower()}" for ext in SUPPORTED_VIDEOS}

    videos: list[Path] = []
    for path in map(Path, data_path):
        if not path.exists():
            raise FileNotFoundError(f"Could not find: {path}. Check access rights.")

        if path.is_dir():
            # Discriminate videos from other files; skip excluded patterns (e.g. prior DLC outputs).
            allowed = explicit_suffixes if explicit_suffixes else implicit_suffixes
            videos.extend(
                f
                for f in path.iterdir()
                if f.is_file()
                and f.suffix.lower() in allowed
                and not any(f.match(pattern) for pattern in exclude_patterns)
            )
        elif path.is_file():
            # Accept all caller-supplied files; ONLY filter extensions if set. ALWAYS filter exclude patterns.
            if explicit_suffixes is None or path.suffix.lower() in explicit_suffixes:
                if not any(path.match(pattern) for pattern in exclude_patterns):
                    videos.append(path)

    # Resolve video paths and remove duplicates
    unique_videos = list(dict.fromkeys(v.absolute() for v in videos))
    if shuffle:
        random.shuffle(unique_videos)
    else:
        unique_videos.sort()

    if any(fn.suffix.lower().lstrip(".") not in SUPPORTED_VIDEOS for fn in unique_videos if fn.suffix):
        warnings.warn(
            f"Some videos have unsupported extensions: {unique_videos} \nSupported extensions are: {SUPPORTED_VIDEOS}",
            stacklevel=2,
        )
    return unique_videos

convert2_maDLC

convert2_maDLC(config: str | Path, userfeedback=True, forceindividual=None)

Convert a single-animal annotation file into a multianimal annotation file.

Introduces an individuals column with either the first individual in individuals list in config.yaml or whatever is passed via "forceindividual".

Parameters:

Name Type Description Default

config

str | Path

Full path of the config.yaml file as a string.

required

userfeedback

bool

If false, all folders are processed without prompting. If true, the user is asked for each folder whether to convert. Use this, e.g. if you have already labeled some folders and want to convert data for new videos only.

True

forceindividual

str | None

If a string is given, that value is used in the individuals column. Defaults to None.

None

Examples:

Convert multianimalbodyparts under the 'first individual' in individuals list in config.yaml and uniquebodyparts under 'single':

deeplabcut.convert2_maDLC("/socialrearing-task/config.yaml")

Convert multianimalbodyparts under the individual label mus17 and uniquebodyparts under 'single':

deeplabcut.convert2_maDLC("/socialrearing-task/config.yaml", forceindividual="mus17")
Source code in deeplabcut/utils/auxfun_multianimal.py
def convert2_maDLC(config: str | Path, userfeedback=True, forceindividual=None):
    """Convert a single-animal annotation file into a multianimal annotation file.

    Introduces an individuals column with either the first individual
    in individuals list in config.yaml or whatever is passed via "forceindividual".

    Args:
        config (str | Path): Full path of the config.yaml file as a string.
        userfeedback (bool, optional): If false, all folders are processed without prompting.
            If true, the user is asked for each folder whether to convert. Use this, e.g. if you have already labeled
            some folders and want to convert data for new videos only.
        forceindividual (str | None, optional): If a string is given, that value is used
            in the individuals column. Defaults to None.

    Examples:
        Convert multianimalbodyparts under the 'first individual' in individuals list in
        `config.yaml` and uniquebodyparts under 'single':

            deeplabcut.convert2_maDLC("/socialrearing-task/config.yaml")

        Convert multianimalbodyparts under the individual label mus17 and uniquebodyparts
        under 'single':

            deeplabcut.convert2_maDLC("/socialrearing-task/config.yaml", forceindividual="mus17")
    """
    cfg = auxiliaryfunctions.read_config(config)
    videos = cfg["video_sets"].keys()
    video_names = [Path(i).stem for i in videos]
    folders = [Path(config).parent / "labeled-data" / Path(i) for i in video_names]

    individuals, uniquebodyparts, multianimalbodyparts = extractindividualsandbodyparts(cfg)

    if forceindividual is None:
        if len(individuals) == 0:
            print("At least one individual should exist...")
            folders = []
            forceindividual = ""
        else:
            forceindividual = individuals[0]  # note that single is added at then end!

        if forceindividual == "single":  # no specific individual ()
            if len(multianimalbodyparts) > 0:  # there should be an individual name...
                print("At least one individual should exist beyond 'single', as there are multianimalbodyparts...")
                folders = []

    for folder in folders:
        if userfeedback:
            print("Do you want to convert the annotation file in folder:", folder, "?")
            askuser = input("yes/no")
        else:
            askuser = "yes"

        if askuser == "y" or askuser == "yes" or askuser == "Ja" or askuser == "ha":  # multilanguage support :)
            fn = folder / ("CollectedData_" + cfg["scorer"])
            Data = pd.read_hdf(fn.with_suffix(".h5"))
            conversioncode.guarantee_multiindex_rows(Data)
            imindex = Data.index

            print("This is a single animal data set, converting to multi...", folder)

            # -> adding (single,bpt) for uniquebodyparts
            for j, bpt in enumerate(uniquebodyparts):
                index = pd.MultiIndex.from_arrays(
                    np.array([2 * [cfg["scorer"]], 2 * ["single"], 2 * [bpt], ["x", "y"]]),
                    names=["scorer", "individuals", "bodyparts", "coords"],
                )

                if bpt in Data[cfg["scorer"]].keys():
                    frame = pd.DataFrame(Data[cfg["scorer"]][bpt].values, columns=index, index=imindex)
                else:
                    frame = pd.DataFrame(
                        np.ones((len(imindex), 2)) * np.nan,
                        columns=index,
                        index=imindex,
                    )

                if j == 0:
                    dataFrame = frame
                else:
                    dataFrame = pd.concat([dataFrame, frame], axis=1)

            if len(uniquebodyparts) == 0:
                dataFrame = None

            # -> adding (individual,bpt) for multianimalbodyparts
            for j, bpt in enumerate(multianimalbodyparts):
                index = pd.MultiIndex.from_arrays(
                    np.array(
                        [
                            2 * [cfg["scorer"]],
                            2 * [str(forceindividual)],
                            2 * [bpt],
                            ["x", "y"],
                        ]
                    ),
                    names=["scorer", "individuals", "bodyparts", "coords"],
                )

                if bpt in Data[cfg["scorer"]].keys():
                    frame = pd.DataFrame(Data[cfg["scorer"]][bpt].values, columns=index, index=imindex)
                else:
                    frame = pd.DataFrame(
                        np.ones((len(imindex), 2)) * np.nan,
                        columns=index,
                        index=imindex,
                    )

                if j == 0 and dataFrame is None:
                    dataFrame = frame
                else:
                    dataFrame = pd.concat([dataFrame, frame], axis=1)

            Data.to_hdf(
                fn.with_name(fn.name + "singleanimal.h5"),
                key="df_with_missing",
            )
            Data.to_csv(fn.with_name(fn.name + "singleanimal.csv"))

            dataFrame.to_hdf(fn.with_suffix(".h5"), key="df_with_missing")
            dataFrame.to_csv(fn.with_suffix(".csv"))

convert_single2multiplelegacyAM

convert_single2multiplelegacyAM(config, userfeedback=True, target=None)

Convert multi animal to single animal code and vice versa.

Note that by providing target='single'/'multi' this will be target!

Source code in deeplabcut/utils/auxfun_multianimal.py
def convert_single2multiplelegacyAM(config, userfeedback=True, target=None):
    """Convert multi animal to single animal code and vice versa.

    Note that by providing target='single'/'multi' this will be target!
    """
    cfg = auxiliaryfunctions.read_config(config)
    videos = cfg["video_sets"].keys()
    video_names = [Path(i).stem for i in videos]
    folders = [Path(config).parent / "labeled-data" / Path(i) for i in video_names]

    prefixes, uniquebodyparts, multianimalbodyparts = extractindividualsandbodyparts(cfg)
    for folder in folders:
        if userfeedback:
            print("Do you want to convert the annotation file in folder:", folder, "?")
            askuser = input("yes/no")
        else:
            askuser = "yes"

        if askuser == "y" or askuser == "yes" or askuser == "Ja" or askuser == "ha":  # multilanguage support :)
            fn = folder / ("CollectedData_" + cfg["scorer"])
            Data = pd.read_hdf(fn.with_suffix(".h5"))
            conversioncode.guarantee_multiindex_rows(Data)
            imindex = Data.index

            if "individuals" in Data.columns.names and (target is None or target == "single"):
                print("This is a multianimal data set, converting to single...", folder)
                for prfxindex, prefix in enumerate(prefixes):
                    if prefix == "single":
                        for j, bpt in enumerate(uniquebodyparts):
                            index = pd.MultiIndex.from_product(
                                [[cfg["scorer"]], [bpt], ["x", "y"]],
                                names=["scorer", "bodyparts", "coords"],
                            )
                            frame = pd.DataFrame(
                                Data[cfg["scorer"]][prefix][bpt].values,
                                columns=index,
                                index=imindex,
                            )
                            if j == 0:
                                dataFrame = frame
                            else:
                                dataFrame = pd.concat([dataFrame, frame], axis=1)
                    else:
                        for j, bpt in enumerate(multianimalbodyparts):
                            index = pd.MultiIndex.from_product(
                                [[cfg["scorer"]], [prefix + bpt], ["x", "y"]],
                                names=["scorer", "bodyparts", "coords"],
                            )
                            frame = pd.DataFrame(
                                Data[cfg["scorer"]][prefix][bpt].values,
                                columns=index,
                                index=imindex,
                            )
                            if j == 0:
                                dataFrame = frame
                            else:
                                dataFrame = pd.concat([dataFrame, frame], axis=1)
                    if prfxindex == 0:
                        DataFrame = dataFrame
                    else:
                        DataFrame = pd.concat([DataFrame, dataFrame], axis=1)

                Data.to_hdf(
                    fn.with_name(fn.name + "multianimal.h5"),
                    key="df_with_missing",
                )
                Data.to_csv(fn.with_name(fn.name + "multianimal.csv"))

                DataFrame.to_hdf(
                    fn.with_suffix(".h5"),
                    key="df_with_missing",
                )
                DataFrame.to_csv(fn.with_suffix(".csv"))
            elif target is None or target == "multi":
                print("This is a single animal data set, converting to multi...", folder)
                for prfxindex, prefix in enumerate(prefixes):
                    if prefix == "single":
                        if cfg["uniquebodyparts"] != [None]:
                            for j, bpt in enumerate(uniquebodyparts):
                                index = pd.MultiIndex.from_arrays(
                                    np.array(
                                        [
                                            2 * [cfg["scorer"]],
                                            2 * [prefix],
                                            2 * [bpt],
                                            ["x", "y"],
                                        ]
                                    ),
                                    names=[
                                        "scorer",
                                        "individuals",
                                        "bodyparts",
                                        "coords",
                                    ],
                                )
                                if bpt in Data[cfg["scorer"]].keys():
                                    frame = pd.DataFrame(
                                        Data[cfg["scorer"]][bpt].values,
                                        columns=index,
                                        index=imindex,
                                    )
                                else:  # fill with nans...
                                    frame = pd.DataFrame(
                                        np.ones((len(imindex), 2)) * np.nan,
                                        columns=index,
                                        index=imindex,
                                    )

                                if j == 0:
                                    dataFrame = frame
                                else:
                                    dataFrame = pd.concat([dataFrame, frame], axis=1)
                        else:
                            dataFrame = None
                    else:
                        for j, bpt in enumerate(multianimalbodyparts):
                            index = pd.MultiIndex.from_arrays(
                                np.array(
                                    [
                                        2 * [cfg["scorer"]],
                                        2 * [prefix],
                                        2 * [bpt],
                                        ["x", "y"],
                                    ]
                                ),
                                names=["scorer", "individuals", "bodyparts", "coords"],
                            )
                            if prefix + "_" + bpt in Data[cfg["scorer"]].keys():
                                frame = pd.DataFrame(
                                    Data[cfg["scorer"]][prefix + "_" + bpt].values,
                                    columns=index,
                                    index=imindex,
                                )
                            else:
                                frame = pd.DataFrame(
                                    np.ones((len(imindex), 2)) * np.nan,
                                    columns=index,
                                    index=imindex,
                                )

                            if j == 0:
                                dataFrame = frame
                            else:
                                dataFrame = pd.concat([dataFrame, frame], axis=1)
                    if prfxindex == 0:
                        DataFrame = dataFrame
                    else:
                        DataFrame = pd.concat([DataFrame, dataFrame], axis=1)

                Data.to_hdf(
                    fn.with_name(fn.name + "singleanimal.h5"),
                    key="df_with_missing",
                )
                Data.to_csv(fn.with_name(fn.name + "singleanimal.csv"))

                DataFrame.to_hdf(
                    fn.with_suffix(".h5"),
                    key="df_with_missing",
                )
                DataFrame.to_csv(fn.with_suffix(".csv"))

convertcsv2h5

convertcsv2h5(config: str | Path, userfeedback=True, scorer=None)

Convert annotation files in labeled-data from csv to h5.

Allows the user to manually edit the csv (e.g. to correct the scorer name and then convert it into hdf format). WARNING: conversion might corrupt the data.

Parameters:

Name Type Description Default

config

str | Path

Full path of the config.yaml file as a string.

required

userfeedback

bool

If true the user will be asked specifically for each folder in labeled-data if the containing csv shall be converted to hdf format.

True

scorer

string

If a string is given, then the scorer/annotator in all csv and hdf files that are changed, will be overwritten with this name.

None

Examples:

Convert csv annotation files for reaching-task project into hdf:

deeplabcut.convertcsv2h5("/analysis/project/reaching-task/config.yaml")

Convert csv annotation files for reaching-task project into hdf while changing the scorer/annotator in all annotation files to Albert:

deeplabcut.convertcsv2h5("/analysis/project/reaching-task/config.yaml", scorer="Albert")
Source code in deeplabcut/utils/conversioncode.py
def convertcsv2h5(config: str | Path, userfeedback=True, scorer=None):
    """Convert annotation files in labeled-data from csv to h5.

    Allows the user to manually edit the csv
    (e.g. to correct the scorer name and then convert it into hdf format).
    WARNING: conversion might corrupt the data.

    Args:
        config (str | Path): Full path of the config.yaml file as a string.
        userfeedback (bool, optional): If true the user will be asked specifically
            for each folder in labeled-data if the containing csv shall be converted to hdf format.
        scorer (string, optional): If a string is given, then the scorer/annotator
            in all csv and hdf files that are changed, will be overwritten with this name.

    Examples:
        Convert csv annotation files for reaching-task project into hdf:

            deeplabcut.convertcsv2h5("/analysis/project/reaching-task/config.yaml")

        Convert csv annotation files for reaching-task project into hdf while changing
        the scorer/annotator in all annotation files to Albert:

            deeplabcut.convertcsv2h5("/analysis/project/reaching-task/config.yaml", scorer="Albert")
    """
    cfg = auxiliaryfunctions.read_config(config)
    videos = cfg["video_sets"].keys()
    video_names = [Path(i).stem for i in videos]
    folders = [Path(config).parent / "labeled-data" / Path(i) for i in video_names]
    if not scorer:
        scorer = cfg["scorer"]

    for folder in folders:
        try:
            if userfeedback:
                print("Do you want to convert the csv file in folder:", folder, "?")
                askuser = input("yes/no")
            else:
                askuser = "yes"

            if askuser in ("y", "yes", "Ja", "ha", "oui"):  # multilanguage support :)
                fn = folder / ("CollectedData_" + cfg["scorer"] + ".csv")
                # Determine whether the data are single- or multi-animal without loading into memory
                # simply by checking whether 'individuals' is in the second line of the CSV.
                with fn.open() as datafile:
                    head = list(islice(datafile, 0, 5))
                if "individuals" in head[1]:
                    header = list(range(4))
                else:
                    header = list(range(3))
                if head[-1].split(",")[0] == "labeled-data":
                    index_col = [0, 1, 2]
                else:
                    index_col = 0
                data = pd.read_csv(fn, index_col=index_col, header=header)
                data.columns = data.columns.set_levels([scorer], level="scorer")
                guarantee_multiindex_rows(data)
                data.to_hdf(fn.replace(".csv", ".h5"), key="df_with_missing", mode="w")
                data.to_csv(fn)
        except FileNotFoundError:
            print("Attention:", folder, "does not appear to have labeled data!")

create_labeled_video

create_labeled_video(
    config: str | Path,
    videos: list[str | Path],
    video_extensions: str | Sequence[str] | None = None,
    shuffle: int = 1,
    trainingsetindex: int = 0,
    filtered: bool = False,
    fastmode: bool = True,
    save_frames: bool = False,
    keypoints_only: bool = False,
    Frames2plot: list[int] | None = None,
    displayedbodyparts: list[str] | str = "all",
    displayedindividuals: list[str] | str = "all",
    codec: str = "mp4v",
    outputframerate: int | None = None,
    destfolder: Path | str | None = None,
    draw_skeleton: bool = False,
    trailpoints: int = 0,
    displaycropped: bool = False,
    color_by: str = "bodypart",
    modelprefix: str = "",
    init_weights: str = "",
    track_method: str = "",
    superanimal_name: str = "",
    pcutoff: float | None = None,
    skeleton: list = None,
    skeleton_color: str = "white",
    dotsize: int = 8,
    colormap: str = "rainbow",
    alphavalue: float = 0.5,
    overwrite: bool = False,
    confidence_to_alpha: bool | Callable[[float], float] = False,
    plot_bboxes: bool = True,
    bboxes_pcutoff: float | None = None,
    max_workers: int | None = None,
    **kwargs
)

Labels the bodyparts in a video.

Make sure the video is already analyzed by the function deeplabcut.analyze_videos.

Parameters:

Name Type Description Default

config

str | Path

Full path of the config.yaml file.

required

videos

list[str | Path]

A list of strings containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored.

required

video_extensions

str | Sequence[str] | None

Controls how videos are filtered, based on file extension. File paths and directory contents are treated differently: - None (default): file paths are accepted as-is; directories are scanned for files with a recognized video extension. - str or Sequence[str] (e.g. "mp4" or ["mp4", "avi"]): both file paths and directory contents are filtered by the given extension(s). Defaults to None.

None

shuffle

int

Number of shuffles of training dataset. Defaults to 1.

1

trainingsetindex

int

Integer specifying which TrainingsetFraction to use. Note that TrainingFraction is a list in config.yaml. Defaults to 0.

0

filtered

bool

If True, plot filtered output rather than frame-by-frame predictions. Filtered version can be calculated with deeplabcut.filterpredictions. Defaults to False.

False

fastmode

bool

If True, uses openCV (much faster but less customization of video) instead of matplotlib if False. You can also "save_frames" individually or not in the matplotlib mode (if you set the "save_frames" variable accordingly). However, using matplotlib to create the frames it therefore allows much more flexible (one can set transparency of markers, crop, and easily customize). Defaults to True.

True

save_frames

bool

If True, creates each frame individual and then combines into a video. Setting this to True is relatively slow as it stores all individual frames. Defaults to False.

False

keypoints_only

bool

By default, both video frames and keypoints are visible. If True, only the keypoints are shown. These clips are an hommage to Johansson movies, see https://www.youtube.com/watch?v=1F5ICP9SYLU and of course his seminal paper: "Visual perception of biological motion and a model for its analysis" by Gunnar Johansson in Perception & Psychophysics 1973. Defaults to False.

False

Frames2plot

List[int] or None

If not None and save_frames=True, plot frames at the given indices. E.g. Frames2plot=[0,11] plots the first and 12th frame. Defaults to None.

None

displayedbodyparts

list[str] or str

Body parts plotted in the video. If all, then all body parts from config.yaml are used. If a list of strings that are a subset of the full list. E.g. ['hand','Joystick'] for the demo Reaching-Mackenzie-2018-08-30/config.yaml to select only these body parts. Defaults to "all".

'all'

displayedindividuals

list[str] or str

Individuals plotted in the video. By default, all individuals present in the config will be shown. Defaults to "all".

'all'

codec

str

Codec for labeled video. For available options, see http://www.fourcc.org/codecs.php. Note that this depends on your ffmpeg installation. Defaults to "mp4v".

'mp4v'

outputframerate

int or None

Output frame rate for labeled video (only when saving frames). If None, uses the original video rate. Defaults to None.

None

destfolder

(Path, string or None)

Destination folder used for storing analysis data. If None, the path of the video file is used. Defaults to None.

None

draw_skeleton

bool

If True adds a line connecting the body parts making a skeleton on each frame. The body parts to be connected and the color of these connecting lines are specified in the config file. Defaults to False.

False

trailpoints

int

Number of previous frames whose body parts are plotted in a frame (for displaying history). Defaults to 0.

0

displaycropped

bool

Specifies whether only cropped frame is displayed (with labels analyzed therein), or the original frame with the labels analyzed in the cropped subset. Defaults to False.

False

color_by

string

Coloring rule. By default, each bodypart is colored differently. If set to 'individual', points belonging to a single individual are colored the same. Defaults to 'bodypart'.

'bodypart'

modelprefix

str

Directory containing the deeplabcut models to use when evaluating the network. By default, the models are assumed to exist in the project folder. Defaults to "".

''

init_weights

str

Checkpoint path to the super model. Defaults to "".

''

track_method

string

Tracker used to generate the data. Empty by default (corresponding to a single animal project). For multiple animals, must be either 'box', 'skeleton', or 'ellipse' and will be taken from the config.yaml file if none is given. Defaults to "".

''

superanimal_name

str

Name of the superanimal model. Defaults to "".

''

pcutoff

float

Overrides the pcutoff set in the project configuration to plot the trajectories. Defaults to None.

None

skeleton

list

Skeleton definition for drawing. Defaults to None.

None

skeleton_color

string

Color for the skeleton. Defaults to "white".

'white'

dotsize

int

Size of label dots to use. Defaults to 8.

8

colormap

str

Colormap to use for the labels. Defaults to "rainbow".

'rainbow'

alphavalue

float

Transparency of markers. Defaults to 0.5.

0.5

overwrite

bool

If True overwrites existing labeled videos. Defaults to False.

False

confidence_to_alpha

bool | Callable[[float], float]

If False, all keypoints use alpha=1. Otherwise, a function f: [0, 1] -> [0, 1] maps score to alpha. When True, f(x) = max(0, (x - pcutoff)/(1 - pcutoff)). Defaults to False.

False

plot_bboxes

bool

If using Pytorch and in Top-Down mode, setting this to true will also plot the bounding boxes. Defaults to True.

True

bboxes_pcutoff

float

If plotting bounding boxes, this overrides the bboxes_pcutoff set in the model configuration. Defaults to None.

None

max_workers

int | None

Maximum number of processes to use for multiprocessing. Set this parameter to limit the total RAM-usage of simultaneous processes. Default: no maximum (i.e. number of spawned processes is based on the number of cores and the number of input videos).

None

kwargs

dict

Additional arguments. For torch-based shuffles, can be used to specify: - snapshot_index - detector_snapshot_index

{}

Returns:

Type Description

list[bool]: True if the video is successfully created for each item in videos.

Examples:

Create the labeled video for a single video

deeplabcut.create_labeled_video(
    '/analysis/project/reaching-task/config.yaml',
    ['/analysis/project/videos/reachingvideo1.avi'],
)

Create the labeled video for a single video and store the individual frames

deeplabcut.create_labeled_video(
    '/analysis/project/reaching-task/config.yaml',
    ['/analysis/project/videos/reachingvideo1.avi'],
    fastmode=True,
    save_frames=True,
)

Create the labeled video for multiple videos

deeplabcut.create_labeled_video(
    '/analysis/project/reaching-task/config.yaml',
    [
        '/analysis/project/videos/reachingvideo1.avi',
        '/analysis/project/videos/reachingvideo2.avi',
    ],
)

Create the labeled video for all the videos with an .avi extension in a directory.

deeplabcut.create_labeled_video(
    '/analysis/project/reaching-task/config.yaml',
    ['/analysis/project/videos/'],
)

Create the labeled video for all the videos with an .mp4 extension in a directory.

deeplabcut.create_labeled_video(
    '/analysis/project/reaching-task/config.yaml',
    ['/analysis/project/videos/'],
    video_extensions='mp4',
)
Source code in deeplabcut/utils/make_labeled_video.py
@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0")
def create_labeled_video(
    config: str | Path,
    videos: list[str | Path],
    video_extensions: str | Sequence[str] | None = None,
    shuffle: int = 1,
    trainingsetindex: int = 0,
    filtered: bool = False,
    fastmode: bool = True,
    save_frames: bool = False,
    keypoints_only: bool = False,
    Frames2plot: list[int] | None = None,
    displayedbodyparts: list[str] | str = "all",
    displayedindividuals: list[str] | str = "all",
    codec: str = "mp4v",
    outputframerate: int | None = None,
    destfolder: Path | str | None = None,
    draw_skeleton: bool = False,
    trailpoints: int = 0,
    displaycropped: bool = False,
    color_by: str = "bodypart",
    modelprefix: str = "",
    init_weights: str = "",
    track_method: str = "",
    superanimal_name: str = "",
    pcutoff: float | None = None,
    skeleton: list = None,
    skeleton_color: str = "white",
    dotsize: int = 8,
    colormap: str = "rainbow",
    alphavalue: float = 0.5,
    overwrite: bool = False,
    confidence_to_alpha: bool | Callable[[float], float] = False,
    plot_bboxes: bool = True,
    bboxes_pcutoff: float | None = None,
    max_workers: int | None = None,
    **kwargs,
):
    """Labels the bodyparts in a video.

    Make sure the video is already analyzed by the function
    ``deeplabcut.analyze_videos``.

    Args:
        config (str | Path): Full path of the config.yaml file.
        videos (list[str | Path]): A list of strings containing the full paths to videos for analysis or a path
            to the directory, where all the videos with same extension are stored.
        video_extensions (str | Sequence[str] | None, optional): Controls how ``videos`` are
            filtered, based on file extension. File paths and directory contents are
            treated differently:
            - ``None`` (default): file paths are accepted as-is; directories are
              scanned for files with a recognized video extension.
            - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``):
              both file paths and directory contents are filtered by the given
              extension(s). Defaults to None.
        shuffle (int, optional): Number of shuffles of training dataset. Defaults to 1.
        trainingsetindex (int, optional): Integer specifying which TrainingsetFraction to use.
            Note that TrainingFraction is a list in config.yaml. Defaults to 0.
        filtered (bool, optional): If True, plot filtered output rather than
            frame-by-frame predictions. Filtered version can be calculated with
            ``deeplabcut.filterpredictions``. Defaults to False.
        fastmode (bool, optional): If ``True``, uses openCV (much faster but less customization of video) instead
            of matplotlib if ``False``. You can also "save_frames" individually or not in
            the matplotlib mode (if you set the "save_frames" variable accordingly).
            However, using matplotlib to create the frames it therefore allows much more
            flexible (one can set transparency of markers, crop, and easily customize). Defaults to True.
        save_frames (bool, optional): If ``True``, creates each frame individual and then combines into a video.
            Setting this to ``True`` is relatively slow as it stores all individual frames. Defaults to False.
        keypoints_only (bool, optional): By default, both video frames and keypoints are visible. If ``True``, only the
            keypoints are shown. These clips are an hommage to Johansson movies,
            see https://www.youtube.com/watch?v=1F5ICP9SYLU and of course his seminal
            paper: "Visual perception of biological motion and a model for its analysis"
            by Gunnar Johansson in Perception & Psychophysics 1973. Defaults to False.
        Frames2plot (List[int] or None, optional): If not ``None`` and ``save_frames=True``,
            plot frames at the given indices. E.g. ``Frames2plot=[0,11]`` plots the first
            and 12th frame. Defaults to None.
        displayedbodyparts (list[str] or str, optional): Body parts plotted in the video. If ``all``, then all
            body parts from config.yaml are used. If a list of strings that are a subset of
            the full list. E.g. ['hand','Joystick'] for the demo
            Reaching-Mackenzie-2018-08-30/config.yaml to select only these body parts. Defaults to "all".
        displayedindividuals (list[str] or str, optional): Individuals plotted in the video.
            By default, all individuals present in the config will be shown. Defaults to "all".
        codec (str, optional): Codec for labeled video. For available options, see
            http://www.fourcc.org/codecs.php. Note that this depends on your ffmpeg
            installation. Defaults to "mp4v".
        outputframerate (int or None, optional): Output frame rate for labeled video (only
            when saving frames). If ``None``, uses the original video rate. Defaults to None.
        destfolder (Path, string or None, optional): Destination folder used for storing analysis data. If
            ``None``, the path of the video file is used. Defaults to None.
        draw_skeleton (bool, optional): If ``True`` adds a line connecting the body parts making a skeleton on each
            frame. The body parts to be connected and the color of these connecting lines
            are specified in the config file. Defaults to False.
        trailpoints (int, optional): Number of previous frames whose body parts are plotted in a frame
            (for displaying history). Defaults to 0.
        displaycropped (bool, optional): Specifies whether only cropped frame is displayed (with labels analyzed
            therein), or the original frame with the labels analyzed in the cropped subset. Defaults to False.
        color_by (string, optional): Coloring rule. By default, each bodypart is colored differently.
            If set to 'individual', points belonging to a single individual are colored the
            same. Defaults to 'bodypart'.
        modelprefix (str, optional): Directory containing the deeplabcut models to use when evaluating the network.
            By default, the models are assumed to exist in the project folder. Defaults to "".
        init_weights (str, optional): Checkpoint path to the super model. Defaults to "".
        track_method (string, optional): Tracker used to generate the data.
            Empty by default (corresponding to a single animal project).
            For multiple animals, must be either 'box', 'skeleton', or 'ellipse' and will
            be taken from the config.yaml file if none is given. Defaults to "".
        superanimal_name (str, optional): Name of the superanimal model. Defaults to "".
        pcutoff (float, optional): Overrides the pcutoff set in the project configuration to plot the trajectories.
            Defaults to None.
        skeleton (list, optional): Skeleton definition for drawing. Defaults to None.
        skeleton_color (string, optional): Color for the skeleton. Defaults to "white".
        dotsize (int, optional): Size of label dots to use. Defaults to 8.
        colormap (str, optional): Colormap to use for the labels. Defaults to "rainbow".
        alphavalue (float, optional): Transparency of markers. Defaults to 0.5.
        overwrite (bool, optional): If ``True`` overwrites existing labeled videos. Defaults to False.
        confidence_to_alpha (bool | Callable[[float], float], optional): If False, all keypoints
            use alpha=1. Otherwise, a function f: [0, 1] -> [0, 1] maps score to alpha.
            When True, f(x) = max(0, (x - pcutoff)/(1 - pcutoff)). Defaults to False.
        plot_bboxes (bool, optional): If using Pytorch and in Top-Down mode,
            setting this to true will also plot the bounding boxes. Defaults to True.
        bboxes_pcutoff (float, optional): If plotting bounding boxes, this overrides the bboxes_pcutoff
            set in the model configuration. Defaults to None.
        max_workers (int | None): Maximum number of processes to use for multiprocessing.
            Set this parameter to limit the total RAM-usage of simultaneous processes.
            Default: no maximum (i.e. number of spawned processes is based on the number of
            cores and the number of input videos).
        kwargs (dict, optional): Additional arguments.
            For torch-based shuffles, can be used to specify:
                - snapshot_index
                - detector_snapshot_index

    Returns:
        list[bool]: ``True`` if the video is successfully created for each item in ``videos``.

    Examples:
        Create the labeled video for a single video

            deeplabcut.create_labeled_video(
                '/analysis/project/reaching-task/config.yaml',
                ['/analysis/project/videos/reachingvideo1.avi'],
            )
        Create the labeled video for a single video and store the individual frames

            deeplabcut.create_labeled_video(
                '/analysis/project/reaching-task/config.yaml',
                ['/analysis/project/videos/reachingvideo1.avi'],
                fastmode=True,
                save_frames=True,
            )

        Create the labeled video for multiple videos

            deeplabcut.create_labeled_video(
                '/analysis/project/reaching-task/config.yaml',
                [
                    '/analysis/project/videos/reachingvideo1.avi',
                    '/analysis/project/videos/reachingvideo2.avi',
                ],
            )

        Create the labeled video for all the videos with an .avi extension in a directory.

            deeplabcut.create_labeled_video(
                '/analysis/project/reaching-task/config.yaml',
                ['/analysis/project/videos/'],
            )

        Create the labeled video for all the videos with an .mp4 extension in a directory.

            deeplabcut.create_labeled_video(
                '/analysis/project/reaching-task/config.yaml',
                ['/analysis/project/videos/'],
                video_extensions='mp4',
            )
    """
    if config != "":
        config = Path(config)
    if destfolder is not None:
        destfolder = Path(destfolder)
    if skeleton is None:
        skeleton = []
    if config == "":
        if pcutoff is None:
            pcutoff = 0.6
        if bboxes_pcutoff is None:
            bboxes_pcutoff = 0.6

        individuals = [""]
        uniquebodyparts = []
    else:
        cfg = auxiliaryfunctions.read_config(config)
        train_fraction = cfg["TrainingFraction"][trainingsetindex]
        track_method = auxfun_multianimal.get_track_method(cfg, track_method=track_method)
        if pcutoff is None:
            pcutoff = cfg["pcutoff"]

        # Get individuals from the config
        individuals = cfg.get("individuals", [""])
        uniquebodyparts = cfg.get("uniquebodyparts", [])

        # Only for PyTorch engine - check if the shuffle was fine-tuned from a
        #  SuperAnimal model with memory replay -> SuperAnimal bodyparts must be used
        model_folder = auxiliaryfunctions.get_model_folder(
            train_fraction,
            shuffle,
            cfg,
            modelprefix,
            engine=Engine.PYTORCH,
        )
        model_config_path = Path(config).parent / model_folder / "train" / Engine.PYTORCH.pose_cfg_name
        if model_config_path.exists():
            model_config = PoseConfig.from_yaml(model_config_path)
            if model_config.select("train_settings.weight_init.memory_replay"):
                superanimal_name = model_config["train_settings"]["weight_init"]["dataset"]
            if bboxes_pcutoff is None:
                bboxes_pcutoff = model_config.select("detector.model.box_score_thresh") or 0.6
        else:
            if bboxes_pcutoff is None:
                bboxes_pcutoff = 0.6

    if init_weights == "":
        DLCscorer, DLCscorerlegacy = auxiliaryfunctions.get_scorer_name(
            cfg,
            shuffle,
            train_fraction,
            modelprefix=modelprefix,
            **kwargs,
        )  # automatically loads corresponding model (even training iteration based on snapshot index)
    else:
        DLCscorer = "DLC_" + Path(init_weights).stem
        DLCscorerlegacy = "DLC_" + Path(init_weights).stem

    if save_frames:
        fastmode = False  # otherwise one cannot save frames
        keypoints_only = False

    # parse the alpha selection function
    if isinstance(confidence_to_alpha, bool):
        confidence_to_alpha = _get_default_conf_to_alpha(confidence_to_alpha, pcutoff)

    if superanimal_name != "":
        dlc_root_path = auxiliaryfunctions.get_deeplabcut_path()
        test_cfg = auxiliaryfunctions.read_plainconfig(
            dlc_root_path / "modelzoo" / "project_configs" / f"{superanimal_name}.yaml"
        )

        bodyparts = test_cfg["bodyparts"]
        cfg = {
            "skeleton": skeleton,
            "skeleton_color": skeleton_color,
            "pcutoff": pcutoff,
            "dotsize": dotsize,
            "alphavalue": alphavalue,
            "colormap": colormap,
            "bodyparts": bodyparts,
            "multianimalbodyparts": bodyparts,
            "individuals": individuals,
            "uniquebodyparts": uniquebodyparts,
        }
    else:
        bodyparts = auxiliaryfunctions.intersection_of_body_parts_and_ones_given_by_user(cfg, displayedbodyparts)

    if draw_skeleton:
        bodyparts2connect = cfg["skeleton"]
        if displayedbodyparts != "all":
            bodyparts2connect = [
                pair for pair in bodyparts2connect if all(element in displayedbodyparts for element in pair)
            ]
        skeleton_color = cfg["skeleton_color"]
    else:
        bodyparts2connect = None
        skeleton_color = None

    start_path = Path.cwd()
    Videos = collect_video_paths(videos, extensions=video_extensions)

    if not Videos:
        return []

    func = partial(
        proc_video,
        videos,
        destfolder,
        filtered,
        DLCscorer,
        DLCscorerlegacy,
        track_method,
        cfg,
        displayedindividuals,
        color_by,
        bodyparts,
        codec,
        bodyparts2connect,
        trailpoints,
        save_frames,
        outputframerate,
        Frames2plot,
        draw_skeleton,
        skeleton_color,
        displaycropped,
        fastmode,
        keypoints_only,
        overwrite,
        init_weights=init_weights,
        pcutoff=pcutoff,
        confidence_to_alpha=confidence_to_alpha,
        plot_bboxes=plot_bboxes,
        bboxes_pcutoff=bboxes_pcutoff,
    )

    if get_start_method() == "fork":
        n_workers = max_workers or min(os.cpu_count(), len(Videos))
        with Pool(n_workers) as pool:
            results = pool.map(func, Videos)
    else:
        results = []
        for video in Videos:
            results.append(func(video))

    os.chdir(start_path)
    return results

create_video_with_all_detections

create_video_with_all_detections(
    config: str | Path,
    videos: list[str | Path],
    video_extensions: str | Sequence[str] | None = None,
    shuffle=1,
    trainingsetindex=0,
    displayedbodyparts="all",
    cropping: list[int] | None = None,
    destfolder=None,
    modelprefix="",
    confidence_to_alpha: bool | Callable[[float], float] = False,
    plot_bboxes: bool = True,
    **kwargs
)

Create a video labeled with all the detections stored in a '*_full.pickle' file.

Parameters:

Name Type Description Default

config

str | Path

Absolute path to the config.yaml file.

required

videos

list[str | Path]

Full paths to videos for analysis, or a directory where all videos with the same extension are stored.

required

video_extensions

str | Sequence[str] | None

Controls how videos are filtered, based on file extension. File paths and directory contents are treated differently: - None (default): file paths are accepted as-is; directories are scanned for files with a recognized video extension. - str or Sequence[str] (e.g. "mp4" or ["mp4", "avi"]): both file paths and directory contents are filtered by the given extension(s). Defaults to None.

None

shuffle

int

Number of shuffles of training dataset. Defaults to 1.

1

trainingsetindex

int

Integer specifying which TrainingsetFraction to use. By default the first (note that TrainingFraction is a list in config.yaml).

0

displayedbodyparts

list of strings

Body parts plotted in the video. Either all, then all body parts from config.yaml are used or a list of strings that are a subset of the full list. E.g. ['hand','Joystick'] for the demo Reaching-Mackenzie-2018-08-30/config.yaml to select only these two body parts.

'all'

cropping

list[int]

If passed in, [x1, x2, y1, y2] crop coordinates shift detections appropriately. Defaults to None.

None

destfolder

string

Destination folder used for storing analysis data (default is the path of the video).

None

confidence_to_alpha

bool | Callable[[float], float]

If False, all keypoints use alpha=1. Otherwise, a function f: [0, 1] -> [0, 1] maps score to alpha. When True, f(x) = x. Defaults to False.

False

plot_bboxes

bool

If detections were produced using a Pytorch Top-Down model, setting this parameter to True will also plot the bounding boxes generated by the detector. Defaults to True.

True

kwargs

dict

Additional arguments. For torch-based shuffles, can be used to specify: - snapshot_index - detector_snapshot_index

{}
Source code in deeplabcut/utils/make_labeled_video.py
@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0")
def create_video_with_all_detections(
    config: str | Path,
    videos: list[str | Path],
    video_extensions: str | Sequence[str] | None = None,
    shuffle=1,
    trainingsetindex=0,
    displayedbodyparts="all",
    cropping: list[int] | None = None,
    destfolder=None,
    modelprefix="",
    confidence_to_alpha: bool | Callable[[float], float] = False,
    plot_bboxes: bool = True,
    **kwargs,
):
    """Create a video labeled with all the detections stored in a '*_full.pickle' file.

    Args:
        config (str | Path): Absolute path to the config.yaml file.
        videos (list[str | Path]): Full paths to videos for analysis, or a directory where all
            videos with the same extension are stored.
        video_extensions (str | Sequence[str] | None, optional): Controls how ``videos`` are
            filtered, based on file extension. File paths and directory contents are
            treated differently:
            - ``None`` (default): file paths are accepted as-is; directories are
              scanned for files with a recognized video extension.
            - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``):
              both file paths and directory contents are filtered by the given
              extension(s). Defaults to None.
        shuffle (int, optional): Number of shuffles of training dataset. Defaults to 1.
        trainingsetindex (int, optional): Integer specifying which TrainingsetFraction to use.
            By default the first (note that TrainingFraction is a list in config.yaml).
        displayedbodyparts (list of strings, optional): Body parts plotted in the video.
            Either ``all``, then all body parts from config.yaml are used or
            a list of strings that are a subset of the full list.
            E.g. ['hand','Joystick'] for the demo Reaching-Mackenzie-2018-08-30/config.yaml
            to select only these two body parts.
        cropping (list[int], optional): If passed in, [x1, x2, y1, y2] crop coordinates
            shift detections appropriately. Defaults to None.
        destfolder (string, optional): Destination folder used for storing analysis data
            (default is the path of the video).
        confidence_to_alpha (bool | Callable[[float], float], optional): If False, all keypoints
            use alpha=1. Otherwise, a function f: [0, 1] -> [0, 1] maps score to alpha.
            When True, f(x) = x. Defaults to False.
        plot_bboxes (bool, optional): If detections were produced using a Pytorch Top-Down model,
            setting this parameter to True will also plot
            the bounding boxes generated by the detector. Defaults to True.
        kwargs (dict, optional): Additional arguments.
            For torch-based shuffles, can be used to specify:
                - snapshot_index
                - detector_snapshot_index
    """
    import re

    from deeplabcut.core.inferenceutils import Assembler

    cfg = auxiliaryfunctions.read_config(config)
    trainFraction = cfg["TrainingFraction"][trainingsetindex]
    DLCscorername, _ = auxiliaryfunctions.get_scorer_name(
        cfg,
        shuffle,
        trainFraction,
        modelprefix=modelprefix,
        **kwargs,
    )

    videos = collect_video_paths(videos, extensions=video_extensions)
    if not videos:
        return

    if isinstance(confidence_to_alpha, bool):
        confidence_to_alpha = _get_default_conf_to_alpha(confidence_to_alpha, 0)

    for video in videos:
        videofolder = str(Path(video).with_suffix(""))

        if destfolder is None:
            outputname = f"{videofolder + DLCscorername}_full.mp4"
            full_pickle = videofolder + DLCscorername + "_full.pickle"
        else:
            auxiliaryfunctions.attempt_to_make_folder(destfolder)
            outputname = str(Path(destfolder) / (Path(video).stem + DLCscorername + "_full.mp4"))
            full_pickle = str(Path(destfolder) / (Path(video).stem + DLCscorername + "_full.pickle"))

        if not Path(outputname).is_file():
            video_name = str(Path(video).stem)
            print("Creating labeled video for ", video_name)
            h5file = full_pickle.replace("_full.pickle", ".h5")
            data, metadata = auxfun_multianimal.LoadFullMultiAnimalData(h5file)
            data = dict(data)  # Cast to dict (making a copy) so items can safely be popped

            x1, y1 = 0, 0
            if cropping is not None:
                x1, _, y1, _ = cropping
            # TODO @deruyter92: This pattern should be refactored throughout the codebase
            # it is reading a config value that is supposed to be missing / None.
            elif (metadata.get("data") or {}).get("cropping"):
                x1, _, y1, _ = metadata["data"]["cropping_parameters"]

            header = data.pop("metadata")
            all_jointnames = header["all_joints_names"]

            if displayedbodyparts == "all":
                numjoints = len(all_jointnames)
                bpts = range(numjoints)
            else:  # select only "displayedbodyparts"
                bpts = []
                for bptindex, bp in enumerate(all_jointnames):
                    if bp in displayedbodyparts:
                        bpts.append(bptindex)
                numjoints = len(bpts)
            frame_names = list(data)
            frames = [int(re.findall(r"\d+", name)[0]) for name in frame_names]
            colorclass = plt.cm.ScalarMappable(cmap=cfg["colormap"])
            C = colorclass.to_rgba(np.linspace(0, 1, numjoints))
            colors = (C[:, :3] * 255).astype(np.uint8)

            pcutoff = cfg["pcutoff"]
            dotsize = cfg["dotsize"]
            clip = vp(fname=video, sname=outputname, codec="mp4v")
            ny, nx = clip.height, clip.width

            bboxes_pcutoff = 0.6
            if pytorch_cfg := (metadata.get("data") or {}).get("pytorch-config"):
                bboxes_pcutoff = PoseConfig.from_any(pytorch_cfg).select("detector.model.box_score_thresh") or 0.6
            bboxes_color = (255, 0, 0)

            for n in trange(clip.nframes):
                frame = clip.load_frame()
                if frame is None:
                    continue
                try:
                    ind = frames.index(n)

                    # Draw bounding boxes of required and present
                    if plot_bboxes and "bboxes" in data[frame_names[ind]] and "bbox_scores" in data[frame_names[ind]]:
                        bboxes = data[frame_names[ind]]["bboxes"]
                        bbox_scores = data[frame_names[ind]]["bbox_scores"]
                        n_bboxes = bboxes.shape[0]
                        for i in range(n_bboxes):
                            bbox = bboxes[i, :]
                            x, y = bbox[0], bbox[1]
                            x += x1
                            y += y1
                            w, h = bbox[2], bbox[3]
                            confidence = bbox_scores[i]
                            if confidence < bboxes_pcutoff:
                                continue
                            rect_coords = rectangle_perimeter(start=(y, x), extent=(h, w))

                            set_color(
                                frame,
                                rect_coords,
                                bboxes_color,
                            )

                    # Draw detected bodyparts
                    dets = Assembler._flatten_detections(data[frame_names[ind]])
                    for det in dets:
                        if det.label not in bpts or det.confidence < pcutoff:
                            continue
                        x, y = det.pos
                        x += x1
                        y += y1
                        rr, cc = disk((y, x), dotsize, shape=(ny, nx))
                        alpha = 1
                        if confidence_to_alpha is not None:
                            alpha = confidence_to_alpha(det.confidence)

                        set_color(
                            frame,
                            (rr, cc),
                            colors[bpts.index(det.label)],
                            alpha,
                        )
                except ValueError as err:  # No data stored for that particular frame
                    print(n, f"no data: {err}")
                try:
                    clip.save_frame(frame)
                except Exception:
                    print(n, "frame writing error.")
            clip.close()
        else:
            print("Detections already plotted, ", outputname)

deprecated

deprecated(
    *, replacement: str | None = None, since: str | None = None, removed_in: str | None = None
) -> Callable[[Callable[P, R]], Callable[P, R]]

Mark a function as deprecated.

Parameters:

Name Type Description Default

replacement

str | None

Fully-qualified name of the replacement callable, e.g. "deeplabcut.utils.auxfun_videos.list_videos_in_folder".

None

since

str | None

Version in which the function was deprecated.

None

removed_in

str | None

Version in which the function will be removed.

None
Source code in deeplabcut/core/deprecation.py
def deprecated(
    *,
    replacement: str | None = None,
    since: str | None = None,
    removed_in: str | None = None,
) -> Callable[[Callable[P, R]], Callable[P, R]]:
    """Mark a function as deprecated.

    Args:
        replacement: Fully-qualified name of the replacement callable, e.g.
            ``"deeplabcut.utils.auxfun_videos.list_videos_in_folder"``.
        since: Version in which the function was deprecated.
        removed_in: Version in which the function will be removed.
    """

    def decorator(fn: Callable[P, R]) -> Callable[P, R]:
        info = DeprecationInfo(
            kind="callable",
            target=fn.__qualname__,
            replacement=replacement,
            since=since,
            removed_in=removed_in,
        )
        message = info.format_message()

        @functools.wraps(fn)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            warnings.warn(message, DLCDeprecationWarning, stacklevel=2)
            return fn(*args, **kwargs)

        wrapper.__doc__ = f"Deprecated. {message}\n\n" + (fn.__doc__ or "")
        wrapper.__deprecated_info__ = info
        return wrapper

    return decorator

filter_files_by_patterns

filter_files_by_patterns(
    folder: str | Path,
    start_patterns: set[str] | None = None,
    contain_patterns: set[str] | None = None,
    end_patterns: set[str] | None = None,
) -> list[Path]

Filters files in a folder based on start, contain, and end patterns.

Parameters:

Name Type Description Default

folder

str | Path

The folder to search for files.

required

start_patterns

Set[str] | None

Patterns the filenames should start with. If None or empty, this pattern is not taken into account.

None

contain_patterns

set[str]

Patterns the filenames should contain. If None or empty, this pattern is not taken into account.

None

end_patterns

set[str]

Patterns the filenames should end with. If None or empty, this pattern is not taken into account.

None

Returns:

Type Description
list[Path]

List[Path]: List of files that match the criteria.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def filter_files_by_patterns(
    folder: str | Path,
    start_patterns: set[str] | None = None,
    contain_patterns: set[str] | None = None,
    end_patterns: set[str] | None = None,
) -> list[Path]:
    """Filters files in a folder based on start, contain, and end patterns.

    Args:
        folder (str | Path): The folder to search for files.

        start_patterns (Set[str] | None): Patterns the filenames should start with.
            If None or empty, this pattern is not taken into account.

        contain_patterns (set[str]): Patterns the filenames should contain.
            If None or empty, this pattern is not taken into account.

        end_patterns (set[str]): Patterns the filenames should end with.
            If None or empty, this pattern is not taken into account.

    Returns:
        List[Path]: List of files that match the criteria.
    """
    folder = Path(folder)  # Ensure the folder is a Path object
    if not folder.is_dir():
        raise ValueError(f"{folder} is not a valid directory.")

    # Filter files based on the given patterns
    matching_files = [
        file
        for file in folder.iterdir()
        if file.is_file()
        and (not start_patterns or any(file.name.startswith(start) for start in start_patterns))
        and (not contain_patterns or any(contain in file.name for contain in contain_patterns))
        and (not end_patterns or any(file.name.endswith(end) for end in end_patterns))
    ]

    return matching_files

filter_unwanted_paf_connections

filter_unwanted_paf_connections(cfg, paf_graph)

Get rid of skeleton connections between multi and unique body parts.

Source code in deeplabcut/utils/auxfun_multianimal.py
def filter_unwanted_paf_connections(cfg, paf_graph):
    """Get rid of skeleton connections between multi and unique body parts."""
    multi = extractindividualsandbodyparts(cfg)[2]
    desired = list(combinations(range(len(multi)), 2))
    return [i for i, edge in enumerate(paf_graph) if tuple(edge) not in desired]

find_analyzed_data

find_analyzed_data(folder, videoname: str, scorer: str, filtered=False, track_method='')

Find potential data files from the hints given to the function.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def find_analyzed_data(folder, videoname: str, scorer: str, filtered=False, track_method=""):
    """Find potential data files from the hints given to the function."""
    scorer_legacy = scorer.replace("DLC", "DeepCut")
    suffix = "_filtered" if filtered else ""
    tracker = TRACK_METHODS.get(track_method, "")

    candidates = []
    for file in collect_video_paths(folder, extensions=".h5"):
        stem = file.stem.replace("_filtered", "")
        starts_by_scorer = file.name.startswith((videoname + scorer, videoname + scorer_legacy))
        if tracker:
            matches_tracker = stem.endswith(tracker)
        else:
            matches_tracker = not any(stem.endswith(s) for s in TRACK_METHODS.values())
        if all(
            (
                starts_by_scorer,
                "skeleton" not in file.name,
                matches_tracker,
                (filtered and "filtered" in file.name) or (not filtered and "filtered" not in file.name),
            )
        ):
            candidates.append(file)

    if not len(candidates):
        msg = (
            f"No {'un' if not filtered else ''}filtered data file found in {folder} "
            f"for video {videoname} and scorer {scorer}"
        )
        if track_method:
            msg += f" and {track_method} tracker"
        msg += "."
        raise FileNotFoundError(msg)

    n_candidates = len(candidates)
    if n_candidates > 1:  # This should not be happening anyway...
        print(f"{n_candidates} possible data files were found: {candidates}.\nPicking the first by default...")
    filepath = str(candidates[0])
    scorer = scorer if scorer in filepath else scorer_legacy
    return filepath, scorer, suffix

find_video_metadata

find_video_metadata(folder, videoname: str, scorer: str)

For backward compatibility, let us search the substring 'meta'.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def find_video_metadata(folder, videoname: str, scorer: str):
    """For backward compatibility, let us search the substring 'meta'."""
    scorer_legacy = scorer.replace("DLC", "DeepCut")
    meta_files = filter_files_by_patterns(
        folder=folder,
        start_patterns={videoname + scorer, videoname + scorer_legacy},
        contain_patterns={"meta"},
        end_patterns={"pickle"},
    )
    if not meta_files:
        raise FileNotFoundError(f"No metadata found in {folder} for video {videoname} and scorer {scorer}.")
    return meta_files[0]

get_bodyparts

get_bodyparts(cfg: dict) -> list[str]

Get the bodyparts.

Parameters:

Name Type Description Default

cfg

dict

a project configuration file

required

Returns: bodyparts listed in the project (does not include the unique_bodyparts entry)

Source code in deeplabcut/utils/auxiliaryfunctions.py
def get_bodyparts(cfg: dict) -> list[str]:
    """Get the bodyparts.

    Args:
        cfg: a project configuration file

    Returns: bodyparts listed in the project (does not include the unique_bodyparts entry)
    """
    if cfg.get("multianimalproject", False):
        (
            _,
            _,
            multianimal_bodyparts,
        ) = auxfun_multianimal.extractindividualsandbodyparts(cfg)
        return multianimal_bodyparts

    return cfg["bodyparts"]

get_data_and_metadata_filenames

get_data_and_metadata_filenames(
    trainingsetfolder: str | Path, trainFraction: float, shuffle: int, cfg: dict
) -> tuple[Path, Path]

Paths to data and metadata files relative to the project root.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def get_data_and_metadata_filenames(
    trainingsetfolder: str | Path,
    trainFraction: float,
    shuffle: int,
    cfg: dict,
) -> tuple[Path, Path]:
    """Paths to data and metadata files relative to the project root."""
    base = Path(trainingsetfolder)
    datafn = base / (
        cfg["Task"] + "_" + cfg["scorer"] + str(int(100 * trainFraction)) + "shuffle" + str(shuffle) + ".mat"
    )
    metadatafn = base / (
        "Documentation_data-" + cfg["Task"] + "_" + str(int(trainFraction * 100)) + "shuffle" + str(shuffle) + ".pickle"
    )
    return datafn, metadatafn

get_deeplabcut_path

get_deeplabcut_path() -> Path

Get path of where deeplabcut is currently running.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def get_deeplabcut_path() -> Path:
    """Get path of where deeplabcut is currently running."""
    import importlib.util

    return Path(importlib.util.find_spec("deeplabcut").origin).parent

get_evaluation_folder

get_evaluation_folder(
    trainFraction: float, shuffle: int, cfg: dict, engine: Engine | None = None, modelprefix: str = ""
) -> Path

Get the evaluation folder.

Parameters:

Name Type Description Default

trainFraction

float

the training fraction (as defined in the project configuration) for which to get the evaluation folder

required

shuffle

int

the index of the shuffle for which to get the evaluation folder

required

cfg

dict

the project configuration

required

engine

Engine | None

The engine for which we want the model folder. Defaults to None, which automatically gets the engine for the shuffle from the training dataset metadata file.

None

modelprefix

str

The name of the folder

''

Returns:

Type Description
Path

the relative path from the project root to the folder containing the model files for a shuffle (configuration files, snapshots, training logs, ...)

Source code in deeplabcut/utils/auxiliaryfunctions.py
def get_evaluation_folder(
    trainFraction: float,
    shuffle: int,
    cfg: dict,
    engine: Engine | None = None,
    modelprefix: str = "",
) -> Path:
    """Get the evaluation folder.

    Args:
        trainFraction: the training fraction (as defined in the project configuration)
            for which to get the evaluation folder
        shuffle: the index of the shuffle for which to get the evaluation folder
        cfg: the project configuration
        engine: The engine for which we want the model folder. Defaults to None,
            which automatically gets the engine for the shuffle from the training
            dataset metadata file.
        modelprefix: The name of the folder

    Returns:
        the relative path from the project root to the folder containing the model files
        for a shuffle (configuration files, snapshots, training logs, ...)
    """
    if engine is None:
        from deeplabcut.generate_training_dataset.metadata import get_shuffle_engine

        engine = get_shuffle_engine(
            cfg=cfg,
            trainingsetindex=cfg["TrainingFraction"].index(trainFraction),
            shuffle=shuffle,
            modelprefix=modelprefix,
        )

    Task = cfg["Task"]
    date = cfg["date"]
    iterate = "iteration-" + str(cfg["iteration"])
    if "eval_prefix" in cfg:
        eval_prefix = cfg["eval_prefix"]
    else:
        eval_prefix = engine.results_folder_name
    return Path(
        modelprefix,
        eval_prefix,
        iterate,
        Task + date + "-trainset" + str(int(trainFraction * 100)) + "shuffle" + str(shuffle),
    )

get_model_folder

get_model_folder(
    trainFraction: float, shuffle: int, cfg: dict, modelprefix: str = "", engine: Engine = Engine.TF
) -> Path

Get the model folder.

Parameters:

Name Type Description Default

trainFraction

float

the training fraction (as defined in the project configuration) for which to get the model folder

required

shuffle

int

the index of the shuffle for which to get the model folder

required

cfg

dict

the project configuration

required

modelprefix

str

The name of the folder

''

engine

Engine

The engine for which we want the model folder. Defaults to tensorflow for backwards compatibility with DeepLabCut 2.X

TF

Returns:

Type Description
Path

the relative path from the project root to the folder containing the model files for a shuffle (configuration files, snapshots, training logs, ...)

Source code in deeplabcut/utils/auxiliaryfunctions.py
def get_model_folder(
    trainFraction: float,
    shuffle: int,
    cfg: dict,
    modelprefix: str = "",
    engine: Engine = Engine.TF,
) -> Path:
    """Get the model folder.

    Args:
        trainFraction: the training fraction (as defined in the project configuration)
            for which to get the model folder
        shuffle: the index of the shuffle for which to get the model folder
        cfg: the project configuration
        modelprefix: The name of the folder
        engine: The engine for which we want the model folder. Defaults to `tensorflow`
            for backwards compatibility with DeepLabCut 2.X

    Returns:
        the relative path from the project root to the folder containing the model files
        for a shuffle (configuration files, snapshots, training logs, ...)
    """
    proj_id = f"{cfg['Task']}{cfg['date']}"
    return Path(
        modelprefix,
        engine.model_folder_name,
        f"iteration-{cfg['iteration']}",
        f"{proj_id}-trainset{int(trainFraction * 100)}shuffle{shuffle}",
    )

get_scorer_name

get_scorer_name(
    cfg: dict,
    shuffle: int,
    trainFraction: float,
    trainingsiterations: str | int = "unknown",
    modelprefix: str = "",
    engine: Engine | None = None,
    **kwargs
)

Extract the scorer/network name for a particular shuffle, training fraction, etc.

If the engine is not specified, determines which to use from the project configuration.

Parameters:

Name Type Description Default

**kwargs

Additional arguments. For torch-based shuffles, can be used to specify: - snapshot_index - detector_snapshot_index

{}

Returns:

Name Type Description
tuple

DLCscorer and DLCscorerlegacy (old naming convention).

Source code in deeplabcut/utils/auxiliaryfunctions.py
def get_scorer_name(
    cfg: dict,
    shuffle: int,
    trainFraction: float,
    trainingsiterations: str | int = "unknown",
    modelprefix: str = "",
    engine: Engine | None = None,
    **kwargs,
):
    """Extract the scorer/network name for a particular shuffle, training fraction, etc.

    If the engine is not specified, determines which to use from the project
    configuration.

    Args:
        **kwargs: Additional arguments. For torch-based shuffles, can be used to specify:
            - snapshot_index
            - detector_snapshot_index

    Returns:
        tuple: DLCscorer and DLCscorerlegacy (old naming convention).
    """
    if engine is None:
        from deeplabcut.generate_training_dataset.metadata import get_shuffle_engine

        engine = get_shuffle_engine(
            cfg=cfg,
            trainingsetindex=cfg["TrainingFraction"].index(trainFraction),
            shuffle=shuffle,
            modelprefix=modelprefix,
        )

    if engine == Engine.PYTORCH:
        from deeplabcut.pose_estimation_pytorch.apis.utils import get_scorer_name

        snapshot_index = kwargs.get("snapshot_index", None)
        detector_snapshot_index = kwargs.get("detector_snapshot_index", None)
        dlc3_scorer = get_scorer_name(
            cfg=cfg,
            shuffle=shuffle,
            train_fraction=trainFraction,
            snapshot_index=snapshot_index,
            detector_index=detector_snapshot_index,
            modelprefix=modelprefix,
        )
        return dlc3_scorer, dlc3_scorer

    Task = cfg["Task"]
    date = cfg["date"]

    if trainingsiterations == "unknown":
        snapshotindex = get_snapshot_index_for_scorer("snapshotindex", cfg["snapshotindex"])
        model_folder = get_model_folder(trainFraction, shuffle, cfg, engine=engine, modelprefix=modelprefix)
        train_folder = Path(cfg["project_path"]) / model_folder / "train"
        snapshot_names = get_snapshots_from_folder(train_folder)
        snapshot_name = snapshot_names[snapshotindex]
        trainingsiterations = Path(snapshot_name).parts[-1].split("-")[-1]

    dlc_cfg = read_plainconfig(
        str(
            Path(cfg["project_path"])
            / get_model_folder(trainFraction, shuffle, cfg, engine=engine, modelprefix=modelprefix)
            / "train"
            / engine.pose_cfg_name
        )
    )
    # ABBREVIATE NETWORK NAMES -- esp. for mobilenet!
    if "resnet" in dlc_cfg["net_type"]:
        if dlc_cfg.get("multi_stage", False):
            netname = "dlcrnetms5"
        else:
            netname = dlc_cfg["net_type"].replace("_", "")
    elif "mobilenet" in dlc_cfg["net_type"]:  # mobilenet >> mobnet_100; mobnet_35 etc.
        netname = "mobnet_" + str(int(float(dlc_cfg["net_type"].split("_")[-1]) * 100))
    elif "efficientnet" in dlc_cfg["net_type"]:
        netname = "effnet_" + dlc_cfg["net_type"].split("-")[1]
    else:
        raise ValueError(f"Failed to abbreviate network name: {dlc_cfg['net_type']}")

    scorer = "DLC_" + netname + "_" + Task + str(date) + "shuffle" + str(shuffle) + "_" + str(trainingsiterations)
    # legacy scorername until DLC 2.1. (cfg['resnet'] is deprecated / which is why we get the resnet_xyz name from
    # dlc_cfg!
    # scorer_legacy = 'DeepCut' + "_resnet" + str(cfg['resnet']) + "_" + Task + str(date) + 'shuffle' + str(shuffle) +
    # '_' + str(trainingsiterations)
    scorer_legacy = scorer.replace("DLC", "DeepCut")
    return scorer, scorer_legacy

get_snapshots_from_folder

get_snapshots_from_folder(train_folder: Path) -> list[str]

Returns an ordered list of existing snapshot names in the train folder, sorted by increasing training iterations.

Raises:

Type Description
FileNotFoundError

If no snapshot_names are found in the train_folder.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def get_snapshots_from_folder(train_folder: Path) -> list[str]:
    """Returns an ordered list of existing snapshot names in the train folder, sorted by
    increasing training iterations.

    Raises:
        FileNotFoundError: If no snapshot_names are found in the train_folder.
    """
    snapshot_names = [file.stem for file in train_folder.iterdir() if "index" in file.name]

    if len(snapshot_names) == 0:
        raise FileNotFoundError(
            f"No snapshots were found in {train_folder}! Please ensure the network has "
            f"been trained and verify the iteration, shuffle and trainFraction are "
            f"correct."
        )

    # sort in ascending order of iteration number
    return sorted(snapshot_names, key=lambda name: int(name.split("-")[1]))

get_training_set_folder

get_training_set_folder(cfg: dict) -> Path

Training Set folder for config file based on parameters.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def get_training_set_folder(cfg: dict) -> Path:
    """Training Set folder for config file based on parameters."""
    Task = cfg["Task"]
    date = cfg["date"]
    iterate = "iteration-" + str(cfg["iteration"])
    return Path("training-datasets") / iterate / ("UnaugmentedDataSet_" + Task + date)

get_unique_bodyparts

get_unique_bodyparts(cfg: dict) -> list[str]

Get the unique bodyparts.

Parameters:

Name Type Description Default

cfg

dict

a project configuration file

required

Returns: all unique bodyparts listed in the project

Source code in deeplabcut/utils/auxiliaryfunctions.py
def get_unique_bodyparts(cfg: dict) -> list[str]:
    """Get the unique bodyparts.

    Args:
        cfg: a project configuration file

    Returns: all unique bodyparts listed in the project
    """
    if cfg.get("multianimalproject", False):
        (
            _,
            unique_bodyparts,
            _,
        ) = auxfun_multianimal.extractindividualsandbodyparts(cfg)
        return unique_bodyparts

    return []

get_video_list

get_video_list(filename, videopath, videtype)

Get list of videos in a path (if filetype == all), otherwise just a specific file.

Source code in deeplabcut/utils/auxiliaryfunctions.py
@deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.0")
def get_video_list(filename, videopath, videtype):
    """Get list of videos in a path (if filetype == all), otherwise just a specific
    file.
    """
    videos = list(grab_files_in_folder(videopath, videtype))
    if filename == "all":
        return videos
    else:
        if filename in videos:
            videos = [filename]
        else:
            videos = []
            print("Video not found!", filename)
    return videos

getpafgraph

getpafgraph(cfg, printnames=True)

Auxiliary function that turns skeleton (list of connected bodypart pairs) into a list of corresponding indices (with regard to the stacked multianimal/uniquebodyparts)

Convention: multianimalbodyparts go first!

Source code in deeplabcut/utils/auxfun_multianimal.py
def getpafgraph(cfg, printnames=True):
    """Auxiliary function that turns skeleton (list of connected bodypart pairs) into a
    list of corresponding indices (with regard to the stacked
    multianimal/uniquebodyparts)

    Convention: multianimalbodyparts go first!
    """
    individuals, uniquebodyparts, multianimalbodyparts = extractindividualsandbodyparts(cfg)
    # Attention this order has to be consistent (for training set creation, training, inference etc.)

    bodypartnames = multianimalbodyparts + uniquebodyparts
    lookupdict = {bodypartnames[j]: j for j in range(len(bodypartnames))}

    if cfg["skeleton"] is None:
        cfg["skeleton"] = []

    connected = set()
    partaffinityfield_graph = []
    for link in cfg["skeleton"]:
        if link[0] in bodypartnames and link[1] in bodypartnames:
            bp1 = int(lookupdict[link[0]])
            bp2 = int(lookupdict[link[1]])
            connected.add(bp1)
            connected.add(bp2)
            partaffinityfield_graph.append([bp1, bp2])
        else:
            print("Attention, parts do not exist!", link)

    if printnames:
        graph2names(cfg, partaffinityfield_graph)

    return partaffinityfield_graph

grab_files_in_folder

grab_files_in_folder(folder, ext='', relative=True)

Return the paths of files with extension ext present in folder.

Source code in deeplabcut/utils/auxiliaryfunctions.py
@deprecated(replacement="deeplabcut.collect_video_paths", since="3.0.1")
def grab_files_in_folder(folder, ext="", relative=True):
    """Return the paths of files with extension *ext* present in *folder*."""
    for file in Path(folder).iterdir():
        if file.name.endswith(ext):
            yield file.name if relative else str(file)

imread

imread(image_path, mode='skimage')

Read image either with skimage or cv2.

Returns frame in uint with 3 color channels.

Source code in deeplabcut/utils/auxfun_videos.py
def imread(image_path, mode="skimage"):
    """Read image either with skimage or cv2.

    Returns frame in uint with 3 color channels.
    """
    if mode == "skimage":
        image = io.imread(image_path)
        if image.ndim == 2 or image.shape[-1] == 1:
            image = skimage.color.gray2rgb(image)
        elif image.shape[-1] == 4:
            image = skimage.color.rgba2rgb(image)

        return img_as_ubyte(image)

    elif mode == "cv2":
        return cv2.imread(str(image_path), cv2.IMREAD_UNCHANGED)[..., ::-1]  # ~10% faster than using cv2.cvtColor

intersection_of_body_parts_and_ones_given_by_user

intersection_of_body_parts_and_ones_given_by_user(cfg, comparisonbodyparts)

Returns all body parts when comparisonbodyparts=='all', otherwise all bpts that are in the intersection of comparisonbodyparts and the actual bodyparts.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def intersection_of_body_parts_and_ones_given_by_user(cfg, comparisonbodyparts):
    """Returns all body parts when comparisonbodyparts=='all', otherwise all bpts that
    are in the intersection of comparisonbodyparts and the actual bodyparts.
    """
    # if "MULTI!" in allbpts:
    if cfg["multianimalproject"]:
        allbpts = cfg["multianimalbodyparts"] + cfg["uniquebodyparts"]
    else:
        allbpts = cfg["bodyparts"]

    if comparisonbodyparts == "all":
        return list(allbpts)
    else:  # take only items in list that are actually bodyparts...
        cpbpts = [bp for bp in allbpts if bp in comparisonbodyparts]
        # Ensure same order as in config.yaml
        return cpbpts

merge_windowsannotationdataONlinuxsystem

merge_windowsannotationdataONlinuxsystem(cfg)

If a project was created on Windows (and labeled there,) but ran on unix then the data folders corresponding in the keys in cfg['video_sets'] are not found.

This function gets them directly by looping over all folders in labeled-data

Source code in deeplabcut/utils/conversioncode.py
def merge_windowsannotationdataONlinuxsystem(cfg):
    """If a project was created on Windows (and labeled there,) but ran on unix then the
    data folders corresponding in the keys in cfg['video_sets'] are not found.

    This function gets them directly by looping over all folders in labeled-data
    """
    AnnotationData = []
    data_path = Path(cfg["project_path"], "labeled-data")
    annotationfolders = [d for d in data_path.iterdir() if d.is_dir()]
    print("The following folders were found:", annotationfolders)
    for folder in annotationfolders:
        filename = str(Path(folder) / ("CollectedData_" + cfg["scorer"] + ".h5"))
        try:
            data = pd.read_hdf(filename)
            guarantee_multiindex_rows(data)
            AnnotationData.append(data)
        except FileNotFoundError:
            print(filename, " not found (perhaps not annotated)")

    return AnnotationData

plot_edge_affinity_distributions

plot_edge_affinity_distributions(eval_pickle_file, include_bodyparts='all', output_name='', figsize=(10, 7))

Display the distribution of affinity costs of within- and between-animal edges.

Parameters:

Name Type Description Default

eval_pickle_file

str | Path

Path to a *_full.pickle from the evaluation-results folder.

required

include_bodyparts

list of strings

A list of body part names whose edges are to be shown. By default, all body parts and their corresponding edges are analyzed. We recommend only passing a subset of body parts for projects with large graphs. Defaults to "all".

'all'

output_name

string

Path where the plot is saved. By default, it is stored as costdist.png.

''

figsize

tuple

Figure size in inches.

(10, 7)
Source code in deeplabcut/utils/plotting.py
def plot_edge_affinity_distributions(
    eval_pickle_file,
    include_bodyparts="all",
    output_name="",
    figsize=(10, 7),
):
    """Display the distribution of affinity costs of within- and between-animal edges.

    Args:
        eval_pickle_file (str | Path): Path to a *_full.pickle from the evaluation-results folder.
        include_bodyparts (list of strings, optional): A list of body part names whose edges are to be shown.
            By default, all body parts and their corresponding edges are analyzed.
            We recommend only passing a subset of body parts for projects with large graphs. Defaults to "all".
        output_name (string, optional): Path where the plot is saved. By default, it is stored as costdist.png.
        figsize (tuple): Figure size in inches.
    """

    eval_pickle_file = Path(eval_pickle_file)
    with eval_pickle_file.open("rb") as file:
        data = pickle.load(file)
    meta_pickle_file = eval_pickle_file.with_name(eval_pickle_file.name.replace("_full.", "_meta."))
    with meta_pickle_file.open("rb") as file:
        metadata = pickle.load(file)
    (w_train, _), (b_train, _) = crossvalutils._calc_within_between_pafs(
        data,
        metadata,
        train_set_only=True,
    )
    data.pop("metadata", None)
    nonempty = set(i for i, vals in w_train.items() if vals)
    meta = metadata["data"]["DLC-model-config file"]
    bpts = list(map(str.lower, meta["all_joints_names"]))
    inds_multi = set(b for edge in meta["partaffinityfield_graph"] for b in edge)
    if include_bodyparts == "all":
        include_bodyparts = inds_multi
    else:
        include_bodyparts = set(bpts.index(bpt) for bpt in include_bodyparts)
    edges_to_keep = set()
    graph = meta["partaffinityfield_graph"]
    for n, edge in enumerate(graph):
        if not any(i in include_bodyparts for i in edge):
            continue
        edges_to_keep.add(n)
    edge_inds = edges_to_keep.intersection(nonempty)
    nrows = int(np.ceil(np.sqrt(len(edge_inds))))
    ncols = int(np.ceil(len(edge_inds) / nrows))
    fig, axes_ = plt.subplots(
        nrows,
        ncols,
        figsize=figsize,
        tight_layout=True,
        squeeze=False,
    )
    axes = axes_.flatten()
    for ax in axes:
        ax.axis("off")
    for n, ind in enumerate(edge_inds):
        i1, i2 = graph[ind]
        w_tr = w_train[ind]
        b_tr = b_train[ind]
        sep, _ = crossvalutils._calc_separability(b_tr, w_tr, metric="auc")
        axes[n].text(
            0.5,
            0.8,
            f"{bpts[i1]}{bpts[i2]}\n{sep:.2f}",
            size=8,
            ha="center",
            transform=axes[n].transAxes,
        )
        _plot_paf_performance(w_tr, b_tr, ax=axes[n], kde=False)
    axes[0].set_xticks([])
    axes[0].set_yticks([])
    if not output_name:
        output_name = "costdist.jpg"
    fig.savefig(output_name, dpi=600)

plot_trajectories

plot_trajectories(
    config: str | Path,
    videos: list[str | Path],
    video_extensions: str | Sequence[str] | None = None,
    shuffle=1,
    trainingsetindex=0,
    filtered=False,
    displayedbodyparts="all",
    displayedindividuals="all",
    showfigures=False,
    destfolder=None,
    modelprefix="",
    imagetype=".png",
    resolution=100,
    linewidth=1.0,
    track_method="",
    pcutoff: float | None = None,
    **kwargs
)

Plots the trajectories of various bodyparts across the video.

Parameters:

Name Type Description Default

config

str | Path

Full path of the config.yaml file.

required

videos

list[str | Path]

Full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored.

required

video_extensions

str | Sequence[str] | None

Controls how videos are filtered, based on file extension. File paths and directory contents are treated differently: - None (default): file paths are accepted as-is; directories are scanned for files with a recognized video extension. - str or Sequence[str] (e.g. "mp4" or ["mp4", "avi"]): both file paths and directory contents are filtered by the given extension(s). Defaults to None.

None

shuffle

int

Integer specifying the shuffle index of the training dataset. Defaults to 1.

1

trainingsetindex

int

Integer specifying which TrainingsetFraction to use. Note that TrainingFraction is a list in config.yaml. Defaults to 0.

0

filtered

bool

Boolean variable indicating if filtered output should be plotted rather than frame-by-frame predictions. Filtered version can be calculated with deeplabcut.filterpredictions. Defaults to False.

False

displayedbodyparts

list[str] or str

This select the body parts that are plotted in the video. Either all, then all body parts from config.yaml are used, or a list of strings that are a subset of the full list. E.g. ['hand','Joystick'] for the demo Reaching-Mackenzie-2018-08-30/config.yaml to select only these two body parts. Defaults to "all".

'all'

showfigures

bool

If True then plots are also displayed. Defaults to False.

False

destfolder

string or None

Destination folder for analysis data. If None, the path of the video is used. Defaults to None.

None

modelprefix

str

Directory containing the deeplabcut models to use when evaluating the network. By default, the models are assumed to exist in the project folder. Defaults to "".

''

imagetype

string

Output image format: '.tif', '.jpg', '.svg', ".png". Defaults to ".png".

'.png'

resolution

int

Specifies the resolution (in dpi) of saved figures. Note higher resolution figures take longer to generate. Defaults to 100.

100

linewidth

float

Specifies width of line for line and histogram plots. Defaults to 1.0.

1.0

track_method

string

Specifies the tracker used to generate the data. Empty by default (corresponding to a single animal project). For multiple animals, must be either 'box', 'skeleton', or 'ellipse' and will be taken from the config.yaml file if none is given. Defaults to "".

''

pcutoff

float | None

Overrides project pcutoff for plotting trajectories. Defaults to None.

None

kwargs

dict

Additional arguments. For torch-based shuffles, can be used to specify: - snapshot_index - detector_snapshot_index

{}

Returns:

Type Description

None

Examples:

To label the frames

deeplabcut.plot_trajectories(
    'home/alex/analysis/project/reaching-task/config.yaml',
    ['/home/alex/analysis/project/videos/reachingvideo1.avi'],
)
Source code in deeplabcut/utils/plotting.py
@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0")
def plot_trajectories(
    config: str | Path,
    videos: list[str | Path],
    video_extensions: str | Sequence[str] | None = None,
    shuffle=1,
    trainingsetindex=0,
    filtered=False,
    displayedbodyparts="all",
    displayedindividuals="all",
    showfigures=False,
    destfolder=None,
    modelprefix="",
    imagetype=".png",
    resolution=100,
    linewidth=1.0,
    track_method="",
    pcutoff: float | None = None,
    **kwargs,
):
    """Plots the trajectories of various bodyparts across the video.

    Args:
        config (str | Path): Full path of the config.yaml file.
        videos (list[str | Path]): Full paths to videos for analysis or a path to the directory, where all the
            videos with same extension are stored.
        video_extensions (str | Sequence[str] | None, optional): Controls how ``videos`` are
            filtered, based on file extension. File paths and directory contents are
            treated differently:
            - ``None`` (default): file paths are accepted as-is; directories are
              scanned for files with a recognized video extension.
            - ``str`` or ``Sequence[str]`` (e.g. ``"mp4"`` or ``["mp4", "avi"]``):
              both file paths and directory contents are filtered by the given
              extension(s). Defaults to None.
        shuffle (int, optional): Integer specifying the shuffle index of the training dataset. Defaults to 1.
        trainingsetindex (int, optional): Integer specifying which TrainingsetFraction to use.
            Note that TrainingFraction is a list in config.yaml. Defaults to 0.
        filtered (bool, optional): Boolean variable indicating if filtered output should be plotted rather than
            frame-by-frame predictions. Filtered version can be calculated with
            ``deeplabcut.filterpredictions``. Defaults to False.
        displayedbodyparts (list[str] or str, optional): This select the body parts that are plotted in the video.
            Either ``all``, then all body parts from config.yaml are used,
            or a list of strings that are a subset of the full list.
            E.g. ['hand','Joystick'] for the demo Reaching-Mackenzie-2018-08-30/config.yaml
            to select only these two body parts. Defaults to "all".
        showfigures (bool, optional): If ``True`` then plots are also displayed. Defaults to False.
        destfolder (string or None, optional): Destination folder for analysis data. If
            ``None``, the path of the video is used. Defaults to None.
        modelprefix (str, optional): Directory containing the deeplabcut models to use when evaluating the network.
            By default, the models are assumed to exist in the project folder. Defaults to "".
        imagetype (string, optional): Output image format: '.tif', '.jpg', '.svg',
            ".png". Defaults to ".png".
        resolution (int, optional): Specifies the resolution (in dpi) of saved figures.
            Note higher resolution figures take longer to generate. Defaults to 100.
        linewidth (float, optional): Specifies width of line for line and histogram plots. Defaults to 1.0.
        track_method (string, optional): Specifies the tracker used to generate the data.
            Empty by default (corresponding to a single animal project).
            For multiple animals, must be either 'box', 'skeleton', or 'ellipse' and will
            be taken from the config.yaml file if none is given. Defaults to "".
        pcutoff (float | None, optional): Overrides project pcutoff for plotting trajectories. Defaults to None.
        kwargs (dict, optional): Additional arguments.
            For torch-based shuffles, can be used to specify:
                - snapshot_index
                - detector_snapshot_index

    Returns:
        None

    Examples:
        To label the frames

            deeplabcut.plot_trajectories(
                'home/alex/analysis/project/reaching-task/config.yaml',
                ['/home/alex/analysis/project/videos/reachingvideo1.avi'],
            )
    """
    config = Path(config)
    if destfolder is not None:
        destfolder = Path(destfolder)
    cfg = auxiliaryfunctions.read_config(config)

    if pcutoff is None:
        pcutoff = cfg["pcutoff"]

    track_method = auxfun_multianimal.get_track_method(cfg, track_method=track_method)

    trainFraction = cfg["TrainingFraction"][trainingsetindex]
    DLCscorer, DLCscorerlegacy = auxiliaryfunctions.get_scorer_name(
        cfg,
        shuffle,
        trainFraction,
        modelprefix=modelprefix,
        **kwargs,
    )  # automatically loads corresponding model (even training iteration based on snapshot index)
    bodyparts = auxiliaryfunctions.intersection_of_body_parts_and_ones_given_by_user(cfg, displayedbodyparts)
    individuals = auxfun_multianimal.IntersectionofIndividualsandOnesGivenbyUser(cfg, displayedindividuals)
    Videos = collect_video_paths(videos, extensions=video_extensions)
    if not len(Videos):
        print("No videos found. Make sure you passed a list of videos and that the video_extensions filter is right.")
        return

    failures, multianimal_errors = [], []
    for video in Videos:
        if destfolder is None:
            videofolder = str(Path(video).parents[0])
        else:
            videofolder = destfolder

        vname = str(Path(video).stem)
        print("Loading ", video, "and data.")
        try:
            df, filepath, _, suffix = auxiliaryfunctions.load_analyzed_data(
                videofolder, vname, DLCscorer, filtered, track_method
            )
            tmpfolder = str(Path(videofolder) / "plot-poses" / vname)
            _plot_trajectories(
                filepath,
                bodyparts,
                individuals,
                showfigures,
                resolution,
                linewidth,
                cfg["colormap"],
                cfg["alphavalue"],
                pcutoff,
                suffix,
                imagetype,
                tmpfolder,
            )
        except FileNotFoundError as e:
            print(e)
            failures.append(video)
            if track_method != "":
                # In a multi animal scenario, show more verbose errors.
                try:
                    _ = auxiliaryfunctions.load_detection_data(video, DLCscorer, track_method)
                    error_message = 'Call "deeplabcut.stitch_tracklets() prior to plotting the trajectories.'
                except FileNotFoundError as e:
                    print(e)
                    error_message = (
                        f"Make sure {video} was previously analyzed, and that "
                        "detections were successively converted to tracklets using "
                        '"deeplabcut.convert_detections2tracklets()" and "deeplabcut.stitch_tracklets()".'
                    )
                multianimal_errors.append(error_message)

    if len(failures) > 0:
        # Some videos were not evaluated.
        failed_videos = ",".join(failures)
        if len(multianimal_errors) > 0:
            verbose_error = ": " + " ".join(multianimal_errors)
        else:
            verbose_error = "."
        print(
            f"Plots could not be created for {failed_videos}. "
            f"Videos were not evaluated with the current scorer {DLCscorer}" + verbose_error
        )
    else:
        print('Plots created! Please check the directory "plot-poses" within the video directory')

proc_video

proc_video(
    videos,
    destfolder,
    filtered,
    DLCscorer,
    DLCscorerlegacy,
    track_method,
    cfg,
    individuals,
    color_by,
    bodyparts,
    codec,
    bodyparts2connect,
    trailpoints,
    save_frames,
    outputframerate,
    Frames2plot,
    draw_skeleton,
    skeleton_color,
    displaycropped,
    fastmode,
    keypoints_only,
    overwrite,
    video,
    init_weights="",
    pcutoff: float | None = None,
    confidence_to_alpha: Callable[[float], float] | None = None,
    plot_bboxes: bool = True,
    bboxes_pcutoff: float = 0.6,
)

Helper function for create_labeled_video.

Returns:

Name Type Description
bool

True if a video is successfully created.

Source code in deeplabcut/utils/make_labeled_video.py
def proc_video(
    videos,
    destfolder,
    filtered,
    DLCscorer,
    DLCscorerlegacy,
    track_method,
    cfg,
    individuals,
    color_by,
    bodyparts,
    codec,
    bodyparts2connect,
    trailpoints,
    save_frames,
    outputframerate,
    Frames2plot,
    draw_skeleton,
    skeleton_color,
    displaycropped,
    fastmode,
    keypoints_only,
    overwrite,
    video,
    init_weights="",
    pcutoff: float | None = None,
    confidence_to_alpha: Callable[[float], float] | None = None,
    plot_bboxes: bool = True,
    bboxes_pcutoff: float = 0.6,
):
    """Helper function for create_labeled_video.

    Returns:
        bool: ``True`` if a video is successfully created.
    """
    videofolder = Path(video).parent
    if destfolder is None:
        destfolder = videofolder  # where your folder with videos is.
    else:
        destfolder = Path(destfolder)

    if pcutoff is None:
        pcutoff = cfg["pcutoff"]

    auxiliaryfunctions.attempt_to_make_folder(destfolder)

    os.chdir(destfolder)  # THE VIDEO IS STILL IN THE VIDEO FOLDER
    print(f"Starting to process video: {video}")
    vname = str(Path(video).stem)

    if init_weights != "":
        DLCscorer = "DLC_" + Path(init_weights).stem
        DLCscorerlegacy = "DLC_" + Path(init_weights).stem

    if filtered:
        videooutname1 = destfolder / f"{vname}{DLCscorer}filtered_labeled.mp4"
        videooutname2 = destfolder / f"{vname}{DLCscorerlegacy}filtered_labeled.mp4"
    else:
        videooutname1 = destfolder / f"{vname}{DLCscorer}_labeled.mp4"
        videooutname2 = destfolder / f"{vname}{DLCscorerlegacy}_labeled.mp4"

    if (videooutname1.is_file() or videooutname2.is_file()) and not overwrite:
        print(f"Labeled video {vname} already created.")
        return True
    else:
        print(f"Loading {video} and data.")
        try:
            df, filepath, _, _ = auxiliaryfunctions.load_analyzed_data(
                destfolder, vname, DLCscorer, filtered, track_method
            )
            metadata = auxiliaryfunctions.load_video_metadata(destfolder, vname, DLCscorer)
            if cfg.get("multianimalproject", False):
                s = "_id" if color_by == "individual" else "_bp"
            else:
                s = ""

            videooutname = filepath.replace(".h5", f"{s}_p{int(100 * pcutoff)}_labeled.mp4")
            if Path(videooutname).is_file() and not overwrite:
                print("Labeled video already created. Skipping...")
                return

            if individuals != "all":
                if isinstance(individuals, str):
                    individuals = [individuals]

                if all(individuals) and "individuals" in df.columns.names:
                    mask = df.columns.get_level_values("individuals").isin(individuals)
                    df = df.loc[:, mask]

            cropping = metadata["data"]["cropping"]
            [x1, x2, y1, y2] = metadata["data"]["cropping_parameters"]
            labeled_bpts = [bp for bp in df.columns.get_level_values("bodyparts").unique() if bp in bodyparts]

            # The full data file is not created for single-animal TensorFlow models
            try:
                full_data = auxiliaryfunctions.load_video_full_data(destfolder, vname, DLCscorer)
                frames_dict = {
                    int(key.replace("frame", "")): value
                    for key, value in full_data.items()
                    if key.startswith("frame") and key[5:].isdigit()
                }
                bboxes_list = None
                if "bboxes" in frames_dict.get(min(frames_dict.keys()), {}):
                    bboxes_list = [frames_dict[key] for key in sorted(frames_dict.keys())]
            except FileNotFoundError:
                bboxes_list = None

            if keypoints_only:
                # Mask rather than drop unwanted bodyparts to ensure consistent coloring
                mask = df.columns.get_level_values("bodyparts").isin(bodyparts)
                df.loc[:, ~mask] = np.nan
                inds = None
                if bodyparts2connect:
                    all_bpts = df.columns.get_level_values("bodyparts")[::3]
                    inds = get_segment_indices(bodyparts2connect, all_bpts)
                clip = vp(fname=video, fps=outputframerate)
                create_video_with_keypoints_only(
                    df,
                    videooutname,
                    inds,
                    pcutoff,
                    cfg["dotsize"],
                    cfg["alphavalue"],
                    skeleton_color=skeleton_color,
                    color_by=color_by,
                    colormap=cfg["colormap"],
                    fps=clip.fps,
                )
                clip.close()
            elif not fastmode:
                tmpfolder = str(Path(str(videofolder)) / ("temp-" + vname))
                if save_frames:
                    auxiliaryfunctions.attempt_to_make_folder(tmpfolder)
                clip = vp(video)
                CreateVideoSlow(
                    videooutname,
                    clip,
                    df,
                    tmpfolder,
                    cfg["dotsize"],
                    cfg["colormap"],
                    cfg["alphavalue"],
                    pcutoff,
                    trailpoints,
                    cropping,
                    x1,
                    x2,
                    y1,
                    y2,
                    save_frames,
                    labeled_bpts,
                    outputframerate,
                    Frames2plot,
                    bodyparts2connect,
                    skeleton_color,
                    draw_skeleton,
                    displaycropped,
                    color_by,
                    plot_bboxes=plot_bboxes,
                    bboxes_list=bboxes_list,
                    bboxes_pcutoff=bboxes_pcutoff,
                )
                clip.close()
            else:
                create_video(
                    video,
                    filepath,
                    keypoints2show=labeled_bpts,
                    animals2show=individuals,
                    bbox=(x1, x2, y1, y2),
                    codec=codec,
                    output_path=videooutname,
                    pcutoff=pcutoff,
                    dotsize=cfg["dotsize"],
                    cmap=cfg["colormap"],
                    color_by=color_by,
                    skeleton_edges=bodyparts2connect,
                    skeleton_color=skeleton_color,
                    trailpoints=trailpoints,
                    fps=outputframerate,
                    display_cropped=displaycropped,
                    confidence_to_alpha=confidence_to_alpha,
                    plot_bboxes=plot_bboxes,
                    bboxes_list=bboxes_list,
                    bboxes_pcutoff=bboxes_pcutoff,
                )

            return True

        except FileNotFoundError as e:
            print(e)
            return False

read_inferencecfg

read_inferencecfg(path_inference_config, cfg)

Load inferencecfg or initialize it.

Source code in deeplabcut/utils/auxfun_multianimal.py
def read_inferencecfg(path_inference_config, cfg):
    """Load inferencecfg or initialize it."""
    try:
        inferencecfg = auxiliaryfunctions.read_plainconfig(path_inference_config)
    except FileNotFoundError:
        inferencecfg = form_default_inferencecfg(cfg)
        auxiliaryfunctions.write_plainconfig(str(path_inference_config), dict(inferencecfg))
    return inferencecfg

read_pickle

read_pickle(filename)

Read the pickle file.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def read_pickle(filename):
    """Read the pickle file."""
    with Path(filename).open("rb") as handle:
        return pickle.load(handle)

read_plainconfig

read_plainconfig(configname: str | Path) -> dict

Load a YAML config (alias for read_config_as_dict). See deeplabcut.core.config.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def read_plainconfig(configname: str | Path) -> dict:
    """Load a YAML config (alias for read_config_as_dict). See deeplabcut.core.config."""
    return core_config.read_config_as_dict(config_path=configname)

renamed_parameter

renamed_parameter(*, old: str, new: str, since: str | None = None) -> Callable[[Callable[P, R]], Callable[P, R]]

Support a renamed keyword argument while warning callers to update.

Parameters:

Name Type Description Default

old

str

The old parameter name that callers may still pass.

required

new

str

The current parameter name the function actually accepts.

required

since

str | None

Version when the rename happened.

None
Rules
  • new must be the name used in the function signature and all internal call-sites. old must not appear in the signature.
  • Do not chain renames. If A was renamed to B and B is later renamed to C, replace the A→B decorator with A→C directly rather than stacking a second decorator. Example: @renamed_parameter(old="A", new="C", since="12.4.0") @renamed_parameter(old="B", new="C", since="13.0.0") def func(*, C: int): print(f"C={C}")
  • Multiple independent renames on the same function (e.g. batchsize→batch_size and videotype→video_extensions) are fine as long as they do not form a chain.
  • This decorator only intercepts keyword arguments. Positional arguments are passed through unchanged; renaming a parameter that callers commonly pass positionally will not be caught.
Source code in deeplabcut/core/deprecation.py
def renamed_parameter(
    *,
    old: str,
    new: str,
    since: str | None = None,
) -> Callable[[Callable[P, R]], Callable[P, R]]:
    """Support a renamed keyword argument while warning callers to update.

    Args:
        old: The old parameter name that callers may still pass.
        new: The current parameter name the function actually accepts.
        since: Version when the rename happened.

    Rules:
        - ``new`` must be the name used in the function signature and all
          internal call-sites.  ``old`` must **not** appear in the signature.
        - Do **not** chain renames.  If ``A`` was renamed to ``B`` and ``B``
          is later renamed to ``C``, replace the ``A→B`` decorator with
          ``A→C`` directly rather than stacking a second decorator.
            Example:
                @renamed_parameter(old="A", new="C", since="12.4.0")
                @renamed_parameter(old="B", new="C", since="13.0.0")
                def func(*, C: int):
                    print(f"C={C}")
        - Multiple independent renames on the same function (e.g.
          ``batchsize→batch_size`` *and* ``videotype→video_extensions``) are fine
          as long as they do not form a chain.
        - This decorator only intercepts **keyword** arguments.  Positional
          arguments are passed through unchanged; renaming a parameter that
          callers commonly pass positionally will not be caught.
    """

    def decorator(fn: Callable[P, R]) -> Callable[P, R]:
        sig = inspect.signature(fn)

        # Guard: disallow chaining renames (A→B stacked on top of B→C).
        existing = getattr(fn, "__deprecated_params__", ())
        for prev in existing:
            if prev.old_parameter == new:
                raise ValueError(
                    f"@renamed_parameter: chaining renames is not allowed. "
                    f"'{old}' → '{new}' would chain with the existing "
                    f"'{prev.old_parameter}' → '{prev.new_parameter}' rename "
                    f"on {fn.__qualname__}. "
                    f"Use '{old}' → '{prev.new_parameter}' directly instead."
                )

        # Guard: 'new' must actually exist in the function's signature.
        if new not in sig.parameters:
            raise ValueError(
                f"@renamed_parameter: '{new}' is not a parameter of "
                f"{fn.__qualname__}. "
                f"Available parameters: {list(sig.parameters)}"
            )

        # Guard: 'old' must NOT exist in the signature.
        if old in sig.parameters:
            raise ValueError(
                f"@renamed_parameter: '{old}' is still a parameter of "
                f"{fn.__qualname__}. Use either old name or new name: '{new}'."
            )

        info = DeprecationInfo(
            kind="parameter",
            target=fn.__qualname__,
            since=since,
            old_parameter=old,
            new_parameter=new,
        )
        message = info.format_message()

        @functools.wraps(fn)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            if old in kwargs:
                if new in kwargs:
                    raise TypeError(f"{fn.__qualname__} received both '{old}' and '{new}'. Use only '{new}'.")
                warnings.warn(message, DLCDeprecationWarning, stacklevel=2)
                kwargs[new] = kwargs.pop(old)
            return fn(*args, **kwargs)

        wrapper.__deprecated_params__ = (*existing, info)
        return wrapper

    return decorator

reorder_individuals_in_df

reorder_individuals_in_df(df: DataFrame, order: list) -> pd.DataFrame

Reorders data of df to match the order given in a list.

Parameters:

Name Type Description Default

df

DataFrame

Data from tracked .h5 file.

required

order

list of str

Desired order of individuals.

required

Returns:

Type Description
DataFrame

pd.DataFrame: Reordered DataFrame.

Source code in deeplabcut/utils/auxfun_multianimal.py
def reorder_individuals_in_df(df: pd.DataFrame, order: list) -> pd.DataFrame:
    """Reorders data of df to match the order given in a list.

    Args:
        df (pd.DataFrame): Data from tracked .h5 file.
        order (list of str): Desired order of individuals.

    Returns:
        pd.DataFrame: Reordered DataFrame.
    """
    columns = df.columns
    inds = df.index

    data = df.loc(axis=1)[:, order].to_numpy()
    df = pd.DataFrame(data, columns=columns, index=inds)

    return df

returnlabelingdata

returnlabelingdata(config)

Returns a specific labeleing data set -- the user will be asked which one.

Source code in deeplabcut/utils/auxfun_multianimal.py
def returnlabelingdata(config):
    """Returns a specific labeleing data set -- the user will be asked which one."""
    cfg = auxiliaryfunctions.read_config(config)
    videos = cfg["video_sets"].keys()
    video_names = [Path(i).stem for i in videos]
    folders = [Path(config).parent / "labeled-data" / Path(i) for i in video_names]
    for folder in folders:
        print("Do you want to get the data for folder:", folder, "?")
        askuser = input("yes/no")
        if askuser == "y" or askuser == "yes" or askuser == "Ja" or askuser == "ha":  # multilanguage support :)
            fn = folder / ("CollectedData_" + cfg["scorer"] + ".h5")
            Data = pd.read_hdf(fn)
            return Data

rotate_video

rotate_video(vname, angle, rotatecw='Arbitrary', outsuffix='rotated', outpath=None)

Auxiliary function to rotate a video and output it to the same folder with "outsuffix" appended in its name. Angle is in degrees.

Parameters:

Name Type Description Default

vname

string

A string containing the full path of the video.

required

angle

float

Angle to rotate by in degrees. Negative values rotate counter-clockwise.

required

rotatecw

str

Default "Arbitrary", rotates clockwise if "Yes", "Arbitrary" for arbitrary rotation by specified angle.

'Arbitrary'

outsuffix

str

Suffix for output videoname (see example).

'rotated'

outpath

str

Output path for saving video to (by default, same folder as the video).

None

Returns:

Name Type Description
str

The full path to the rotated video.

Examples:

Linux/MacOs:

deeplabcut.rotate_video("/data/videos/mouse1.avi", angle=90)

Rotates the video by 90 degrees and saves it in /data/videos as mouse1rotated.avi

Windows:

shortenedvideoname = deeplabcut.rotate_video(
    "C:\yourusername\rig-95\Videos\reachingvideo1.avi",
    angle=180,
    rotatecw="Yes",
)

Rotates the video by 180 degrees and saves it in C:\yourusername\rig-95\Videos as reachingvideo1rotated.avi

Source code in deeplabcut/utils/auxfun_videos.py
def rotate_video(vname, angle, rotatecw="Arbitrary", outsuffix="rotated", outpath=None):
    """Auxiliary function to rotate a video and output it to the same folder with
    "outsuffix" appended in its name. Angle is in degrees.

    Args:
        vname (string): A string containing the full path of the video.
        angle (float): Angle to rotate by in degrees. Negative values rotate
            counter-clockwise.
        rotatecw (str): Default "Arbitrary", rotates clockwise if "Yes", "Arbitrary" for
            arbitrary rotation by specified angle.
        outsuffix (str): Suffix for output videoname (see example).
        outpath (str): Output path for saving video to (by default, same folder as the
            video).

    Returns:
        str: The full path to the rotated video.

    Examples:
        Linux/MacOs:

            deeplabcut.rotate_video("/data/videos/mouse1.avi", angle=90)

        Rotates the video by 90 degrees and saves it in /data/videos as mouse1rotated.avi

        Windows:

            shortenedvideoname = deeplabcut.rotate_video(
                "C:\\yourusername\\rig-95\\Videos\\reachingvideo1.avi",
                angle=180,
                rotatecw="Yes",
            )

        Rotates the video by 180 degrees and saves it in
        C:\\yourusername\\rig-95\\Videos as reachingvideo1rotated.avi
    """
    writer = VideoWriter(vname)
    return writer.rotate(angle, rotatecw, outsuffix, outpath)

safe_resolve

safe_resolve(path: Path) -> Path

Return a resolved Path that is safe to use with str-based I/O.

Prefers Path.resolve() so that symlinks are followed (useful on Linux). Falls back to Path.absolute() when resolve() fails or the resolved path cannot be stat'd via its plain string form — e.g. on Windows 11 + SMB network drives where resolve() may return an unusable \?\Volume{GUID}... form.

See https://github.com/DeepLabCut/DeepLabCut/issues/3348

Source code in deeplabcut/utils/auxiliaryfunctions.py
def safe_resolve(path: Path) -> Path:
    """Return a resolved Path that is safe to use with str-based I/O.

    Prefers Path.resolve() so that symlinks are followed (useful on Linux).
    Falls back to Path.absolute() when resolve() fails or the resolved path
    cannot be stat'd via its plain string form — e.g. on Windows 11 + SMB
    network drives where resolve() may return an unusable
    \\\\?\\Volume{GUID}\\... form.

    See https://github.com/DeepLabCut/DeepLabCut/issues/3348
    """
    try:
        resolved = path.resolve()
        os.stat(os.fspath(resolved))
        return resolved
    except OSError as exc:
        fallback = path.absolute()
        logger.debug(
            "safe_resolve: using absolute() fallback for %s (%s)",
            path,
            exc,
        )
        return fallback

save_data

save_data(PredicteData, metadata, dataname, pdindex, imagenames, save_as_csv)

Save predicted data as h5 file and metadata as pickle file; created by predict_videos.py.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def save_data(PredicteData, metadata, dataname, pdindex, imagenames, save_as_csv):
    """Save predicted data as h5 file and metadata as pickle file; created by
    predict_videos.py.
    """
    dataname = Path(dataname)
    DataMachine = pd.DataFrame(PredicteData, columns=pdindex, index=imagenames)
    if save_as_csv:
        print("Saving csv poses!")
        DataMachine.to_csv(dataname.with_suffix(".csv"))
    DataMachine.to_hdf(dataname, key="df_with_missing", format="table", mode="w")
    with dataname.with_name(dataname.stem + "_meta.pickle").open("wb") as f:
        # Pickle the 'data' dictionary using the highest protocol available.
        pickle.dump(metadata, f, pickle.HIGHEST_PROTOCOL)

write_pickle

write_pickle(filename, data)

Write the pickle file.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def write_pickle(filename, data):
    """Write the pickle file."""
    with Path(filename).open("wb") as handle:
        pickle.dump(data, handle, protocol=pickle.HIGHEST_PROTOCOL)

write_plainconfig

write_plainconfig(configname: str | Path, cfg: dict, overwrite: bool = True) -> None

Write a config dict to YAML (alias for write_config). See deeplabcut.core.config.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def write_plainconfig(configname: str | Path, cfg: dict, overwrite: bool = True) -> None:
    """Write a config dict to YAML (alias for write_config). See deeplabcut.core.config."""
    core_config.write_config(config_path=configname, config=cfg, overwrite=overwrite)