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
crossvalutils
deprecation
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)

trainingsetmanipulation
video_processor

Author: Hao Wu

visualization

DeepLabCut2.0 Toolbox (deeplabcut.org)

Classes:

Name Description
DLCDeprecationWarning

Project-specific deprecation warning. Helps with filtering.

VideoProcessor

Base class for a video processing unit, implementation is required for video

VideoProcessorCV

OpenCV implementation of VideoProcessor requires opencv-python==3.4.0.12.

VideoWriter
vp

OpenCV implementation of VideoProcessor requires opencv-python==3.4.0.12.

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

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

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

Converts single animal annotation file into a multianimal annotation file,

convert_single2multiplelegacyAM

Convert multi animal to single animal code and vice versa.

convertcsv2h5

Convert (image) annotation files in folder labeled-data from csv to h5.

create_config_template

Creates a template for config.yaml file.

create_config_template_3d

Creates a template for config.yaml file for 3d project.

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.

edit_config

Convenience function to edit and save a config file from a dictionary.

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

Args:

get_deeplabcut_path

Get path of where deeplabcut is currently running.

get_evaluation_folder

Args:

get_immediate_subdirectories

Get list of immediate subdirectories.

get_model_folder

Args:

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

Args:

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_videos.

read_config

Reads structured config file defining a project.

read_inferencecfg

Load inferencecfg or initialize it.

read_pickle

Read the pickle file.

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

save_data

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

write_config

Write structured config file.

write_config_3d

Write structured 3D config file.

write_pickle

Write the pickle file.

DLCDeprecationWarning

Bases: DeprecationWarning

Project-specific deprecation warning. Helps with filtering.

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

VideoProcessor

Base class for a video processing unit, implementation is required for video loading and saving.

sh and sw are the output height and width respectively.

Methods:

Name Description
close

Implement your own.

create_video

Implement your own.

get_info

Implement your own.

get_video

Implement your own.

save_frame

Implement your own.

Source code in deeplabcut/utils/video_processor.py
class VideoProcessor:
    """Base class for a video processing unit, implementation is required for video
    loading and saving.

    sh and sw are the output height and width respectively.
    """

    def __init__(self, fname="", sname="", nframes=-1, fps=None, codec="X264", sh="", sw=""):
        self.fname = fname
        self.sname = sname
        self.nframes = nframes
        self.codec = codec
        self.h = 0
        self.w = 0
        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 == "" and sw == "":
                    self.sh = self.h
                    self.sw = self.w
                else:
                    self.sw = sw
                    self.sh = sh
                self.svid = self.create_video()

        except Exception as ex:
            print("Error: %s", ex)

        if fps is not None:  # Overwrite the video's FPS
            self.FPS = fps

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

    def height(self):
        return self.h

    def width(self):
        return self.w

    def fps(self):
        return self.FPS

    def counter(self):
        return self.i

    def frame_count(self):
        return self.nframes

    def get_video(self):
        """Implement your own."""
        pass

    def get_info(self):
        """Implement your own."""
        pass

    def create_video(self):
        """Implement your own."""
        pass

    def _read_frame(self):
        """Implement your own."""
        pass

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

    def close(self):
        """Implement your own."""
        pass

close

close()

Implement your own.

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

create_video

create_video()

Implement your own.

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

get_info

get_info()

Implement your own.

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

get_video

get_video()

Implement your own.

Source code in deeplabcut/utils/video_processor.py
def get_video(self):
    """Implement your own."""
    pass

save_frame

save_frame(frame)

Implement your own.

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

VideoProcessorCV

Bases: VideoProcessor

OpenCV implementation of VideoProcessor requires opencv-python==3.4.0.12.

