Note
Go to the end to download the full example code. or to run this example in your browser via Binder
Convert pose tracks between file formats#
Load pose tracks from one file format, modify them, and save them to another file format.
Motivation#
When working with pose estimation data, it’s often useful to convert between file formats. For example, you may need to use some downstream analysis tool that requires a specific file format, or expects the keypoints to be named in a certain way.
In the following example, we will load a dataset from a SLEAP file, modify the keypoints (rename, delete, reorder), and save the modified dataset as a DeepLabCut file.
We’ll first walk through each step separately, and then combine them into a single function that can be applied to multiple files at once.
Imports#
import tempfile
from pathlib import Path
from movement import sample_data
from movement.io import load_poses, save_poses
Load the dataset#
We’ll start with the path to a file output by one of our supported pose estimation frameworks. For example, the path could be something like:
# uncomment and edit the following line to point to your own local file
# file_path = "/path/to/my/data.h5"
For the sake of this example, we will use the path to one of
the sample datasets provided with movement
.
file_path = sample_data.fetch_dataset_paths(
"SLEAP_single-mouse_EPM.analysis.h5"
)["poses"]
print(file_path)
/home/runner/.movement/data/poses/SLEAP_single-mouse_EPM.analysis.h5
Now let’s load this file into a movement poses dataset, which we can then modify to our liking.
ds = load_poses.from_sleap_file(file_path, fps=30)
print(ds, "\n")
print("Individuals:", ds.coords["individuals"].values)
print("Keypoints:", ds.coords["keypoints"].values)
<xarray.Dataset> Size: 1MB
Dimensions: (time: 18485, space: 2, keypoints: 6, individuals: 1)
Coordinates:
* time (time) float64 148kB 0.0 0.03333 0.06667 ... 616.1 616.1 616.1
* space (space) <U1 8B 'x' 'y'
* keypoints (keypoints) <U9 216B 'snout' 'left_ear' ... 'tail_end'
* individuals (individuals) <U12 48B 'individual_0'
Data variables:
position (time, space, keypoints, individuals) float32 887kB nan ... ...
confidence (time, keypoints, individuals) float32 444kB nan nan ... 0.7607
Attributes:
fps: 30.0
time_unit: seconds
source_software: SLEAP
source_file: /home/runner/.movement/data/poses/SLEAP_single-mouse_EP...
ds_type: poses
Individuals: ['individual_0']
Keypoints: ['snout' 'left_ear' 'right_ear' 'centre' 'tail_base' 'tail_end']
Note
If you’re running this code in a Jupyter notebook,
you can just type ds
(instead of printing it)
to explore the dataset interactively.
Rename keypoints#
We start with a dictionary that maps old keypoint names to new ones.
Next, we define a function that takes that dictionary and a dataset
as inputs, and returns a modified dataset. Notice that under the hood
this function calls xarray.Dataset.assign_coords()
.
rename_dict = {
"snout": "nose",
"left_ear": "earL",
"right_ear": "earR",
"centre": "middle",
"tail_base": "tailbase",
"tail_end": "tailend",
}
def rename_keypoints(ds, rename_dict):
# get the current names of the keypoints
keypoint_names = ds.coords["keypoints"].values
# rename the keypoints
if not rename_dict:
print("No keypoints to rename. Skipping renaming step.")
else:
new_keypoints = [rename_dict.get(kp, str(kp)) for kp in keypoint_names]
# Assign the modified values back to the Dataset
ds = ds.assign_coords(keypoints=new_keypoints)
return ds
Let’s apply the function to our dataset and see the results.
ds_renamed = rename_keypoints(ds, rename_dict)
print("Keypoints in modified dataset:", ds_renamed.coords["keypoints"].values)
Keypoints in modified dataset: ['nose' 'earL' 'earR' 'middle' 'tailbase' 'tailend']
Delete keypoints#
Let’s create a list of keypoints to delete.
In this case, we choose to get rid of the tailend
keypoint,
which is often hard to reliably track.
We delete it using xarray.Dataset.drop_sel()
,
wrapped in an appropriately named function.
keypoints_to_delete = ["tailend"]
def delete_keypoints(ds, delete_keypoints):
if not delete_keypoints:
print("No keypoints to delete. Skipping deleting step.")
else:
# Delete the specified keypoints and their corresponding data
ds = ds.drop_sel(keypoints=delete_keypoints)
return ds
ds_deleted = delete_keypoints(ds_renamed, keypoints_to_delete)
print("Keypoints in modified dataset:", ds_deleted.coords["keypoints"].values)
Keypoints in modified dataset: ['nose' 'earL' 'earR' 'middle' 'tailbase']
Reorder keypoints#
We start with a list of keypoints in the desired order
(in this case, we’ll just swap the order of the left and right ears).
We then use xarray.Dataset.reindex()
, wrapped in yet another function.
ordered_keypoints = ["nose", "earR", "earL", "middle", "tailbase"]
def reorder_keypoints(ds, ordered_keypoints):
if not ordered_keypoints:
print("No keypoints to reorder. Skipping reordering step.")
else:
ds = ds.reindex(keypoints=ordered_keypoints)
return ds
ds_reordered = reorder_keypoints(ds_deleted, ordered_keypoints)
print(
"Keypoints in modified dataset:", ds_reordered.coords["keypoints"].values
)
Keypoints in modified dataset: ['nose' 'earR' 'earL' 'middle' 'tailbase']
Save the modified dataset#
Now that we have modified the dataset to our liking,
let’s save it to a .csv file in the DeepLabCut format.
In this case, we save the file to a temporary
directory, and we use the same file name
as the original, but ending in _dlc.csv
.
You will need to specify a different target_dir
and edit
the dest_path
variable to your liking.
target_dir = tempfile.mkdtemp()
dest_path = Path(target_dir) / f"{file_path.stem}_dlc.csv"
save_poses.to_dlc_file(ds_reordered, dest_path, split_individuals=False)
print(f"Saved modified dataset to {dest_path}.")
Saved modified dataset to /tmp/tmpr8e5hfhq/SLEAP_single-mouse_EPM.analysis_dlc.csv.
Note
The split_individuals
argument allows you to save
a dataset with multiple individuals as separate files,
with the individual ID appended to each file name.
In this case, we set it to False
because we only have
one individual in the dataset, and we don’t need its name
appended to the file name.
One function to rule them all#
Since we know how to rename, delete, and reorder keypoints, let’s put it all together in a single function and see how we could apply it to multiple files at once, as we might do in a real-world scenario.
The following function will convert all files in a folder
(that end with a specified suffix) from SLEAP to DeepLabCut format.
Each file will be loaded, modified according to the
rename_dict
, keypoints_to_delete
, and ordered_keypoints
we’ve defined above, and saved to the target directory.
data_dir = "/path/to/your/data/"
target_dir = "/path/to/your/target/data/"
def convert_all(data_dir, target_dir, suffix=".slp"):
source_folder = Path(data_dir)
file_paths = list(source_folder.rglob(f"*{suffix}"))
for file_path in file_paths:
file_path = Path(file_path)
# this determines the file names for the modified files
dest_path = Path(target_dir) / f"{file_path.stem}_dlc.csv"
if dest_path.exists():
print(f"Skipping {file_path} as {dest_path} already exists.")
continue
if file_path.exists():
print(f"Processing: {file_path}")
# load the data from SLEAP file
ds = load_poses.from_sleap_file(file_path)
# modify the data
ds_renamed = rename_keypoints(ds, rename_dict)
ds_deleted = delete_keypoints(ds_renamed, keypoints_to_delete)
ds_reordered = reorder_keypoints(ds_deleted, ordered_keypoints)
# save modified data to a DeepLabCut file
save_poses.to_dlc_file(
ds_reordered, dest_path, split_individuals=False
)
else:
raise ValueError(
f"File '{file_path}' does not exist. "
f"Please check the file path and try again."
)
Total running time of the script: (0 minutes 0.276 seconds)