Formatting your data: feature extraction from motion tracking output

Open In Colab

What we’ll cover:

  • Create and run a project.

  • Load a previously generated project.

  • Interact with your project: generate coordinates, distances, angles, and areas.

  • Exploratory visualizations: heatmaps and processed animations.

[1]:
# # If using Google colab, uncomment and run this cell and the one below to set up the environment
# # Note: becasuse of how colab handles the installation of local packages, this cell will kill your runtime.
# # This is not an error! Just continue with the cells below.
# import os
# !git clone -q https://github.com/mlfpm/deepof.git
# !pip install -q -e deepof --progress-bar off
# os.chdir("deepof")
# !curl --output tutorial_files.zip https://datashare.mpcdf.mpg.de/s/knF7t78isQuIAr0/download
# !unzip tutorial_files.zip
# os.kill(os.getpid(), 9)
[2]:
# os.chdir("deepof")
# import os, warnings
# warnings.filterwarnings('ignore')

Let’s start by importing some packages. We’ll use python’s os library to handle paths, pickle to load saved objects, pandas to load data frames, and the data entry API within DeepOF, located in deepof.data

[3]:
import os
import pandas as pd
import pickle
import deepof.data

We’ll also need some plotting gear:

[4]:
import deepof.visuals
import matplotlib.pyplot as plt
import seaborn as sns

Creating and running a project

With that out of the way, the first thing to do when starting a DeepOF project is to load your videos and DeepLabCut tracking files into a deepof.data.Project object.

Like depicted in the cell below, the three crucial parameters to input are the project, video and tab paths.

  • project_path specifies where the project folder containing all processing and output files will be created.

  • video_path points towards where your DLC or SLEAP labelled videos are stored.

  • similarly, table_path should point to the directory containing all tracking files (see section on supported input files below).

  • last but not least, you can give your project a name (optional; deepof_project by default).

The dataset used in this tutorial is a subset of the social interaction (SI) dataset used in DeepOF’s original paper. It contains six 10-minute-long videos with two animals (a C57Bl6 and a CD1) tracked in a round arena, tracked with DeepLabCut. Three of the C57Bl6 mice have been exposed to chronic social defeat stress (CSDS).

[5]:
my_deepof_project_raw = deepof.data.Project(
                project_path=os.path.join("tutorial_files"),
                video_path=os.path.join("tutorial_files/Videos/"),
                table_path=os.path.join("tutorial_files/Tables/"),
                project_name="deepof_tutorial_project",
                arena="circular-autodetect",
                animal_ids=["B", "W"],
                table_format="h5",
                video_format=".mp4",
                exclude_bodyparts=["Tail_1", "Tail_2", "Tail_tip"],
                video_scale=380,
                smooth_alpha=1,
                exp_conditions=None,
)

As you may see, there are some extra (optional) parameters in the call above. These specify some details on how the videos and tracks will be processed. Some include:

  • arena: some functions within DeepOF (as you will see in the next tutorial on supervised annotations) require the program to detect the arena. The following section explains how to handle this step in more detail.

  • animal_ids: in case more than one animal is present in the dataset, the IDs assigned to each during tracking need to be specified as a list. For SLEAP projects, this will be detected automatically, but can be overriten.

  • exclude_body_parts: while DeepOF originally relies on 14 body part tracking models, body parts along the tail are no longer used. This step could be bypassed using smaller models following an 11-body-part scheme, such as the one presented in the landing page of the documentation. Custom labelling schemes are also supported (see tutorials).

  • video_scale: diameter of the arena (in mm) if circular. In the case of polygonal arenas, the length (in mm) of the first edge as specified in the GUI (see next section) should be provided.

  • smooth_alpha: smoothing intensity. The higher the value, the more smoothing is applied.

  • exp_conditions: dictionary with a video IDs as keys, and data frames with all experimental conditions as values. DeepOF will use this information to enable all sorts of comparisons between conditions, as we will see in the following tutorials. We’ll leave it blank for now and update it afterwards.

For more details, feel free to visit the full API reference.

Let’s then create our first project, by running the .create() method in our newly created object:

[6]:
my_deepof_project = my_deepof_project_raw.create(force=True)
Setting up project directories...
Loading trajectories...
Smoothing trajectories...
Interpolating outliers...
Iterative imputation of ocluded bodyparts...
Detecting arena...
Computing distances...
Computing angles...
Computing areas...
Done!

NOTE: the force parameter allows DeepOF to override an existing project in the same directory. Use with caution!

NOTE 2: the cell above can take a significant amount of time to run in Google colab. Feel free to skip and continue with the loaded results below.

As you will see, this organizes all required file into a folder in the specified path. Moreover, some processing steps are computed on the go, such as distances, angles and areas. This makes it easier for DeepOF to load all required features later on, without the need to compute them every time.

Arena detection

As mentioned above, one of the key aspects of project creation involves setting up the arena detection mechanism, which will be used in downstream tasks, such as climbing detection and overall video scaling.