Source code in deeplabcut/utils/video_processor.py
class VideoProcessorCV(VideoProcessor):
    """OpenCV implementation of VideoProcessor requires opencv-python==3.4.0.12."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def get_video(self):
        return cv2.VideoCapture(self.fname)

    def get_info(self):
        self.w = int(self.vid.get(cv2.CAP_PROP_FRAME_WIDTH))
        self.h = 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):
        fourcc = cv2.VideoWriter_fourcc(*self.codec)
        return cv2.VideoWriter(self.sname, fourcc, self.FPS, (self.sw, self.sh), True)

    def _read_frame(self):  # return RGB (rather than BGR)!
        # return cv2.cvtColor(np.flip(self.vid.read()[1],2), cv2.COLOR_BGR2RGB)
        success, frame = self.vid.read()
        if not success:
            return frame
        return np.flip(frame, 2)

    def save_frame(self, frame):
        if frame is not None:
            self.svid.write(np.flip(frame, 2))

    def close(self):
        if hasattr(self, "svid") and self.svid is not None:
            self.svid.release()
        if hasattr(self, "vid") and self.vid is not None:
            self.vid.release()

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.

        Parameter
        ----------
        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 ('short' by default).

        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.

        Parameters
        ----------
        n_splits : int
            Number of shorter videos to produce

        suffix: str, optional
            String added to the name of the splits ('short' by default).

        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 os.path.join(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.

Parameter

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

str

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

str, optional

String added to the name of the shortened video ('short' by default).

str, optional

Folder the video is saved into (by default, same as the original video)

Returns

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.

    Parameter
    ----------
    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 ('short' by default).

    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

n_splits : int Number of shorter videos to produce

str, optional

String added to the name of the splits ('short' by default).

str, optional

Folder the video splits are saved into (by default, same as the original video)

Returns

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.

    Parameters
    ----------
    n_splits : int
        Number of shorter videos to produce

    suffix: str, optional
        String added to the name of the splits ('short' by default).

    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 implementation of VideoProcessor requires opencv-python==3.4.0.12.

Source code in deeplabcut/utils/video_processor.py
class VideoProcessorCV(VideoProcessor):
    """OpenCV implementation of VideoProcessor requires opencv-python==3.4.0.12."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def get_video(self):
        return cv2.VideoCapture(self.fname)

    def get_info(self):
        self.w = int(self.vid.get(cv2.CAP_PROP_FRAME_WIDTH))
        self.h = 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):
        fourcc = cv2.VideoWriter_fourcc(*self.codec)
        return cv2.VideoWriter(self.sname, fourcc, self.FPS, (self.sw, self.sh), True)

    def _read_frame(self):  # return RGB (rather than BGR)!
        # return cv2.cvtColor(np.flip(self.vid.read()[1],2), cv2.COLOR_BGR2RGB)
        success, frame = self.vid.read()
        if not success:
            return frame
        return np.flip(frame, 2)

    def save_frame(self, frame):
        if frame is not None:
            self.svid.write(np.flip(frame, 2))

    def close(self):
        if hasattr(self, "svid") and self.svid is not None:
            self.svid.release()
        if hasattr(self, "vid") and self.vid is not None:
            self.vid.release()

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.groupby(level="individuals", axis=1).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.groupby(level="individuals", axis=1).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.

Returns the full path to the downsampled video!

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

Parameter

vname : string A string containing the full path of the video.

int

width of output video

int

height of output video.

origin_x, origin_y: int x- and y- axis origin of bounding box for cropping.

str

Suffix for output videoname (see example).

str

