library(animovement)
#> -- Attaching packages ------------------------------------- animovement 0.7.3 --
#> v aniframe 0.6.0 v anicheck 0.2.0
#> v aniread 0.5.0 v animetric 0.3.2
#> v anispace 0.1.3 v anivis 0.2.0
#> v aniprocess 0.2.0
library(tibble)
library(dplyr, warn.conflicts = FALSE)
library(here)
#> here() starts at /home/runner/work/animovement/animovement
here::i_am("vignettes/articles/clean-tracks.Rmd")
#> here() starts at /home/runner/work/animovement/animovementThe next step in our workflow is to clean the tracks. This step commonly covers three separate components:
Although these are not always completely separate steps in practice, we will treat them as such to ensure the integrity of our tracks.
Interpolation
In case we removed any outliers, we can now interpolate across the gaps created.
TO BE CONTINUED…
Smoothing
All there is left to do is smooth our tracks, which is done using the
smooth_tracks() function. The smoothing itself is super
simple. smooth_tracks() provides a few different
options:
roll_meanroll_median-
SOON
savitsky_golay
For the rolling filters you can provide the
window_width, i.e. how many observations to use in the
rolling filter. The filters result in some NA values at the
beginning and end of your data.
An important point about smooth_tracks() is that,
instead of using our x and y values, it first
back-transforms into the raw values obtained from your sensors (which
are effectively “differences” between coordinates, so dx
and dy) and performs the smoothing on them, before finally
converting back to x and y. This may seem
strange if you have previously worked with tracking data from computer
vision or GPS loggers. However, whereas those modalities would return to
the “true” coordinates after an outlier, mouse sensors do not. So the
only way we can identify rogue values is by filtering those raw
values.
Let’s try smoothing our data with a rolling_mean filter
with 0.5 second (30 observations at at a sampling rate of 60Hz) window
width. In case we work with multiple keypoints and/or individuals we can
use group_by with our metadata for a
tidyverse-friendly workflow.
df_rollmedian <- df |>
filter_aniframe(method = "rollmedian",
window_width = 3,
use_derivatives = TRUE)
df_kalman <- df |>
filter_aniframe(method = "kalman",
sampling_rate = 60,
use_derivatives = TRUE)
df_sgolay <- df |>
filter_aniframe(method = "sgolay",
sampling_rate = 60,
use_derivatives = TRUE)
df_lowpass <- df |>
filter_aniframe(method = "lowpass",
cutoff_freq = 0.1,
sampling_rate = 60,
use_derivatives = TRUE)Let’s visualise how they compare. Note that although the difference may seem negligible when plotting paths, they may become important when computing derivatives such as velocity and acceleration.
library(ggplot2)
ggplot() +
geom_path(data = df, aes(x, y), colour = "red") +
geom_path(data = df_rollmedian, aes(x, y), colour = "blue") +
geom_path(data = df_kalman, aes(x, y), colour = "orange") +
geom_path(data = df_sgolay, aes(x, y), colour = "purple") +
geom_path(data = df_lowpass, aes(x, y), colour = "green")
#> Warning: Removed 1 row containing missing values or values outside the scale range
#> (`geom_path()`).
#> Warning: Removed 19 rows containing missing values or values outside the scale range
#> (`geom_path()`).
Not that different as the sensors are doing a good job! But we can see that the smoothed track end a bit further to the left than the raw version.