You can contrAlong these lines, the package provides tools for detecting elliptical arenas specifically (as seen in this tutorial), and general polygonal shapes (such as squares, rectangles, Y-mazes, etc).

In principle, DeepOF can detect your arenas automatically by relying in SAM (Segment Anything Model), a state-of-the-art image segmentation deep learning model. By selecting arena='circular-autodetect', or arena='polygonal-autodetect', users can benefit from this approach. A folder named Arena_detection will be created in your project directory, which contains samples of the detected arenas for all videos, so you can visually inspect if DeepOF did a good job.

Moreover, manual annotation can be selected using arena='circular-manual', and arena='polygonal-manual'. In this case, you will see a window per video appear, allowing you to manually mark where the arena is with just a few clicks. All results will be stored for further processing.

Here, clicking anywhere in the video will make an orange marker appear. In the case of polygonal arenas, at least a marker per corner should be used. When dealing with circular (or elliptical) arenas, DeepOF will fit an ellipse to the marked points after a minimum of 5 clicks. The ellipse can always be refined by adding more markers. Once finished with a video, press q to save and move to the next one. If you made a mistake and would like to correct it, press d to delete the last added marker. Moreover, after you tagged at least one video, you’ll see the p option appear, which will copy the last marked arena to all remaining videos in the dataset.

NOTE: If you select arena='polygonal-autodetect', you will still be prompted with the GUI to select the arena just once, so SAM roughly knows how the segmentation it should return looks like. Detection in all remaining videos will be automatic. This is not the case for arena='circular-autodetect', as we already know we’re looking for a circle.

arena_GUI

One last thing is that, in polygonal arenas, the first edge you input will be used to scale the coordinates to the proper distances (pixels to millimeters). Make sure you always mark the same edge first, and that it coincides with the length passed to the “video_scale” parameter when creating the project. In saved images of automatically detected polygons, you’ll see orange circles marking two arena corners. These should match the first edge you selected when prompting SAM, ensuring data is properly scaled.

If after all this work you make a mistake while labelling, or see that automatic detection failed in some cases, don’t worry! You can always edit manually annotated arenas (or rerun automatic annotation) for specific videos using the .edit_arenas() method. Just pass a list with the IDs of the videos you would like to relabel, and the GUI will pop up once again. The same methods described above can be passed to the arena_type parameter.

NOTE: If you don’t pass any video IDs, all videos will be selected by default.

[7]:
my_deepof_project.edit_arenas(
    videos=['20191204_Day2_SI_JB08_Test_54', '20191204_Day2_SI_JB08_Test_62'],
    arena_type="circular-autodetect",
)
Editing 2 arenas
Done!

Supported input tracking files:

DeepOF currently supports input from both DeepLabCut and SLEAP. While DeepOF will try to detect automatically which file type you’re trying to use, this can be forced with the table_format parameter in the deepof.data.Project() call depicted in the previous section.

