LINCellularNeuroscience/VAME

Multi-Animal CSV format

Nike-Nay opened this issue · 3 comments

I am using videos of roots growing. There are 5 roots in each video. I did not use DeepLabCut but matched the coordinate data I got from a different plugin to that which you would get from DLC.

Therefore, I have 5 csv files for each video. One for each root. Do I need to combine these in one in order to use them in VAME?

I am aware a similar issue was opened May 30, 2021 ("How to deal with multi-animals?#54"). However, I did not understand if I need to keep them separated or in one big csv file?

Thank you for any responses!

For my multi animal videos and .csv files I needed to have an individual .csv file for each animal. VAME requires you to have an individual video for each animal (root) also. Because I didn't want to have the same video with a different name for each animal, I just created four (one for each animal in my video files) video aliases for each video. Each alias file just points to the actual video file so you don't have to have one for each animal. I wrote this function to make those alias files:

def create_symlinks(video_dir, new_dir):
    """_summary_

    Args:
    video_dir (str): The path to the directory containing the video files.
    new_dir (str): The path to the directory where the video files will be moved and the symbolic links will be created.

    1. It moves the video files from the original directory (video_dir) to a new directory (new_dir).
    2. It creates symbolic links in the original directory that point to the moved video files in the new directory.
    """
    # Create the new directory if it doesn't exist
    os.makedirs(new_dir, exist_ok=True)

    # Get a list of all video files in the directory
    video_files = glob.glob(os.path.join(video_dir, '*.mp4'))  # adjust the extension if needed

    # List of rat names
    rat_names = ['Rat1', 'Rat2', 'Rat3', 'Rat4']

    # Loop through each video file
    for video_path in video_files:
        
        # Get the video filename
        video_filename = os.path.basename(video_path)

        # Move the video file to the new directory
        new_video_path = os.path.join(new_dir, video_filename)
        shutil.move(video_path, new_video_path)

        # Create a symbolic link for each rat name
        for rat_name in rat_names:
            # Path for the symbolic link
            symlink_path = os.path.join(video_dir, os.path.splitext(video_filename)[0] + '_' + rat_name + os.path.splitext(video_filename)[1])
            # Create the symbolic link
            os.symlink(new_video_path, symlink_path)

# Usage:
# create_symlinks('/path/to/videos', '/path/to/new_directory')

NOTE: Be sure to change the 'rat_names' to your root names (or however you are naming each root).

After this, you will need to update the video files listed in your configuration file to reflect the symlinks. You can do that with:

def update_video_sets_in_config(config_path, video_dir):
    # Get a list of all video files in the directory
    video_files = glob.glob(os.path.join(video_dir, '*.mp4'))  # adjust the extension if needed

    # Get the video names (without extension)
    video_names = [os.path.splitext(os.path.basename(video_file))[0] for video_file in video_files]

    # Create a YAML object
    yaml = YAML()

    # Load the existing config data
    with open(config_path, 'r') as file:
        config_data = yaml.load(file)

    # Update the 'video_sets' field
    config_data['video_sets'] = video_names

    # Write the updated config data back to the file
    with open(config_path, 'w') as file:
        yaml.dump(config_data, file)

# Usage:
# update_video_sets_in_config('/path/to/config.yaml', '/path/to/videos')

Be sure to back up your video files and configuration file before using these functions.

To use them:

  1. Copy them into the 'helperFunctions.py' file.
  2. After you enter a python terminal and 'import vame', also enter 'from vame.custom import helperFunctions as hf'
  3. Then just use 'vame.hf.create_symlinks('/path/to/videos', '/path/to/new_directory')' & 'vame.hf.update_video_sets_in_config('/path/to/config.yaml', '/path/to/videos')'

Let me know if you have any questions @Nike-Nay !

Also, in vame/util/align_egocentrical.py you may need to exchange the original 'def alignment' with this one:

def alignment(config, filename, pose_ref_index, video_format, crop_size, use_video=False, check_video=False, save=False, blank_background=True):
    """
    Parameters
    ----------
    config : string
        Path to config file
    filename : string
        Name of video to be processed (without extension)
    pose_ref_index : list
        List of 2 body parts to egocentrically align to.
    video_format : string
        Extension of filename.
    crop_size : tuple
        Size to crop aligned data to. Should be larger than maximum single-frmae movement.
    use_video : Tbool, optional (default False)
        Whether to use the video or only DLC poses.
    check_video : bool, optional (Default False)
        Whether to display the video. Press Q to exit while displaying.
    save : bool, optional, (default False)
        Whether to save the video instead of displaying.

    Returns
    -------
    time_series : list
        Frame indices
    frames : list of arrays
        Aligned poses or video frames.
    """
    config_file = Path(config).resolve()
    cfg = read_config(config_file)
    path_to_file = cfg['project_path']
    confidence = cfg['pose_confidence']
    #read out data
    dataFile = glob.glob(os.path.join(path_to_file,'videos','pose_estimation',filename+'*.csv'))
    if len(dataFile)>1:
        raise KeyError("Multiple csv files match video filename")
    else:
        dataFile=dataFile[0]
    
    dlc_data_type = input("Were your DLC .csv files orginially multi-animal? (y/n): ")
    
    if dlc_data_type == 'n':
        data = pd.read_csv(dataFile, skiprows=0, index_col=0, header=[1,2])
    if dlc_data_type == 'y':
        data = pd.read_csv(dataFile, index_col=0, header=[0, 1])
    #data = pd.read_csv(os.path.join(path_to_file,'videos','pose_estimation',filename+'.csv'), skiprows = 0, header=[1,2], index_col=0)
    data_mat = pd.DataFrame.to_numpy(data)
    
    # get the coordinates for alignment from data table
    pose_list = []
    
    for i in range(int(data_mat.shape[1]/3)):
        pose_list.append(data_mat[:,i*3:(i+1)*3])  
        
    #list of reference coordinate indices for alignment
    #0: snout, 1: forehand_left, 2: forehand_right, 
    #3: hindleft, 4: hindright, 5: tail    
    
    pose_ref_index = pose_ref_index
    
    #list of 2 reference coordinate indices for avoiding flipping
    pose_flip_ref = pose_ref_index
        
    if use_video:
        if not blank_background:
        #compute background
            bg = background(path_to_file,filename,video_format)
        elif blank_background:
            bg=0

        capture = cv.VideoCapture(os.path.join(path_to_file,'videos',filename+video_format))
        if not capture.isOpened():
            raise Exception("Unable to open video file: {0}".format(os.path.join(path_to_file,'videos',filename+video_format)))          
        frame_count = int(capture.get(cv.CAP_PROP_FRAME_COUNT))
        capture.release()
    else:
        bg = 0
        frame_count = len(data) # Change this to an abitrary number if you first want to test the code
    
    
    frames, n, time_series = align_mouse(path_to_file, filename, video_format, crop_size, pose_list, pose_ref_index,
                                         confidence, pose_flip_ref, bg, frame_count, use_video=use_video)
    
    if check_video and not save:
        play_aligned_video(frames, n, frame_count, path_to_file)
    elif check_video and save:
        play_aligned_video(frames, n, frame_count, path_to_file, save=True, filename=filename, crop_size=crop_size)

    return time_series, frames

The original VAME does not adjust how it processes the headers of the .csv files based on whether or not they were originally multi animal .csv files. The conversion of the .csv files from multi animal to individual animal removes the header row of the .csv file and the original code does account for this.

Thank you so much for getting back to me! Your advice was super helpful. I will close this issue now since this solved my problem!