Output path for saving video to (by default will be the same folder as the 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.

    Returns the full path to the downsampled video!

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

    Parameter
    ----------
    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, origin_y: int
        x- and 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 will be the same folder as the 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.

Returns the full path to the downsampled video!

Parameter

vname : string A string containing the full path of the video.

int

width of output video

int

height of output video.

str

Suffix for output videoname (see example).

str

Output path for saving video to (by default will be the same folder as the video)

str

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

float

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

Examples

Linux/MacOs

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

Downsamples the video using default values and saves it in /data/videos as mouse1cropped.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.

    Returns the full path to the downsampled video!

    Parameter
    ----------
    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 will be the 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

    Examples
    ----------

    Linux/MacOs
    >>> deeplabcut.DownSampleVideo('/data/videos/mouse1.avi')

    Downsamples the video using default values and saves it in /data/videos as mouse1cropped.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)

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 LoadFullMultiAnimalData(dataname):
    """Save predicted data as h5 file and metadata as pickle file; created by
    predict_videos.py."""
    data_file = dataname.split(".h5")[0] + "_full.pickle"
    try:
        with open(data_file, "rb") as handle:
            data = pickle.load(handle)
    except (pickle.UnpicklingError, FileNotFoundError):
        data = shelve.open(data_file, flag="r")
    with open(data_file.replace("_full.", "_meta."), "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(
        os.path.join(tmpfolder, "trajectory" + suffix),
        bbox_inches="tight",
        dpi=resolution,
    )
    fig2.savefig(os.path.join(tmpfolder, "plot" + suffix), bbox_inches="tight", dpi=resolution)
    fig3.savefig(
        os.path.join(tmpfolder, "plot-likelihood" + suffix),
        bbox_inches="tight",
        dpi=resolution,
    )
    fig4.savefig(os.path.join(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."""
    data_path = dataname.split(".h5")[0] + suffix + ".pickle"
    metadata_path = dataname.split(".h5")[0] + "_meta.pickle"

    with open(data_path, "wb") as f:
        pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
    with open(metadata_path, "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).

Returns the full path to the shortened video!

Parameter

videos : string A string containing the full paths of the video.

hours:minutes:seconds

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

hours:minutes:seconds

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

str

Suffix for output videoname (see example).

str

Output path for saving video to (by default will be the same folder as the 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 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).

    Returns the full path to the shortened video!

    Parameter
    ----------
    videos : string
        A string containing the full paths of the video.

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

    stop: hours:minutes:seconds
        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 will be the same folder as the 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 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, 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

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

Source code in deeplabcut/utils/conversioncode.py
def adapt_labeled_data_to_new_project(config_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
    ----------
    config_path : str
        The path to the config.yaml file.
    remove_old_bodyparts : bool (default = False)
        If True, the old bodyparts that are not in the new project will be removed from the dataframe.
    other_scorer : bool (default = False)
        If True, the labels will be converted to the new scorer.
    userfeedback : bool (default = True)
        If true the user will be asked specifically
        for each folder in labeled-data if the containing csv
        shall be converted to hdf format.
    """

    # 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 = os.path.join(project_path, "labeled-data", video_name)
        csv_files = [file for file in os.listdir(label_path) if file.endswith(".csv")]
        if not csv_files:
            print("No csv file in the folder:", label_path)
        else:
            csv_path = os.path.join(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


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

videotype: string, optional (default=.mp4) Only videos with this extension are screened.

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 \n in pixels, and the likelihood for each frame per body part.
    These arrays are stored in an efficient Hierarchical Data Format (HDF) \n 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
    ----------

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

    videotype: string, optional (default=.mp4)
        Only videos with this extension are screened.

    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 = list(auxiliaryfunctions.grab_files_in_folder(Path(videos[0]).parent, "h5", relative=False))
        else:
            h5_files = []
    else:
        h5_files = list(auxiliaryfunctions.grab_files_in_folder(video_folder, "h5", relative=False))
        videos = auxiliaryfunctions.grab_files_in_folder(video_folder, videotype, relative=False)

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

analyze_videos_converth5_to_nwb

analyze_videos_converth5_to_nwb(config, video_folder, videotype='.mp4', listofvideos=False)

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

Parameters

config : string Absolute path to the project YAML config file.

string

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

string, optional (default=.mp4)

Only videos with this extension are screened.

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_nwb(
    config,
    video_folder,
    videotype=".mp4",
    listofvideos=False,
):
    """Convert all h5 output data files in `video_folder` to NWB format.

    Parameters
    ----------
    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 (default=.mp4)
        Only videos with this extension are screened.

    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 = list(auxiliaryfunctions.grab_files_in_folder(Path(videos[0]).parent, "h5", relative=False))
        else:
            h5_files = []
    else:
        h5_files = list(auxiliaryfunctions.grab_files_in_folder(video_folder, "h5", relative=False))
        videos = auxiliaryfunctions.grab_files_in_folder(video_folder, videotype, relative=False)

    _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.
    """
    try:
        os.path.isdir(foldername)
    except TypeError:  # https://www.python.org/dev/peps/pep-0519/
        foldername = os.fspath(foldername)  # https://github.com/DeepLabCut/DeepLabCut/issues/105 (windows)

    if os.path.isdir(foldername):
        pass
    else:
        if recursive:
            os.makedirs(foldername)
        else:
            os.mkdir(foldername)

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!)
    """
    outdataname = os.path.join(folder, vname + DLCscorer + suffix + ".h5")
    sourcedataname = os.path.join(folder, vname + DLCscorer + ".h5")
    if os.path.isfile(outdataname):  # 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 = os.path.join(folder, vname + DLCscorerlegacy + suffix + ".h5")
        if os.path.isfile(odn):  # 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 = os.path.join(folder, vname + DLCscorerlegacy + ".h5")
            tracks = sourcedataname.replace(".h5", "tracks.h5")
            if os.path.isfile(sourcedataname):  # Was the video already analyzed?
                return True, outdataname, sourcedataname, DLCscorer
            elif os.path.isfile(sdn):  # was it analyzed with DLC<2.1?
                return True, odn, sdn, DLCscorerlegacy
            elif os.path.isfile(tracks):  # 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.resolve() 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, userfeedback=True, forceindividual=None)

Converts single animal annotation file into a multianimal annotation file, by introducing an individuals column with either the first individual in individuals list in config.yaml or whatever is passed via "forceindividual".


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

bool, optional

If this is set to false during automatic mode then frames for all videos are extracted. The user can set this to true, which will result in a dialog, where the user is asked for each video if (additional/any) frames from this video should be extracted. Use this, e.g. if you have already labeled some folders and want to extract data for new videos.

None default

If a string is given that is used in the individuals column.

Examples

Converts mulianimalbodyparts under the 'first individual' in individuals list in config.yaml and uniquebodyparts under 'single'

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


Converts mulianimalbodyparts 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, userfeedback=True, forceindividual=None):
    """
    Converts single animal annotation file into a multianimal annotation file,
    by introducing an individuals column with either the first individual
    in individuals list in config.yaml or whatever is passed via "forceindividual".

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

    userfeedback: bool, optional
            If this is set to false during automatic mode then frames for all videos are extracted. The user can set
            this to true, which will result in a dialog,
            where the user is asked for each video if (additional/any) frames from this video should be extracted. Use
            this, e.g. if you have already labeled
            some folders and want to extract data for new videos.

    forceindividual: None default
            If a string is given that is used in the individuals column.

    Examples
    --------
    Converts mulianimalbodyparts under the 'first individual' in individuals list in config.yaml
    and uniquebodyparts under 'single'
    >>> deeplabcut.convert2_maDLC('/socialrearing-task/config.yaml')

    --------
    Converts mulianimalbodyparts 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 = [trainingsetmanipulation._robust_path_split(i)[1] 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 = os.path.join(str(folder), "CollectedData_" + cfg["scorer"])
            Data = pd.read_hdf(fn + ".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 + "singleanimal.h5",
                key="df_with_missing",
            )
            Data.to_csv(fn + "singleanimal.csv")

            dataFrame.to_hdf(fn + ".h5", key="df_with_missing")
            dataFrame.to_csv(fn + ".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 = os.path.join(str(folder), "CollectedData_" + cfg["scorer"])
            Data = pd.read_hdf(fn + ".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 + "multianimal.h5",
                    key="df_with_missing",
                )
                Data.to_csv(fn + "multianimal.csv")

                DataFrame.to_hdf(
                    fn + ".h5",
                    key="df_with_missing",
                )
                DataFrame.to_csv(fn + ".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 + "singleanimal.h5",
                    key="df_with_missing",
                )
                Data.to_csv(fn + "singleanimal.csv")

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

convertcsv2h5

convertcsv2h5(config, userfeedback=True, scorer=None)

Convert (image) annotation files in folder labeled-data from csv to h5. This function 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.

string

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

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.

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')


Source code in deeplabcut/utils/conversioncode.py
def convertcsv2h5(config, userfeedback=True, scorer=None):
    """
    Convert (image) annotation files in folder labeled-data from csv to h5.
    This function 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.

    config : string
        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 = os.path.join(str(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 open(fn) 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_config_template

create_config_template(multianimal=False)

Creates a template for config.yaml file.

This specific order is preserved while saving as yaml file.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def create_config_template(multianimal=False):
    """Creates a template for config.yaml file.

    This specific order is preserved while saving as yaml file.
    """
    if multianimal:
        yaml_str = """\
# Project definitions (do not edit)
Task:
scorer:
date:
multianimalproject:
identity:
\n
# Project path (change when moving around)
project_path:
\n
# Default DeepLabCut engine to use for shuffle creation (either pytorch or tensorflow)
engine: pytorch
\n
# Annotation data set configuration (and individual video cropping parameters)
video_sets:
individuals:
uniquebodyparts:
multianimalbodyparts:
bodyparts:
\n
# Fraction of video to start/stop when extracting frames for labeling/refinement
start:
stop:
numframes2pick:
\n
# Plotting configuration
skeleton:
skeleton_color:
pcutoff:
dotsize:
alphavalue:
colormap:
\n
# Training,Evaluation and Analysis configuration
TrainingFraction:
iteration:
default_net_type:
default_augmenter:
default_track_method:
snapshotindex:
detector_snapshotindex:
batch_size:
\n
# Cropping Parameters (for analysis and outlier frame detection)
cropping:
#if cropping is true for analysis, then set the values here:
x1:
x2:
y1:
y2:
\n
# Refinement configuration (parameters from annotation dataset configuration also relevant in this stage)
corner2move2:
move2corner:
\n
# Conversion tables to fine-tune SuperAnimal weights
SuperAnimalConversionTables:
        """
    else:
        yaml_str = """\
# Project definitions (do not edit)
Task:
scorer:
date:
multianimalproject:
identity:
\n
# Project path (change when moving around)
project_path:
\n
# Default DeepLabCut engine to use for shuffle creation (either pytorch or tensorflow)
engine: pytorch
\n
# Annotation data set configuration (and individual video cropping parameters)
video_sets:
bodyparts:
\n
# Fraction of video to start/stop when extracting frames for labeling/refinement
start:
stop:
numframes2pick:
\n
# Plotting configuration
skeleton:
skeleton_color:
pcutoff:
dotsize:
alphavalue:
colormap:
\n
# Training,Evaluation and Analysis configuration
TrainingFraction:
iteration:
default_net_type:
default_augmenter:
snapshotindex:
detector_snapshotindex:
batch_size:
detector_batch_size:
\n
# Cropping Parameters (for analysis and outlier frame detection)
cropping:
#if cropping is true for analysis, then set the values here:
x1:
x2:
y1:
y2:
\n
# Refinement configuration (parameters from annotation dataset configuration also relevant in this stage)
corner2move2:
move2corner:
\n
# Conversion tables to fine-tune SuperAnimal weights
SuperAnimalConversionTables:
        """

    ruamelFile = YAML()
    cfg_file = ruamelFile.load(yaml_str)
    return cfg_file, ruamelFile

create_config_template_3d

create_config_template_3d()

Creates a template for config.yaml file for 3d project.

This specific order is preserved while saving as yaml file.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def create_config_template_3d():
    """Creates a template for config.yaml file for 3d project.

    This specific order is preserved while saving as yaml file.
    """
    yaml_str = """\
# Project definitions (do not edit)
Task:
scorer:
date:
\n
# Project path (change when moving around)
project_path:
\n
# Plotting configuration
skeleton: # Note that the pairs must be defined, as you want them linked!
skeleton_color:
pcutoff:
colormap:
dotsize:
alphaValue:
markerType:
markerColor:
\n
# Number of cameras, camera names, path of the config files, shuffle index and trainingsetindex used to analyze videos:
num_cameras:
camera_names:
scorername_3d: # Enter the scorer name for the 3D output
    """
    ruamelFile_3d = YAML()
    cfg_file_3d = ruamelFile_3d.load(yaml_str)
    return cfg_file_3d, ruamelFile_3d

create_labeled_video

create_labeled_video(
    config: str,
    videos: list[str],
    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

config : string Full path of the config.yaml file.

list[str]

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.

str | Sequence[str] | None, optional, default=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).

int, optional, default=1

Number of shuffles of training dataset.

int, optional, default=0

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

bool, optional, default=False

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

bool, optional, default=True

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).

bool, optional, default=False

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.

bool, optional, default=False

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.

List[int] or None, optional, default=None

If not None and save_frames=True then the frames corresponding to the index will be plotted. For example, Frames2plot=[0,11] will plot the first and the 12th frame.

list[str] or str, optional, default="all"

This selects the body parts that are 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.

list[str] or str, optional, default="all"

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

str, optional, default="mp4v"

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

int or None, optional, default=None

Positive number, output frame rate for labeled video (only available for the mode with saving frames.) If None, which results in the original video rate.

Path, string or None, optional, default=None

Specifies the destination folder that was used for storing analysis data. If None, the path of the video file is used.

bool, optional, default=False

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.

int, optional, default=0

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

bool, optional, default=False

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

string, optional, default='bodypart'

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

str, optional, default=""

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

str,

Checkpoint path to the super model

string, optional, default=""

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.

str, optional, default=""

Name of the superanimal model.

float, optional, default=None

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

skeleton: list, optional, default=[],

string, optional, default="white",

Color for the skeleton

dotsize, int, optional, default=8, Size of label dots tu use

str, optional, default="rainbow",

Colormap to use for the labels

alphavalue: float, optional, default=0.5,

bool, optional, default=False

If True overwrites existing labeled videos.

Union[bool, Callable[[float], float], default=False

If False, all keypoints will be plot with alpha=1. Otherwise, this can be defined as a function f: [0, 1] -> [0, 1] such that the alpha value for a keypoint will be set as a function of its score: alpha = f(score). The default function used when True is f(x) = max(0, (x - pcutoff)/(1 - pcutoff)).

bool, optional, default=True

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

bboxes_pcutoff, float, optional, default=None: If plotting bounding boxes, this overrides the bboxes_pcutoff set in the model configuration.

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).

additional arguments.

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

Returns

results : 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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0")
def create_labeled_video(
    config: str,
    videos: list[str],
    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
    ----------
    config : string
        Full path of the config.yaml file.

    videos : list[str]
        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, default=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).

    shuffle : int, optional, default=1
        Number of shuffles of training dataset.

    trainingsetindex: int, optional, default=0
        Integer specifying which TrainingsetFraction to use.
        Note that TrainingFraction is a list in config.yaml.

    filtered: bool, optional, default=False
        Boolean variable indicating if filtered output should be plotted rather than
        frame-by-frame predictions. Filtered version can be calculated with
        ``deeplabcut.filterpredictions``.

    fastmode: bool, optional, default=True
        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).

    save_frames: bool, optional, default=False
        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.

    keypoints_only: bool, optional, default=False
        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.

    Frames2plot: List[int] or None, optional, default=None
        If not ``None`` and ``save_frames=True`` then the frames corresponding to the
        index will be plotted. For example, ``Frames2plot=[0,11]`` will plot the first
        and the 12th frame.

    displayedbodyparts: list[str] or str, optional, default="all"
        This selects the body parts that are 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.

    displayedindividuals: list[str] or str, optional, default="all"
        Individuals plotted in the video.
        By default, all individuals present in the config will be shown.

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

    outputframerate: int or None, optional, default=None
        Positive number, output frame rate for labeled video (only available for the
        mode with saving frames.) If ``None``, which results in the original video
        rate.

    destfolder: Path, string or None, optional, default=None
        Specifies the destination folder that was used for storing analysis data. If
        ``None``, the path of the video file is used.

    draw_skeleton: bool, optional, default=False
        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.

    trailpoints: int, optional, default=0
        Number of previous frames whose body parts are plotted in a frame
        (for displaying history).

    displaycropped: bool, optional, default=False
        Specifies whether only cropped frame is displayed (with labels analyzed
        therein), or the original frame with the labels analyzed in the cropped subset.

    color_by : string, optional, default='bodypart'
        Coloring rule. By default, each bodypart is colored differently.
        If set to 'individual', points belonging to a single individual are colored the
        same.

    modelprefix: str, optional, default=""
        Directory containing the deeplabcut models to use when evaluating the network.
        By default, the models are assumed to exist in the project folder.

    init_weights: str,
        Checkpoint path to the super model

    track_method: string, optional, default=""
        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.

    superanimal_name: str, optional, default=""
        Name of the superanimal model.

    pcutoff: float, optional, default=None
        Overrides the pcutoff set in the project configuration to plot the trajectories.

    skeleton: list, optional, default=[],

    skeleton_color: string, optional, default="white",
        Color for the skeleton

    dotsize, int, optional, default=8,
        Size of label dots tu use

    colormap: str, optional, default="rainbow",
        Colormap to use for the labels

    alphavalue: float, optional, default=0.5,

    overwrite: bool, optional, default=False
        If ``True`` overwrites existing labeled videos.

    confidence_to_alpha: Union[bool, Callable[[float], float], default=False
        If False, all keypoints will be plot with alpha=1. Otherwise, this can be
        defined as a function f: [0, 1] -> [0, 1] such that the alpha value for a
        keypoint will be set as a function of its score: alpha = f(score). The default
        function used when True is f(x) = max(0, (x - pcutoff)/(1 - pcutoff)).

    plot_bboxes: bool, optional, default=True
        If using Pytorch and in Top-Down mode,
        setting this to true will also plot the bounding boxes

    bboxes_pcutoff, float, optional, default=None:
        If plotting bounding boxes, this overrides the bboxes_pcutoff
        set in the model configuration.

    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: additional arguments.
        For torch-based shuffles, can be used to specify:
            - snapshot_index
            - detector_snapshot_index

    Returns
    -------
        results : 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 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 = auxiliaryfunctions.read_plainconfig(str(model_config_path))
            if model_config["train_settings"].get("weight_init", {}).get("memory_replay", False):
                superanimal_name = model_config["train_settings"]["weight_init"]["dataset"]
            if bboxes_pcutoff is None:
                bboxes_pcutoff = model_config.get("detector", {}).get("model", {}).get("box_score_thresh", 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(
            os.path.join(
                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 = os.getcwd()
    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,
    videos,
    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

config : str Absolute path to the config.yaml file

list of str

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.

str | Sequence[str] | None, optional, default=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).

int, optional

Number of shuffles of training dataset. Default is set to 1.

int, optional

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

list of strings, optional

This selects 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.

list[int], optional (default=None)

If passed in, the [x1, x2, y1, y2] crop coordinates are used to shift detections appropriately.

string, optional

Specifies the destination folder that was used for storing analysis data (default is the path of the video).

Union[bool, Callable[[float], float], default=False

If False, all keypoints will be plot with alpha=1. Otherwise, this can be defined as a function f: [0, 1] -> [0, 1] such that the alpha value for a keypoint will be set as a function of its score: alpha = f(score). The default function used when True is f(x) = x.

bool, optional (default=True)

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.

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,
    videos,
    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
    ----------
    config : str
        Absolute path to the config.yaml file

    videos : list of str
        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, default=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).

    shuffle : int, optional
        Number of shuffles of training dataset. Default is set 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
        This selects 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.

    cropping: list[int], optional (default=None)
        If passed in, the [x1, x2, y1, y2] crop coordinates are used to shift detections appropriately.

    destfolder: string, optional
        Specifies the destination folder that was used for storing analysis data
        (default is the path of the video).

    confidence_to_alpha: Union[bool, Callable[[float], float], default=False
        If False, all keypoints will be plot with alpha=1. Otherwise, this can be
        defined as a function f: [0, 1] -> [0, 1] such that the alpha value for a
        keypoint will be set as a function of its score: alpha = f(score). The default
        function used when True is f(x) = x.

    plot_bboxes: bool, optional (default=True)
        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.

    kwargs: 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 = os.path.splitext(video)[0]

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

        if not (os.path.isfile(outputname)):
            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
            elif metadata.get("data", {}).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 = (
                metadata.get("data", {})
                .get("pytorch-config", {})
                .get("detector", {})
                .get("model", {})
                .get("box_score_thresh", 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}")
                    pass
                try:
                    clip.save_frame(frame)
                except Exception:
                    print(n, "frame writing error.")
                    pass
            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/utils/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

edit_config

edit_config(configname, edits, output_name='')

Convenience function to edit and save a config file from a dictionary.

Parameters

configname : string String containing the full path of the config file in the project. edits : dict Key–value pairs to edit in config output_name : string, optional (default='') Overwrite the original config.yaml by default. If passed in though, new filename of the edited config.

Examples

config_path = 'my_stellar_lab/dlc/config.yaml'

edits = {'numframes2pick': 5, 'trainingFraction': [0.5, 0.8], 'skeleton': [['a', 'b'], ['b', 'c']]}

deeplabcut.auxiliaryfunctions.edit_config(config_path, edits)

Source code in deeplabcut/utils/auxiliaryfunctions.py
def edit_config(configname, edits, output_name=""):
    """Convenience function to edit and save a config file from a dictionary.

    Parameters
    ----------
    configname : string
        String containing the full path of the config file in the project.
    edits : dict
        Key–value pairs to edit in config
    output_name : string, optional (default='')
        Overwrite the original config.yaml by default.
        If passed in though, new filename of the edited config.

    Examples
    --------
    config_path = 'my_stellar_lab/dlc/config.yaml'

    edits = {'numframes2pick': 5,
             'trainingFraction': [0.5, 0.8],
             'skeleton': [['a', 'b'], ['b', 'c']]}

    deeplabcut.auxiliaryfunctions.edit_config(config_path, edits)
    """
    cfg = read_plainconfig(configname)
    for key, value in edits.items():
        cfg[key] = value
    if not output_name:
        output_name = configname
    try:
        write_plainconfig(output_name, cfg)
    except ruamel.yaml.representer.RepresenterError:
        warnings.warn("Some edits could not be written. The configuration file will be left unchanged.", stacklevel=2)
        for key in edits:
            cfg.pop(key)
        write_plainconfig(output_name, cfg)
    return cfg

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 grab_files_in_folder(folder, "h5"):
        stem = Path(file).stem.replace("_filtered", "")
        starts_by_scorer = file.startswith(videoname + scorer) or file.startswith(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,
                matches_tracker,
                (filtered and "filtered" in file) or (not filtered and "filtered" not in file),
            )
        ):
            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(Path(folder) / 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]

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]:
    """
    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_deeplabcut_path

get_deeplabcut_path()

Get path of where deeplabcut is currently running.

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

    return os.path.split(importlib.util.find_spec("deeplabcut").origin)[0]

get_evaluation_folder

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

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:
    """
    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_immediate_subdirectories

get_immediate_subdirectories(a_dir)

Get list of immediate subdirectories.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def get_immediate_subdirectories(a_dir):
    """Get list of immediate subdirectories."""
    return [name for name in os.listdir(a_dir) if os.path.isdir(os.path.join(a_dir, name))]

get_model_folder

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

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:
    """
    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 kwargs: additional arguments. For torch-based shuffles, can be used to specify: - snapshot_index - detector_snapshot_index

Returns tuple of DLCscorer, 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
    kwargs: additional arguments.
        For torch-based shuffles, can be used to specify:
            - snapshot_index
            - detector_snapshot_index

    Returns tuple of DLCscorer, 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 = (snapshot_name.split(os.sep)[-1]).split("-")[-1]

    dlc_cfg = read_plainconfig(
        os.path.join(
            cfg["project_path"],
            str(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(os.path.join("training-datasets", iterate, "UnaugmentedDataSet_" + Task + date))

get_unique_bodyparts

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

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]:
    """
    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
def grab_files_in_folder(folder, ext="", relative=True):
    """Return the paths of files with extension *ext* present in *folder*."""
    for file in os.listdir(folder):
        if file.endswith(ext):
            yield file if relative else os.path.join(folder, 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(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 = []
    for elem in auxiliaryfunctions.grab_files_in_folder(data_path, relative=False):
        if os.path.isdir(elem):
            annotationfolders.append(elem)
    print("The following folders were found:", annotationfolders)
    for folder in annotationfolders:
        filename = os.path.join(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

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

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.

string, optional

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

tuple

Figure size in inches.

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.

    Parameters
    ----------
    eval_pickle_file : string
        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.

    output_name: string, optional
        Path where the plot is saved. By default, it is stored as costdist.png.

    figsize: tuple
        Figure size in inches.
    """

    with open(eval_pickle_file, "rb") as file:
        data = pickle.load(file)
    meta_pickle_file = eval_pickle_file.replace("_full.", "_meta.")
    with open(meta_pickle_file, "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,
    videos,
    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

config: str Full path of the config.yaml file.

list[str]

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

str | Sequence[str] | None, optional, default=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).

int, optional, default=1

Integer specifying the shuffle index of the training dataset.

int, optional, default=0

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

bool, optional, default=False

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

list[str] or str, optional, default="all"

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.

bool, optional, default=False

If True then plots are also displayed.

string or None, optional, default=None

Specifies the destination folder that was used for storing analysis data. If None, the path of the video is used.

str, optional, default=""

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

string, optional, default=".png"

Specifies the output image format - '.tif', '.jpg', '.svg' and ".png".

int, optional, default=100

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

float, optional, default=1.0

Specifies width of line for line and histogram plots.

string, optional, default=""

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.

string, optional, default=None

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

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'], )

Source code in deeplabcut/utils/plotting.py
@renamed_parameter(old="videotype", new="video_extensions", since="3.0.0")
def plot_trajectories(
    config,
    videos,
    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
    ----------
    config: str
        Full path of the config.yaml file.

    videos: list[str]
        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, default=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).

    shuffle: int, optional, default=1
        Integer specifying the shuffle index of the training dataset.

    trainingsetindex: int, optional, default=0
        Integer specifying which TrainingsetFraction to use.
        Note that TrainingFraction is a list in config.yaml.

    filtered: bool, optional, default=False
        Boolean variable indicating if filtered output should be plotted rather than
        frame-by-frame predictions. Filtered version can be calculated with
        ``deeplabcut.filterpredictions``.

    displayedbodyparts: list[str] or str, optional, default="all"
        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.

    showfigures: bool, optional, default=False
        If ``True`` then plots are also displayed.

    destfolder: string or None, optional, default=None
        Specifies the destination folder that was used for storing analysis data. If
        ``None``, the path of the video is used.

    modelprefix: str, optional, default=""
        Directory containing the deeplabcut models to use when evaluating the network.
        By default, the models are assumed to exist in the project folder.

    imagetype: string, optional, default=".png"
        Specifies the output image format - '.tif', '.jpg', '.svg' and ".png".

    resolution: int, optional, default=100
        Specifies the resolution (in dpi) of saved figures.
        Note higher resolution figures take longer to generate.

    linewidth: float, optional, default=1.0
        Specifies width of line for line and histogram plots.

    track_method: string, optional, default=""
         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.

    pcutoff: string, optional, default=None
        Overrides the pcutoff set in the project configuration to plot the trajectories.

    kwargs: 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'],
        )
    """
    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 = os.path.join(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_videos.

Parameters

Returns

result : 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_videos.

    Parameters
    ----------


    Returns
    -------
        result : 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 os.path.isfile(videooutname) 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 = os.path.join(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_config

read_config(configname)

Reads structured config file defining a project.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def read_config(configname):
    """Reads structured config file defining a project."""
    ruamelFile = YAML()
    path = Path(configname)
    if os.path.exists(path):
        try:
            with open(path) as f:
                cfg = ruamelFile.load(f)
                curr_dir = str(Path(configname).parent.resolve())

                if cfg.get("engine") is None:
                    cfg["engine"] = Engine.TF.aliases[0]
                    write_config(configname, cfg)

                if cfg.get("detector_snapshotindex") is None:
                    cfg["detector_snapshotindex"] = -1

                if cfg.get("detector_batch_size") is None:
                    cfg["detector_batch_size"] = 1

                if cfg["project_path"] != curr_dir:
                    cfg["project_path"] = curr_dir
                    write_config(configname, cfg)
        except Exception as err:
            if len(err.args) > 2:
                if err.args[2] == "could not determine a constructor for the tag '!!python/tuple'":
                    with open(path) as ymlfile:
                        cfg = yaml.load(ymlfile, Loader=yaml.SafeLoader)
                        write_config(configname, cfg)
                else:
                    raise

    else:
        raise FileNotFoundError(
            f"Config file at {path} not found. Please make sure that the file exists and/or that you passed the path of"
            f"the config file correctly!"
        )
    return cfg

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(str(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 open(filename, "rb") as handle:
        return pickle.load(handle)

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/utils/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:

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

Return:

df: 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.

    Parameters:
    ----------
    df: pd.DataFrame
        Data from tracked .h5 file
    order: list of str
        Desired order of individuals

    Return:
    -------
        df: 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 = os.path.join(str(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.

Returns the full path to the rotated video!

Parameter

vname : string A string containing the full path of the video.

float

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

str

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

str

Suffix for output videoname (see example).

str

Output path for saving video to (by default will be the same folder as the 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.

    Returns the full path to the rotated video!

    Parameter
    ----------
    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 will be the same folder as the 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)

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."""
    DataMachine = pd.DataFrame(PredicteData, columns=pdindex, index=imagenames)
    if save_as_csv:
        print("Saving csv poses!")
        DataMachine.to_csv(dataname.split(".h5")[0] + ".csv")
    DataMachine.to_hdf(dataname, key="df_with_missing", format="table", mode="w")
    with open(dataname.split(".h5")[0] + "_meta.pickle", "wb") as f:
        # Pickle the 'data' dictionary using the highest protocol available.
        pickle.dump(metadata, f, pickle.HIGHEST_PROTOCOL)

write_config

write_config(configname, cfg)

Write structured config file.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def write_config(configname, cfg):
    """Write structured config file."""
    with open(configname, "w") as cf:
        cfg_file, ruamelFile = create_config_template(cfg.get("multianimalproject", False))
        for key in cfg.keys():
            cfg_file[key] = cfg[key]

        # Adding default value for variable skeleton and skeleton_color for backward compatibility.
        if "skeleton" not in cfg.keys():
            cfg_file["skeleton"] = []
            cfg_file["skeleton_color"] = "black"
        # Use a very large width so long strings (e.g., file paths or keys with spaces)
        # are kept on a single line instead of being wrapped, which can otherwise cause
        # them to be emitted as complex keys. See also:
        # https://stackoverflow.com/questions/31197268/pyyaml-yaml-dump-produces-complex-key-for-string-key-122-chars/31199123#31199123
        ruamelFile.width = 1_000_000
        ruamelFile.dump(cfg_file, cf)

write_config_3d

write_config_3d(configname, cfg)

Write structured 3D config file.

Source code in deeplabcut/utils/auxiliaryfunctions.py
def write_config_3d(configname, cfg):
    """Write structured 3D config file."""
    with open(configname, "w") as cf:
        cfg_file, ruamelFile = create_config_template_3d()
        for key in cfg.keys():
            cfg_file[key] = cfg[key]
        ruamelFile.dump(cfg_file, cf)

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 open(filename, "wb") as handle:
        pickle.dump(data, handle, protocol=pickle.HIGHEST_PROTOCOL)