Complementary filtering for acceleration: low- & high-frequency
tagtools project team
2023-07-12
Source:vignettes/articles/acceleration-filtering.Rmd
acceleration-filtering.Rmd
Welcome to the acceleration-filtering
vignette! Thanks
for taking some time to learn our package; we hope it’s, thus far, all
you’ve dreamed it would be.
In this vignette, you will explore postural dynamics and specific acceleration in the time and frequency domains. Specifically, you’ll load in some new data and use complementary filters to separate intervals of movements into distinct frequency bands, i.e., posture (low frequency) and strikes/flinches (high frequency), in order to gain insight about the movements of a beaked whale.
Estimated time for this practical: 20 minutes.
These vignettes all assume that you have R/Rstudio installed on your machine, some basic experience working with them, and can execute provided code, making some user-specific changes along the way (e.g. to help R find a file you downloaded). We will provide you with quite a few lines. To boost your own learning, you would do well to try and write them before opening what we give, using this just to check your work.
Additionally, be careful when copy-pasting special characters such as
_underscores_ and ‘quotes’. If you get an error, one thing to check is
that you have just a single, simple underscore, and
'straight quotes'
, whether 'single'
or
"double"
(rather than “smart quotes”).
Setup
For this vignette we will use data from a suction cup tag attached to the back of a beaked whale. If you want to run this example, download the “testset1.nc” file from https://github.com/animaltags/tagtools_data and change the file path to match where you’ve saved the files
Write testset1
to the object bw
, for
“beaked whale”. Then use plott()
to inspect it.
library(tagtools)
bw_file_path <- "nc_files/testset1.nc"
bw <- load_nc(bw_file_path)
plott(X = list(Depth = bw$P, Acc = bw$A, Mag = bw$M))
This dataset contains a deep dive followed by a shallow dive. We want to infer the function of these by looking for locomotion effort and sudden changes in acceleration that could be indicative of prey capture attempts. We are also going to look for changes in swimming gait.
Complementary filtering
To separate slow orientation changes from postural dynamics in
locomotion, we need to choose a filter cut-off frequency below the
stroke frequency. We can estimate the dominant stroke frequency using
dsf()
. There is a bout of steady swimming between minutes
35 and 42 in the data. Use crop_to()
to pick out the
accelerometer data in that interval:
Mimic the previous code to similarly crop the pressure (depth) and magnetometer data:
Dseg <- crop_to(X = bw$P, tcues = c(35*60, 42*60))
Mseg <- crop_to(X = bw$M, tcues = c(35*60, 42*60))
Plot the three of them together to make sure you got it right.
Then, run dsf()
on Aseg
to get the mean
stroking rate:
dsfa <- dsf(Aseg)$fpk # estimated stroking rate in Hz
Try doing the same with the magnetometer data:
dsfm <- dsf(Mseg)$fpk # another estimate
Are the estimated stroke rates similar?
Yes, they should be. The acceleration-based estimate is .3821574, and the magnetometer-based estimate is .3807932.
The magnetometer is not sensitive to specific acceleration, so why do
stroking motions show up in Mseg
?
The animal is changing its position, so the magnetometer reading will change.
Refer to your plots of Mseg
and Dseg
to try
and figure out which axis the stroking motions show up in.
A good starting choice for the filter cut-off frequency is 70% of the
stroking rate (pick one of the estimates, or average the two). Call this
value fc
. Run a complementary filter on A
to
separate the slow and fast time-scales. Recall that A
is
stored under bw
as bw$A
:
fc <- 'YourValueHere' # your value for fc in Hz, a number, without quotes
Af <- comp_filt(bw$A, fc = fc)
str(Af, max.level = 1)
#> List of 2
#> $ lowpass : num [1:137976, 1:3] -1.22 -1.22 -1.21 -1.19 -1.17 ...
#> $ highpass: num [1:137976, 1:3] -0.624 -0.558 -0.516 -0.511 -0.471 ...
The complementary filter returns a list containing two data matrices: the low-pass filtered data and the high-pass filtered data. Each of these is a three-column matrix because the filter is run on each column of the input data. So it is like you now have two accelerometers in the tag—one is only sensitive to low frequencies and the other is only sensitive to high frequencies. If you would like to get each matrix out of the cell array, do:
Alow <- Af[[1]] # low frequency A data
Ahigh <- Af[[2]] # high frequency A data
The sampling rate of these is the same as for the original data. For
simplicity, make a variable sampling_rate
equal to the
sampling rate and use plott()
to plot the two filtered
accelerations along with the dive profile:
sampling_rate <- bw$A$sampling_rate
plott(X = list(`Depth (m)` = bw$P$data,
`LF Accel` = Alow,
`HF Accel` = Ahigh),
fsx = sampling_rate)
These two versions of acceleration are sometimes called ‘static’ and
‘dynamic’ acceleration. If the filtering worked, you should see that
Alow
has large, relatively slow changes in acceleration
which are mostly to do with orientation. These are missing in
Ahigh
, which has the high frequency specific acceleration,
having to do with propulsion and strikes or flinches.
If you like, zoom in to the section of steady stroking that you used
for dsf
—you should only see the stroking in
Ahigh
, not in Alow
.
plott(X = list(`Depth` = bw$P$data,
`LF Accel` = Alow,
`HF Accel` = Ahigh),
fsx = sampling_rate, xlim = c(35*60, 42*60)/3600)
Locomotion style
We want to characterise the locomotion style of the animal during the
deep-dive ascent. Using the plot you made above, zoom in on the ascent
and see if you can identify intervals in which the animal appears to be
just swimming steadily (hint: look for when Alow
is fairly
constant, indicating a steady orientation).
Do you see any changes in swimming style throughout the ascent? In
particular, check out the swimming styles in time intervals (1) 36-38
minutes and (2) 56-58 minutes. Write two objects (say,
intvl1
and intvl2
) that contain these
intervals in seconds, using c(YourStartTime, YourEndTime)
.
Then, crop out the high frequency acceleration data in each of these
intervals using crop_to(...)
. (Try and write the code
yourself!)
intvl1 <- c(36*60, 38*60)
intvl2 <- c(56*60, 58*60)
Ah1 <- crop_to(Ahigh, sampling_rate = sampling_rate, tcues = intvl1)
Ah2 <- crop_to(Ahigh, sampling_rate = sampling_rate, tcues = intvl2)
Now plott these together. Be careful when comparing, since plott puts them on different y-scales automatically to fit the data to the window!
Comparing these two intervals of swimming, what do you conclude about the swimming styles? Look at the magnitude of the acceleration (the units are in \(m/s^2\)). You may want to compare the depth data in these intervals, too.
D1 <- crop_to(bw$P$data, sampling_rate = sampling_rate, tcues = intvl1)
D2 <- crop_to(bw$P$data, sampling_rate = sampling_rate, tcues = intvl2)
plot(x = (1:length(D1)), y = (-D1), xlab = "samples", ylab = "Depth") # plott doesn't make it very clear
plot(x = (1:length(D2)), y = (-D2), xlab = "samples", ylab = "Depth") # so we'll just use plot
Does one swimming style seem more energetic than the other?
The magnitude of acceleration in each peak is lower in the first interval (between \(-0.5\) and \(+1\) \(m/s^2\)), and this time interval is a time when the animal is swimming steadily upwards. However, the acceleration is changing quickly back and forth: the peaks come much more frequently, so that there are 46 of them within the two minutes. The second time interval is different, with higher magnitudes of acceleration (between \(-3\) and \(+4\) \(m/s^2\)). This time interval is also a time when the animal is swimming slightly upwards, but there are more visually obvious “bumps” in the path upwards, which actually correspond to the individual acceleration peaks. Possibly, rather than just ascending to the surface, the whale is exerting itself, accelerating very quickly, to try to catch prey. Overall, this second swimming style seems more energetic than the first.
Finally go back and plot the full high frequency acceleration data,
Ahigh
, then a portion of it cropped to the shallow dive.
Use this to see whether there is active swimming in the shallow dive
and, if so, which swimming gait is used there.
plott(X = list(`Depth` = bw$P$data,
`LF Accel` = Alow,
`HF Accel` = Ahigh),
fsx = sampling_rate)
intvl3 <- c(77*60, 89*60) # whole shallow dive, for context
intvl4 <- c(77*60, 79*60) # earlier in the shallow dive
intvl5 <- c(87*60, 89*60) # later in the shallow dive
Ah3 <- crop_to(X = Ahigh, sampling_rate = sampling_rate, tcues = intvl3)
D3 <- crop_to(X = bw$P$data, sampling_rate = sampling_rate, tcues = intvl3)
Ah4 <- crop_to(X = Ahigh, sampling_rate = sampling_rate, tcues = intvl4)
D4 <- crop_to(X = bw$P$data, sampling_rate = sampling_rate, tcues = intvl4)
Ah5 <- crop_to(X = Ahigh, sampling_rate = sampling_rate, tcues = intvl5)
D5 <- crop_to(X = bw$P$data, sampling_rate = sampling_rate, tcues = intvl5)
plott(X = list(Depth = D3, HfAccel3 = Ah3), fsx = sampling_rate, interactive = TRUE)
plot(x = (1:length(D4)), y = D4)
plott(X = list(HfAccel4 = Ah4), fsx = sampling_rate, interactive = TRUE)
plot(x = (1:length(D5)), y = D5)
plott(X = list(HfAccel5 = Ah5), fsx = sampling_rate, interactive = TRUE)
Any similarities to the swimming earlier? Any differences?
These two intervals look much more similar to intvl2
than intvl3
. Large spikes in acceleration are almost
nonexistent, with the magnitude of acceleration usually neatly
between \(-1\) and \(+1\) \(m/s^2\). Further, the number of peaks
in intvl4
(47 peaks in 2 minutes) is almost exactly the
same as that in intvl2
(46 peaks in 2 minutes).
intvl5
is slightly more complicated. As the whale
approaches zero depth before the end of intvl5
, almost all
acceleration stops momentarily during the final glide to the surface.
Once at the surface, the behavior is more erratic, representing perhaps
a kind of relaxed frolicking, rather than an efficient swim
upwards.
Review
You’ve learned how to separate low- and high-frequency acceleration data with a complementary filter, and done some interpretation of these two sets of data.
Aaaaand… congrats! You’ve aced this vignette.
If you’d like to continue working through these vignettes,
jerk-transients
is a very logical choice. So is
magnetometer-filtering
. These three vignettes all go
together. Perhaps the most logical ordering is
acceleration-filtering
, then jerk-transients
,
then magnetometer-filtering
. But, you do what suits your
fancy/data!
Animaltags home pages: http://animaltags.org/ (old), https://animaltags.netlify.app/ (new), https://github.com/stacyderuiter/TagTools (for latest beta source code), https://stacyderuiter.github.io/TagTools/articles/TagTools (vignettes overview)