For DeepLabCut, we support:

  • Single and multi-animal project CSV files (indicated simply with table_format='csv'.

  • Single and multi-animal project h5 files (indicated simply with table_format='h5'.

For SLEAP, you can use:

  • SLP files (indicated as with table_format='slp'.

  • Raw .npy files (indicated with table_format='npy'.

  • h5 files (indicated with table_format=analysis.h5.

Downstream processing should be identical regardless of the files selected, as DeepOF internally brings all these inputs into an equivalent representation. Let’s continue!

Loading a previously generated project

Once you ran your project at least once, it can be loaded without much effort from the path you specified in the first place (plus the project name -deepof_project, if you didn’t specify otherwise-).

[8]:
# Load a previously saved project
my_deepof_project = deepof.data.load_project("./tutorial_files/deepof_tutorial_project/")

Extend a previously generated project

If you’d like to add data (videos and tracks) to a previously processed project, you can use the extend method instead of create. Just pass the path to your previously processed project, and DeepOF will take care of merging the two. A new directory will be created with all files corresponding to the merged projects.

NOTE: Your old files will not be deleted.

[9]:
# Extend a previously saved project
my_deepof_project_raw.extend("./tutorial_files/deepof_tutorial_project/")
Loading previous project...
Processing data from 0 experiments...
No new experiments to process. Exiting...

Interacting with your project: generating coordinates, distances, angles, and areas.

That’s it for basic project creation. We now have a DeepOF project up and running! Before ending the tutorial, however, let’s explore the object that the commands above produced.

For starters, if we print it, we see it’s a DeepOF analysis of 6 videos. Furthermore, the object belongs to a custom class within DeepOF, called Coordinates. This class allows the package to store all sorts of relevant information required for further processing, as we’ll see below:

[11]:
print(my_deepof_project)
print(type(my_deepof_project))
deepof analysis of 6 videos
<class 'deepof.data.Coordinates'>

As described before, the .create() method runs most of the heavy preprocessing already, which allows us to extract features including coordinates, distances, angles, and areas. Let’s see how that works!

preprocessing

With the .get_coords() method, for example, we can obtain the processed (smooth and imputed) tracks for all videos in a dictionary. The returned objects are called table dictionaries (TableDict is the name of the class). They follow a dictionary-like structure, where each value is a data frame. They also provide a plethora of extra methods, some of which we’ll cover in these tutorials. Let’s retrieve these for one of the animals:

[12]:
my_deepof_project.get_coords(polar=False, center="Center", align="Spine_1")['20191204_Day2_SI_JB08_Test_54']
[12]:
B_Spine_1 B_Center B_Left_bhip B_Left_ear B_Left_fhip ... W_Right_bhip W_Right_ear W_Right_fhip W_Spine_2 W_Tail_base
x y x y x y x y x y ... x y x y x y x y x y
00:00:00 0.0 17.041283 0.0 0.0 16.235626 -9.286406 2.338149 35.826017 14.520708 16.191088 ... -11.115287 -13.877286 -11.056773 52.165913 -10.837443 11.900607 1.303025 -19.189055 -9.987794 -46.786090
00:00:00.039935995 0.0 16.475167 0.0 0.0 15.143549 -10.150172 0.671672 35.836244 14.183559 14.703795 ... -13.884385 -18.083860 -11.634608 53.793769 -13.457341 12.356739 1.828954 -24.439299 -1.620732 -48.755033
00:00:00.079871991 0.0 16.446049 0.0 0.0 11.477393 -12.396184 0.097058 35.005123 13.716176 13.898245 ... -15.550991 -13.873025 -12.439161 53.259569 -14.284002 12.808460 0.793345 -21.164099 1.765989 -41.624561
00:00:00.119807987 0.0 15.217471 0.0 0.0 10.208930 -14.160422 -0.358944 35.315238 15.230370 11.435026 ... -14.469009 -16.138564 -11.961743 52.265874 -14.571504 13.210702 1.309584 -22.165508 2.815459 -42.444154
00:00:00.159743982 0.0 15.037661 0.0 0.0 10.836279 -15.220016 0.861588 34.324995 14.984768 10.471671 ... -14.028915 -18.169221 -19.961073 49.485545 -14.900604 11.323389 1.955114 -22.447853 2.289354 -44.418900
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
00:09:58.800320021 0.0 17.383252 0.0 0.0 15.964064 -11.925356 10.656661 35.405103 14.810418 11.274649 ... -20.815692 -6.861448 -17.302919 31.105013 -12.210518 10.521213 -6.286187 -15.291244 -18.448989 -26.719926
00:09:58.840256017 0.0 15.069470 0.0 0.0 15.782188 -13.477302 7.241669 39.238723 14.817890 9.091584 ... -20.930728 -7.043085 -17.317452 31.011691 -12.259446 10.426808 -6.220324 -15.480332 -18.379192 -26.895317
00:09:58.880192012 0.0 19.435457 0.0 0.0 12.847697 -11.565332 7.622541 42.242592 14.851511 12.187561 ... -20.912197 -7.052485 -17.362287 30.948820 -12.239039 10.390575 -6.179682 -15.503151 -18.331690 -26.956528
00:09:58.920128008 0.0 19.435457 0.0 0.0 12.847697 -11.565332 7.622541 42.242592 14.851511 12.187561 ... -20.912197 -7.052485 -17.362287 30.948820 -12.239039 10.390575 -6.179682 -15.503151 -18.331690 -26.956528
00:09:58.960064004 0.0 19.435457 0.0 0.0 12.847697 -11.565332 7.622541 42.242592 14.851511 12.187561 ... -20.912197 -7.052485 -17.362287 30.948820 -12.239039 10.390575 -6.179682 -15.503151 -18.331690 -26.956528

14999 rows × 44 columns

Note that there are a few parameters you can pass to the .get_coords() method. If “polar” is set to True, polar coordinates will be used, instead of Cartesian. Both “center” and “align” control how translational and rotational variance are removed from the data: the former will use the specified body part as [0, 0] (or the center of the arena, if set to “arena”). The latter will rotate the mice on each time point, to align the line connecting the body parts specified in the “center” and “align” parameters with the y-axis.

Furthermore, not only processed coordinates can be retrieved, but also distances, angles, and areas with the .get_distances(), .get_angles(), and .get_areas(), respectively.

[13]:
my_deepof_project.get_distances()['20191204_Day2_SI_JB08_Test_54']
[13]:
(B_Nose, B_Right_ear) (B_Tail_base, W_Tail_base) (W_Center, W_Right_fhip) (B_Center, B_Left_fhip) (W_Left_ear, W_Spine_1) (W_Left_ear, W_Nose) (B_Tail_base, W_Nose) (W_Center, W_Spine_2) (B_Right_bhip, B_Spine_2) (W_Center, W_Spine_1) ... (B_Right_ear, B_Spine_1) (W_Spine_2, W_Tail_base) (B_Left_ear, B_Nose) (B_Center, B_Spine_1) (B_Nose, W_Tail_base) (W_Nose, W_Right_ear) (B_Left_bhip, B_Spine_2) (W_Center, W_Left_fhip) (B_Nose, W_Nose) (B_Spine_2, B_Tail_base)
00:00:00 18.781728 97.709786 13.041369 17.621480 27.055962 18.834943 148.048046 15.583439 11.304016 19.977755 ... 14.239388 24.159109 15.890697 13.807436 115.086175 22.834626 15.229008 18.574999 197.209343 14.568245
00:00:00.039935995 16.395499 102.888817 14.802899 16.552894 25.757160 19.117538 156.962058 19.856936 11.571620 21.885587 ... 14.693174 19.898728 15.271448 13.348749 118.018418 21.852281 15.199081 20.296875 201.897777 14.454055
00:00:00.079871991 15.057738 112.513259 15.544880 15.821263 27.572622 22.222081 165.295346 17.159928 11.412604 19.920155 ... 14.429184 16.596494 15.170325 13.325157 122.652716 24.791305 14.600725 20.121680 202.543917 14.753496
00:00:00.119807987 15.112614 119.626219 15.936129 15.431171 29.026338 20.982981 171.180259 17.990578 11.977952 20.211183 ... 15.581831 16.475699 16.038286 12.329721 122.474901 24.705419 14.741844 19.739289 201.439639 15.937254
00:00:00.159743982 16.235095 123.351461 15.163451 14.811991 26.424186 21.167892 174.253493 18.256880 12.857731 21.018731 ... 15.989769 17.803761 18.028338 12.184032 121.775639 22.790681 14.977541 20.680558 200.696460 15.523106
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
00:09:58.800320021 22.674736 231.732647 13.059429 15.081384 18.866566 23.477526 172.650733 13.395564 13.647533 15.129049 ... 16.974596 13.522631 20.974634 14.084511 191.389072 19.277812 13.789807 15.207663 148.312574 14.714033
00:09:58.840256017 22.789396 228.571928 13.039802 14.085660 18.910177 23.502572 169.636565 13.517401 13.324808 15.079827 ... 17.378502 13.512709 16.403318 12.209805 186.729656 19.322534 13.468828 15.197649 144.670506 14.576626
00:09:58.880192012 21.730112 226.395893 13.008192 15.566291 18.928975 23.463719 168.414230 13.522330 13.673167 15.108134 ... 17.947812 13.529974 18.592353 15.747278 186.008429 19.336323 13.742711 15.241505 146.851047 15.374270
00:09:58.920128008 21.730112 226.395893 13.008192 15.566291 18.928975 23.463719 168.414230 13.522330 13.673167 15.108134 ... 17.947812 13.529974 18.592353 15.747278 186.008429 19.336323 13.742711 15.241505 146.851047 15.374270
00:09:58.960064004 21.730112 226.395893 13.008192 15.566291 18.928975 23.463719 168.414230 13.522330 13.673167 15.108134 ... 17.947812 13.529974 18.592353 15.747278 186.008429 19.336323 13.742711 15.241505 146.851047 15.374270

14999 rows × 26 columns

[14]:
my_deepof_project.get_angles()['20191204_Day2_SI_JB08_Test_54']
[14]:
(B_Left_ear, B_Nose, B_Right_ear) (B_Nose, B_Left_ear, B_Spine_1) (B_Right_fhip, B_Center, B_Spine_2) (B_Right_fhip, B_Center, B_Left_fhip) (B_Right_fhip, B_Center, B_Spine_1) (B_Spine_2, B_Center, B_Left_fhip) (B_Spine_2, B_Center, B_Spine_1) (B_Left_fhip, B_Center, B_Spine_1) (B_Left_bhip, B_Spine_2, B_Center) (B_Left_bhip, B_Spine_2, B_Right_bhip) ... (W_Left_bhip, W_Spine_2, W_Center) (W_Left_bhip, W_Spine_2, W_Right_bhip) (W_Left_bhip, W_Spine_2, W_Tail_base) (W_Center, W_Spine_2, W_Right_bhip) (W_Center, W_Spine_2, W_Tail_base) (W_Right_bhip, W_Spine_2, W_Tail_base) (W_Nose, W_Right_ear, W_Spine_1) (W_Left_ear, W_Spine_1, W_Center) (W_Left_ear, W_Spine_1, W_Right_ear) (W_Center, W_Spine_1, W_Right_ear)
00:00:00 1.222277 1.422657 1.805680 1.281616 1.342828 0.880634 1.020852 1.157763 1.110616 1.293108 ... 0.931157 0.848570 0.884871 0.865821 0.905761 0.866423 1.181972 1.145697 1.162453 1.018039
00:00:00.039935995 1.306489 1.247439 1.370046 1.299858 1.296478 1.502996 1.503997 1.052589 1.150996 1.200270 ... 0.994129 0.891450 0.966073 0.853879 0.919997 0.829069 1.212442 1.149331 1.114826 1.044152
00:00:00.079871991 1.282906 1.033643 1.190446 1.295514 1.433251 1.331724 1.469462 1.398862 1.548047 1.722389 ... 1.017974 0.977607 0.900188 0.915664 0.838244 0.740774 1.069299 1.090081 1.214856 1.133077
00:00:00.119807987 1.251990 1.454675 1.765511 1.277808 1.363800 0.915767 1.064082 1.182411 1.116587 1.309047 ... 0.968484 0.885802 0.910080 0.903568 0.931403 0.882201 1.213669 1.177122 1.188797 1.039895
00:00:00.159743982 1.330860 1.264571 1.356475 1.286804 1.286837 1.450061 1.454712 1.065928 1.106752 1.156228 ... 1.003958 0.907370 0.988059 0.879502 0.953687 0.854115 1.254271 1.189981 1.148939 1.079375
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
00:09:58.800320021 1.249109 1.107626 0.859565 0.832034 0.888055 0.753215 0.805548 0.793002 0.664003 0.694227 ... 0.006698 0.026468 0.016655 0.036679 0.036456 0.152219 0.049799 0.096655 0.082662 0.071047
00:09:58.840256017 1.097980 0.939944 0.779166 0.870837 0.946506 0.793762 0.862571 0.836612 0.737746 0.738258 ... 0.104709 0.167571 0.107108 0.170584 0.116224 0.140526 0.030954 0.026903 0.110395 0.121503
00:09:58.880192012 1.062318 1.104193 0.861663 0.853209 0.904947 0.707809 0.759548 0.837450 0.711801 0.763568 ... 0.054504 0.049245 0.078203 0.145142 0.174100 0.247217 0.180157 0.128315 0.004496 0.090246
00:09:58.920128008 1.271455 1.135542 0.861166 0.833963 0.890707 0.749906 0.802551 0.806550 0.667206 0.697214 ... 0.003272 0.021836 0.011959 0.027468 0.026288 0.141577 0.061570 0.109516 0.093470 0.063343
00:09:58.960064004 1.136102 0.979934 0.781150 0.872829 0.948545 0.795213 0.864082 0.841856 0.735044 0.735689 ... 0.099354 0.163372 0.102508 0.162928 0.108771 0.133632 0.052552 0.041997 0.097895 0.103528

14999 rows × 36 columns

[15]:
my_deepof_project.get_areas()['20191204_Day2_SI_JB08_Test_54']
[15]:
B_head_area B_torso_area B_back_area B_full_area W_head_area W_torso_area W_back_area W_full_area
00:00:00 287.018462 467.472460 513.431000 1697.086099 588.316685 575.287843 557.837678 2402.446220
00:00:00.039935995 298.006089 461.593907 511.611552 1664.187297 532.941463 711.610814 747.171324 2612.577515
00:00:00.079871991 283.907493 436.994147 491.022208 1571.659017 615.224965 659.274363 696.773878 2680.819039
00:00:00.119807987 336.017364 459.434553 495.609970 1674.157290 664.955274 698.278394 709.587295 2725.074795
00:00:00.159743982 338.565821 456.785954 510.514167 1706.762126 589.501549 713.393985 706.975280 2732.936140
... ... ... ... ... ... ... ... ...
00:09:58.800320021 364.598702 512.092068 562.957165 1882.932433 516.368794 466.566421 607.161201 1974.352930
00:09:58.840256017 342.771541 514.231920 543.299019 1832.083470 517.334943 468.582411 611.765902 1979.455000
00:09:58.880192012 388.332158 529.127829 558.340318 1889.399535 516.875687 468.401897 613.444346 1980.834529
00:09:58.920128008 388.332158 529.127829 558.340318 1889.399535 516.875687 468.401897 613.444346 1980.834529
00:09:58.960064004 388.332158 529.127829 558.340318 1889.399535 516.875687 468.401897 613.444346 1980.834529

14999 rows × 8 columns

Last but not least, features can be merged using the .merge() method, which can yield combinations of features if needed. For example, the code in the following cell creates an object with both coordinates and areas per time point:

[16]:
my_deepof_project.get_coords().merge(my_deepof_project.get_areas())['20191204_Day2_SI_JB08_Test_54']
[16]:
(B_Center, x) (B_Center, y) (B_Left_bhip, x) (B_Left_bhip, y) (B_Left_ear, x) (B_Left_ear, y) (B_Left_fhip, x) (B_Left_fhip, y) (B_Nose, x) (B_Nose, y) ... (W_Tail_base, x) (W_Tail_base, y) B_head_area B_torso_area B_back_area B_full_area W_head_area W_torso_area W_back_area W_full_area
00:00:00 223.193283 184.124115 222.160935 202.799423 191.852982 166.609666 201.709718 187.509186 188.298004 147.322081 ... 321.353759 97.605996 287.018462 467.472460 513.431000 1697.086099 588.316685 575.287843 557.837678 2402.446220
00:00:00.039935995 222.081848 185.302109 221.340759 203.517593 192.753159 164.698335 201.841401 188.076859 194.544312 145.935454 ... 332.379546 98.838112 298.006089 461.593907 511.611552 1664.187297 532.941463 711.610814 747.171324 2612.577515
00:00:00.079871991 220.547333 186.246719 219.854218 203.126160 197.765198 159.669617 201.092896 184.567551 205.235306 142.500976 ... 350.856689 101.147659 283.907493 436.994147 491.022208 1571.659017 615.224965 659.274363 696.773878 2680.819039
00:00:00.119807987 218.467209 184.700256 216.118439 201.998322 202.173355 153.366486 199.648239 181.772736 214.989639 138.281066 ... 361.710663 101.918587 336.017364 459.434553 495.609970 1674.157290 664.955274 698.278394 709.587295 2725.074795
00:00:00.159743982 219.402542 182.458069 214.480026 200.481461 206.756088 150.536057 201.725708 177.796661 221.262161 133.663879 ... 369.073914 106.446114 338.565821 456.785954 510.514167 1706.762126 589.501549 713.393985 706.975280 2732.936140
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
00:09:58.800320021 108.366647 302.971861 127.398353 297.067658 106.577644 339.902678 118.557281 318.548034 91.342528 360.831938 ... 267.896393 517.758606 364.598702 512.092068 562.957165 1882.932433 516.368794 466.566421 607.161201 1974.352930
00:09:58.840256017 108.605596 310.532362 127.530082 302.013032 104.571034 350.229231 120.285217 323.409210 93.433890 367.135740 ... 267.855316 517.770020 342.771541 514.231920 543.299019 1832.083470 517.334943 468.582411 611.765902 1979.455000
00:09:58.880192012 108.844546 318.092862 124.715041 311.241029 102.564423 360.555784 119.021164 334.388275 86.603620 377.042506 ... 267.961975 517.801758 388.332158 529.127829 558.340318 1889.399535 516.875687 468.401897 613.444346 1980.834529
00:09:58.920128008 108.844546 318.092862 124.715041 311.241029 102.564423 360.555784 119.021164 334.388275 86.603620 377.042506 ... 267.961975 517.801758 388.332158 529.127829 558.340318 1889.399535 516.875687 468.401897 613.444346 1980.834529
00:09:58.960064004 108.844546 318.092862 124.715041 311.241029 102.564423 360.555784 119.021164 334.388275 86.603620 377.042506 ... 267.961975 517.801758 388.332158 529.127829 558.340318 1889.399535 516.875687 468.401897 613.444346 1980.834529

14999 rows × 52 columns

Loading experimental conditions

So far, DeepOF does not know to which condition each animal belongs. This can be either set up when creating the project (as described above) or specified afterward using the .load_exp_conditions() method. We just need to pass the path to a CSV file containing all conditions per animal as extra columns. The only hard requirement is that the first column should have the experiment IDs.

Here is an example:

[17]:
pd.read_csv("./tutorial_files/tutorial_project/Coordinates/tutorial_exp_conditions.csv", index_col=0)
[17]:
experiment_id CSDS
0 20191204_Day2_SI_JB08_Test_54 Nonstressed
1 20191204_Day2_SI_JB08_Test_56 Stressed
2 20191204_Day2_SI_JB08_Test_61 Stressed
3 20191204_Day2_SI_JB08_Test_62 Stressed
4 20191204_Day2_SI_JB08_Test_63 Nonstressed
5 20191204_Day2_SI_JB08_Test_64 Nonstressed

Great! Now that we understand how the CSV should be formatted, let’s then load it onto our project:

[18]:
my_deepof_project.load_exp_conditions("./tutorial_files/tutorial_project/Coordinates/tutorial_exp_conditions.csv")

And we’re done!. Let’s explore what’s in there with the .get_exp_conditions property:

[19]:
print(my_deepof_project.get_exp_conditions)
{'20191204_Day2_SI_JB08_Test_54':           CSDS
0  Nonstressed, '20191204_Day2_SI_JB08_Test_56':        CSDS
1  Stressed, '20191204_Day2_SI_JB08_Test_61':        CSDS
2  Stressed, '20191204_Day2_SI_JB08_Test_62':        CSDS
3  Stressed, '20191204_Day2_SI_JB08_Test_63':           CSDS
4  Nonstressed, '20191204_Day2_SI_JB08_Test_64':           CSDS
5  Nonstressed}

We can see that the property retrieves a dictionary with all animal experiments as keys, and data frames with conditions as values. Although in this case we only have the CSDS condition, which can take two values (“Nonstressed” and “Stressed”, with three animals each), adding more just requires us to add extra columns to the CSV file.

Filtering DeepOF objects

Now that experimental conditions were added, we’ll explore some filtering tools that DeepOF provides for table dictionary objects.

For starters, imagine you want to subset your data to only contain stressed animals. You can do that with the .filter_condition() method, which takes a dictionary as input with the experimental condition to filter on as key, and the value you’d like to keep as value:

[20]:
# Let's use coords as an example
coords = my_deepof_project.get_coords()
print("The original dataset has {} videos".format(len(coords)))

# Let's keep only those experiments where the subject is stressed:
coords = coords.filter_condition({"CSDS": "Stressed"})
print("The filtered dataset has only {} videos".format(len(coords)))
The original dataset has 6 videos
The filtered dataset has only 3 videos

We can also filter specific experiments with the .filter_videos() method, which takes a list of experiment IDs as input:

[21]:
single_video_coords = coords.filter_videos(['20191204_Day2_SI_JB08_Test_56'])
print("The new filtered dataset has only {} video".format(len(single_video_coords)))
The new filtered dataset has only 1 video

Last but not least, we can also keep all videos, but filter certain animals whose coordinates we’d like to keep for further analysis. As seen above, the dataset used in this tutorial contains two animals per video: a C57Bl6 (labelled “B”) and a CD1 (labelled “W”). Let’s see how we can keep only the C57B16 with the .filter_id() method:

Let’s first point out that, before filtering, a given experiment in the coords object has 44 features (22 from each animal).

[22]:
coords['20191204_Day2_SI_JB08_Test_56']
[22]:
B_Center B_Left_bhip B_Left_ear B_Left_fhip B_Nose ... W_Right_ear W_Right_fhip W_Spine_1 W_Spine_2 W_Tail_base
x y x y x y x y x y ... x y x y x y x y x y
00:00:00 538.363647 249.284027 552.938171 238.256393 526.389221 298.484528 554.218140 263.846924 497.151367 300.893616 ... 163.311036 230.528197 151.194123 260.958313 145.208389 245.764724 123.380149 292.024324 117.470673 316.318786
00:00:00.039938658 538.369385 249.327469 552.927368 238.261719 526.412781 298.484711 554.220703 263.867645 497.139038 300.743500 ... 167.872041 221.538756 156.158676 252.000641 147.822707 238.749525 126.308410 282.404055 117.764466 305.007018
00:00:00.079877316 538.341919 249.331680 552.979980 238.308563 526.437988 298.471222 554.231751 263.856079 497.157898 300.888397 ... 172.082931 213.227768 160.587585 244.264328 151.865173 229.841355 129.889419 272.855470 119.838295 295.199738
00:00:00.119815975 538.328064 249.476318 553.003174 238.238953 526.427002 298.530182 554.242981 263.896118 497.196014 300.982727 ... 176.770797 203.171585 164.369293 235.348343 156.897583 219.281571 134.908768 261.188996 124.537338 280.778840
00:00:00.159754633 538.361267 249.156189 552.935913 238.178787 526.232361 298.454224 554.304565 263.707153 497.193512 300.888733 ... 184.708252 193.616104 168.269272 226.879074 161.464935 209.005005 135.903274 252.997026 125.637749 273.162354
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
00:09:58.800306707 417.213013 120.369354 434.364075 121.799324 407.657898 149.051193 420.671143 132.926590 403.773225 168.571396 ... 458.965545 138.696243 478.439513 152.962585 464.861633 161.004669 492.194305 185.569657 507.282135 198.650726
00:09:58.840245366 418.764465 121.211830 437.265686 124.925872 408.706879 148.632080 423.572205 135.010574 403.317353 167.953598 ... 459.109069 139.845016 480.203215 153.216278 465.769653 161.612426 494.342407 186.055055 509.487884 199.299560
00:09:58.880184024 417.745423 119.924720 436.591827 123.725265 407.754945 145.980683 422.490540 133.576019 404.400087 162.526779 ... 459.507567 142.085800 482.209348 155.651763 468.152923 164.586746 496.519652 187.808962 510.491302 200.315918
00:09:58.920122683 417.745423 119.924720 436.591827 123.725265 407.754945 145.980683 422.490540 133.576019 404.400087 162.526779 ... 459.507567 142.085800 482.209348 155.651763 468.152923 164.586746 496.519652 187.808962 510.491302 200.315918
00:09:58.960061341 417.745423 119.924720 436.591827 123.725265 407.754945 145.980683 422.490540 133.576019 404.400087 162.526779 ... 459.507567 142.085800 482.209348 155.651763 468.152923 164.586746 496.519652 187.808962 510.491302 200.315918

14998 rows × 44 columns

After filtering to keep only the C57Bl6 (“B”), there are only 22 features left (scroll right to see that, as expected, no features related to “W” remain):

[23]:
coords.filter_id("B")['20191204_Day2_SI_JB08_Test_56']
[23]:
B_Center B_Left_bhip B_Left_ear B_Left_fhip B_Nose ... B_Right_ear B_Right_fhip B_Spine_1 B_Spine_2 B_Tail_base
x y x y x y x y x y ... x y x y x y x y x y
00:00:00 538.363647 249.284027 552.938171 238.256393 526.389221 298.484528 554.218140 263.846924 497.151367 300.893616 ... 515.534424 278.740295 524.835266 258.351959 536.314819 269.603027 536.819580 230.367477 530.644226 211.947082
00:00:00.039938658 538.369385 249.327469 552.927368 238.261719 526.412781 298.484711 554.220703 263.867645 497.139038 300.743500 ... 515.557556 278.727722 524.861694 258.385285 536.332397 269.632355 536.820496 230.371612 530.645569 211.948883
00:00:00.079877316 538.341919 249.331680 552.979980 238.308563 526.437988 298.471222 554.231751 263.856079 497.157898 300.888397 ... 515.525696 278.735718 524.850891 258.373383 536.320923 269.619812 536.835327 230.389618 530.657898 211.960388
00:00:00.119815975 538.328064 249.476318 553.003174 238.238953 526.427002 298.530182 554.242981 263.896118 497.196014 300.982727 ... 515.538391 278.738708 524.852112 258.445496 536.338318 269.629272 536.825806 230.358459 530.663635 211.948959
00:00:00.159754633 538.361267 249.156189 552.935913 238.178787 526.232361 298.454224 554.304565 263.707153 497.193512 300.888733 ... 515.538025 278.705048 524.844055 258.242065 536.360901 269.499512 536.811890 230.327743 530.650696 211.961029
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
00:09:58.800306707 417.213013 120.369354 434.364075 121.799324 407.657898 149.051193 420.671143 132.926590 403.773225 168.571396 ... 389.377777 147.743684 402.071594 119.623657 406.150024 129.519211 430.530731 113.965172 448.407562 110.662156
00:09:58.840245366 418.764465 121.211830 437.265686 124.925872 408.706879 148.632080 423.572205 135.010574 403.317353 167.953598 ... 389.137940 146.219651 402.076691 119.482201 407.196777 129.546539 432.573608 114.848869 448.386139 111.038552
00:09:58.880184024 417.745423 119.924720 436.591827 123.725265 407.754945 145.980683 422.490540 133.576019 404.400087 162.526779 ... 387.196167 144.977769 399.367402 117.643234 406.196411 127.711792 431.796935 114.095085 448.092437 110.713548
00:09:58.920122683 417.745423 119.924720 436.591827 123.725265 407.754945 145.980683 422.490540 133.576019 404.400087 162.526779 ... 387.196167 144.977769 399.367402 117.643234 406.196411 127.711792 431.796935 114.095085 448.092437 110.713548
00:09:58.960061341 417.745423 119.924720 436.591827 123.725265 407.754945 145.980683 422.490540 133.576019 404.400087 162.526779 ... 387.196167 144.977769 399.367402 117.643234 406.196411 127.711792 431.796935 114.095085 448.092437 110.713548

14998 rows × 22 columns

Now that we have a basic understanding of how to create and interact with a project, coordinates, and table dictionaries, let’s show some plots!

Basic visual exploration

Let’s first see some basic heatmaps per condition. All plotting functions within DeepOF are hosted in the deepof.visuals module. Among many other things, we can plot average heatmaps per experimental condition! Let’s see if we can visualize ant interesting patterns on the available data:

[24]:
sns.set_context("notebook")

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))

deepof.visuals.plot_heatmaps(
    my_deepof_project,
    ["B_Nose"],
    center="arena",
    exp_condition="CSDS",
    condition_value="Nonstressed",
    ax=ax1,
    show=False,
    display_arena=True,
    experiment_id="average",
)

deepof.visuals.plot_heatmaps(
    my_deepof_project,
    ["B_Nose"],
    center="arena",
    exp_condition="CSDS",
    condition_value="Stressed",
    ax=ax2,
    show=False,
    display_arena=True,
    experiment_id="average",
)

plt.tight_layout()
plt.show()
../_images/tutorial_notebooks_deepof_preprocessing_tutorial_70_0.png

It seems stressed animals spend more time closer to the walls of the arena, and less time in the center! For details on how deepof.visuals.plot_heatmap() works, feel free to check the full API reference or the function docstring.

Finally, let’s create an animated video showing our newly preprocessed data. DeepOF can produce reconstructions of the tracks and show them as videos. All animals and the arena are displayed by default. This is particularly useful when interpreting clusters and visualizing embeddings in the unsupervised pipeline, as we’ll see in a later turorial.

[25]:
from IPython import display

video = deepof.visuals.animate_skeleton(
    my_deepof_project,
    experiment_id="20191204_Day2_SI_JB08_Test_54",
    frame_limit=500,
    dpi=60,
)

html = display.HTML(video)
display.display(html)
plt.close()

What’s next

That’s it for this tutorial. Next, we’ll see how to run a supervised annotation pipeline with pretrained models!