How to analyse a (Stytra) embedded tail tracking experiment¶
In this tutorial we show how to use bouter to analyse data that was aquired with Stytra.
First, some imports:
[1]:
from bouter import EmbeddedExperiment
from bouter import ASSETS_PATH
Given a folder containing data from a Stytra experiment, it’s very easy to instantiate an Experiment
object. Here we use example bouter data, otherwise replace the path with the folder to your data:
[2]:
experiment_data_path = ASSETS_PATH / "embedded_dataset"
# Initialize the experiment:
exp = EmbeddedExperiment(experiment_data_path)
The Experiment
class¶
Experiment
objects are objects that behaves like dictionariers with extended magical properties.
They have keys, reflecting the Stytra metadata:
[3]:
print(exp.keys())
dict_keys(['camera_params', 'general', 'gui', 'stimulus', 'behavior'])
And looking inside these keys, we found Stytra hierarchical data log:
[4]:
print(exp["general"]["animal"])
{'age': 8, 'comments': '', 'dish_diameter': '30', 'embedded': True, 'genotype': 'TL', 'id': 10, 'screened': 'not', 'species': 'Danio Rerio', 'treatment': ''}
In addition, there are some values (properties) that are computed from the metadata, invoked in this way:
[5]:
# sampling dt of the behavioral tracking
exp.behavior_dt
[5]:
0.003343767676767584
There’s several useful properties. E.g.
Fish id from folder:
[6]:
exp.fish_id
[6]:
'181115_f10'
Unique id for the experiment:
[7]:
exp.full_name
[7]:
'181115_f10_192316'
Protocol name:
[8]:
exp.protocol_name
[8]:
'closed_open_loop'
Simplified protocol parameters dict:
[9]:
exp.protocol_parameters
[9]:
{'grating_cycle': 10,
'grating_duration': 4.0,
'inter_stim_pause': 0,
'n_repeats': 1,
'post_pause': 0.0,
'pre_pause': 0.0}
Start and end times of each stimulus in the log:
[10]:
(exp.stim_start_times, exp.stim_end_times)
[10]:
(array([0.002]), array([600.004995]))
The behavioural logs¶
The bulk of the behavioural data is contained in the stytra log files. With properties you can look at the logs of the experiments, returned as pd.DataFrame
s; depending on what experiment you run you’ll find for example:
Experiment.stimulus_log
: the dynamic log of stytra stimulus
[11]:
exp.stimulus_log.head()
[11]:
closed loop 1D_vel | closed loop 1D_base_vel | closed loop 1D_gain | closed loop 1D_lag | closed loop 1D_fish_swimming | t | |
---|---|---|---|---|---|---|
0 | -10.000000 | -10.0 | 1.0 | 0.0 | 0.0 | 0.026302 |
1 | -10.000000 | -10.0 | 1.0 | 0.0 | 0.0 | 0.070306 |
2 | -10.000000 | -10.0 | 1.0 | 0.0 | 0.0 | 0.104309 |
3 | -10.000000 | -10.0 | 1.0 | 0.0 | 0.0 | 0.158315 |
4 | 1.554264 | -10.0 | 1.0 | 0.0 | 1.0 | 0.189318 |
Experiment.behavior_log
: the dynamic log of the tracked tail (or eye, position, etc.):
[12]:
exp.behavior_log.head()
[12]:
tail_sum | theta_00 | theta_01 | theta_02 | theta_03 | theta_04 | theta_05 | theta_06 | theta_07 | theta_08 | ... | theta_16 | theta_17 | theta_18 | theta_19 | theta_20 | theta_21 | theta_22 | theta_23 | theta_24 | t | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.009258 | 0.054718 | 0.006608 | -0.003954 | 0.002449 | 0.009792 | 0.007008 | -0.012253 | 0.001501 | 0.001586 | ... | -0.000696 | 0.003783 | 0.029968 | 0.078343 | -0.015539 | 0.032789 | 0.030664 | 0.018611 | 0.051973 | 0.000299 |
1 | 0.007062 | 0.054792 | 0.006414 | -0.003654 | 0.002591 | 0.009731 | 0.006824 | -0.012492 | 0.001748 | 0.001355 | ... | 0.000460 | 0.002993 | 0.030286 | 0.075214 | -0.014672 | 0.033336 | 0.030252 | 0.016750 | 0.051518 | 0.004300 |
2 | 0.007431 | 0.054551 | 0.006468 | -0.003410 | 0.003069 | 0.009150 | 0.007100 | -0.012553 | 0.001517 | 0.001845 | ... | 0.000749 | 0.003484 | 0.031352 | 0.073967 | -0.013793 | 0.031789 | 0.030658 | 0.016382 | 0.052068 | 0.007300 |
3 | 0.006643 | 0.054923 | 0.006485 | -0.003741 | 0.002603 | 0.009605 | 0.006857 | -0.012986 | 0.001931 | 0.002025 | ... | 0.000263 | 0.003009 | 0.030150 | 0.075574 | -0.014799 | 0.032864 | 0.031805 | 0.016760 | 0.051292 | 0.010300 |
4 | 0.007127 | 0.054769 | 0.006760 | -0.004054 | 0.002216 | 0.010527 | 0.007025 | -0.012684 | 0.001756 | 0.001611 | ... | -0.000001 | 0.003914 | 0.029962 | 0.073601 | -0.014057 | 0.033337 | 0.030134 | 0.016966 | 0.051691 | 0.014301 |
5 rows × 27 columns
Filter the tail log¶
Sometimes, Stytra will mess the tracking of the last tail segments. Luckily, this can be fixed using a custom interpolation function. It uses n=continue_curvature
points to average the difference of consecutive segments and fill the nan values using the same incremental difference. This in general is safe as long as the tail tracking was not completely off.
To use it, before using the behavior log, run the following:
[13]:
exp.reconstruct_missing_segments(continue_curvature=4)
exp.behavior_log.head()
[13]:
tail_sum | theta_00 | theta_01 | theta_02 | theta_03 | theta_04 | theta_05 | theta_06 | theta_07 | theta_08 | ... | theta_17 | theta_18 | theta_19 | theta_20 | theta_21 | theta_22 | theta_23 | theta_24 | t | missing_n | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.009258 | 0.054718 | 0.006608 | -0.003954 | 0.002449 | 0.009792 | 0.007008 | -0.012253 | 0.001501 | 0.001586 | ... | 0.003783 | 0.029968 | 0.078343 | -0.015539 | 0.032789 | 0.030664 | 0.018611 | 0.051973 | 0.000299 | 0 |
1 | 0.007062 | 0.054792 | 0.006414 | -0.003654 | 0.002591 | 0.009731 | 0.006824 | -0.012492 | 0.001748 | 0.001355 | ... | 0.002993 | 0.030286 | 0.075214 | -0.014672 | 0.033336 | 0.030252 | 0.016750 | 0.051518 | 0.004300 | 0 |
2 | 0.007431 | 0.054551 | 0.006468 | -0.003410 | 0.003069 | 0.009150 | 0.007100 | -0.012553 | 0.001517 | 0.001845 | ... | 0.003484 | 0.031352 | 0.073967 | -0.013793 | 0.031789 | 0.030658 | 0.016382 | 0.052068 | 0.007300 | 0 |
3 | 0.006643 | 0.054923 | 0.006485 | -0.003741 | 0.002603 | 0.009605 | 0.006857 | -0.012986 | 0.001931 | 0.002025 | ... | 0.003009 | 0.030150 | 0.075574 | -0.014799 | 0.032864 | 0.031805 | 0.016760 | 0.051292 | 0.010300 | 0 |
4 | 0.007127 | 0.054769 | 0.006760 | -0.004054 | 0.002216 | 0.010527 | 0.007025 | -0.012684 | 0.001756 | 0.001611 | ... | 0.003914 | 0.029962 | 0.073601 | -0.014057 | 0.033337 | 0.030134 | 0.016966 | 0.051691 | 0.014301 | 0 |
5 rows × 28 columns
Extract and analyse bouts¶
To analyse bouts, we will need to:
Compute vigor
Detect bouts
(Optional) Extract bout statistics
1. Compute vigor: use a rolling standard deviation to infer fish speed. After calling the function, a exp._behavior_log["vigor"]
column is added to our exp._behavior_log
dataframe.
vigor_duration_s
is the window for the rolling std in s; the default 0.05 is the lab standard.
[14]:
exp.compute_vigor(vigor_duration_s=0.05)
exp.behavior_log.head()
[14]:
tail_sum | theta_00 | theta_01 | theta_02 | theta_03 | theta_04 | theta_05 | theta_06 | theta_07 | theta_08 | ... | theta_18 | theta_19 | theta_20 | theta_21 | theta_22 | theta_23 | theta_24 | t | missing_n | vigor | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.009258 | 0.054718 | 0.006608 | -0.003954 | 0.002449 | 0.009792 | 0.007008 | -0.012253 | 0.001501 | 0.001586 | ... | 0.029968 | 0.078343 | -0.015539 | 0.032789 | 0.030664 | 0.018611 | 0.051973 | 0.000299 | 0 | NaN |
1 | 0.007062 | 0.054792 | 0.006414 | -0.003654 | 0.002591 | 0.009731 | 0.006824 | -0.012492 | 0.001748 | 0.001355 | ... | 0.030286 | 0.075214 | -0.014672 | 0.033336 | 0.030252 | 0.016750 | 0.051518 | 0.004300 | 0 | NaN |
2 | 0.007431 | 0.054551 | 0.006468 | -0.003410 | 0.003069 | 0.009150 | 0.007100 | -0.012553 | 0.001517 | 0.001845 | ... | 0.031352 | 0.073967 | -0.013793 | 0.031789 | 0.030658 | 0.016382 | 0.052068 | 0.007300 | 0 | NaN |
3 | 0.006643 | 0.054923 | 0.006485 | -0.003741 | 0.002603 | 0.009605 | 0.006857 | -0.012986 | 0.001931 | 0.002025 | ... | 0.030150 | 0.075574 | -0.014799 | 0.032864 | 0.031805 | 0.016760 | 0.051292 | 0.010300 | 0 | NaN |
4 | 0.007127 | 0.054769 | 0.006760 | -0.004054 | 0.002216 | 0.010527 | 0.007025 | -0.012684 | 0.001756 | 0.001611 | ... | 0.029962 | 0.073601 | -0.014057 | 0.033337 | 0.030134 | 0.016966 | 0.051691 | 0.014301 | 0 | NaN |
5 rows × 29 columns
2. Detect bouts: threshold the vigor to find consecutive periods of continuous swimming. # This will return an array with the index (not the time) for the detected bouts start and end points.
vigor_threshold
is the threshold on the vigor in a.u.; 0.1 is a reasonable value that will work in 95% of cases.
[15]:
exp.get_bouts(vigor_threshold=0.1)
[15]:
array([[ 40, 236],
[ 377, 584],
[ 717, 887],
[1015, 1174]])
3. Extract bouts properties: Infer some properties of the detected bouts. It Returns a dataframe with bout properties.
directionality_duration
is a window in seconds to compute directionality idx; lab standard is 0.07 s.
[16]:
exp.get_bout_properties(directionality_duration=0.07)
[16]:
t_start | duration | peak_vig | med_vig | bias | bias_total | n_pos_peaks | n_neg_peaks | |
---|---|---|---|---|---|---|---|---|
0 | 0.133313 | 0.654065 | 1.934675 | 0.012229 | -0.087974 | -0.026151 | 15 | 15 |
1 | 1.257425 | 0.689069 | 2.929249 | 0.025798 | 0.901800 | 0.169295 | 15 | 14 |
2 | 2.390538 | 0.567057 | 3.014080 | 0.036779 | 0.900419 | 0.223960 | 13 | 12 |
3 | 3.384638 | 0.529053 | 3.910690 | 0.029872 | 0.745277 | 0.130034 | 11 | 13 |
[ ]: