Skip to content

Commit f22411e

Browse files
author
Max Czapanskiy
committed
Create supplemental
1 parent 06fcf18 commit f22411e

File tree

4 files changed

+169
-23
lines changed

4 files changed

+169
-23
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
.Rdata
44
.httr-oauth
55
.DS_Store
6-
analysis/paper/paper_cache/*
6+
analysis/*/*_cache/*
7+
analysis/*/*_files/*

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Imports:
1717
bookdown,
1818
cowplot,
1919
dplyr,
20+
ggrepel,
2021
ggplot2,
2122
here,
2223
lubridate,
Lines changed: 166 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11
---
2-
title: "Supplementary Materials"
2+
title: "Supplemental material for:"
3+
subtitle: "An accelerometer-derived ballistocardiogram method for detecting heartrates in free-ranging marine mammals"
34
date: "`r format(Sys.time(), '%d %B, %Y')`"
4-
output: pdf_document
5+
output:
6+
bookdown::pdf_document2:
7+
fig_caption: yes
8+
toc: false
9+
header-includes:
10+
- \renewcommand{\figurename}{Figure S}
11+
- \makeatletter
12+
- \def\fnum@figure{\figurename\thefigure}
13+
- \makeatother
514
---
615

716
```{r setup, echo=FALSE, message=FALSE}
817
knitr::opts_chunk$set(
9-
echo=FALSE,
10-
fig.height = 6,
11-
fig.width = 6,
18+
echo = FALSE,
19+
cache = TRUE,
20+
message = FALSE,
21+
fig.height = 5,
22+
fig.width = 5,
1223
dpi = 300
1324
)
1425
library(cetaceanbcg)
1526
library(tidyverse)
1627
```
1728

18-
```{r motionless}
29+
```{r bw-data}
1930
bw_elg <- bw180905_53_elg %>%
2031
mutate(
2132
depth_min = map2_dbl(start, stop, function(t1, t2) {
@@ -24,15 +35,48 @@ bw_elg <- bw180905_53_elg %>%
2435
depth_max = map2_dbl(start, stop, function(t1, t2) {
2536
bw180905_53_10hz[between(bw180905_53_10hz$dt, t1, t2), "depth"] %>% max()
2637
}),
38+
depth_med = map2_dbl(start, stop, function(t1, t2) {
39+
bw180905_53_10hz[between(bw180905_53_10hz$dt, t1, t2), "depth"] %>%
40+
first() %>%
41+
median()
42+
}),
2743
ygyr_min = map2_dbl(start, stop, function(t1, t2) {
2844
bw180905_53_10hz[between(bw180905_53_10hz$dt, t1, t2), ]$gw[,2] %>% min()
2945
}),
3046
ygyr_max = map2_dbl(start, stop, function(t1, t2) {
3147
bw180905_53_10hz[between(bw180905_53_10hz$dt, t1, t2), ]$gw[,2] %>% max()
32-
})
48+
}),
49+
ygyr_med = map2_dbl(start, stop, function(t1, t2) {
50+
bw180905_53_10hz[between(bw180905_53_10hz$dt, t1, t2), ]$gw[,2] %>%
51+
first() %>%
52+
median()
53+
}),
3354
)
34-
bw10hz <- bw180905_53_10hz %>%
55+
56+
bw_10hz <- bw180905_53_10hz %>%
57+
mutate(rid_left = approx(bw_elg$start,
58+
bw_elg$region_id,
59+
dt,
60+
"constant")$y,
61+
rid_right = approx(bw_elg$stop,
62+
bw_elg$region_id,
63+
dt,
64+
"constant",
65+
yleft = 0)$y + 1,
66+
region_id = ifelse(rid_left == rid_right, rid_left, NA),
67+
dive_id = split_dives(dt, depth,
68+
surface = 2, min_depth = 10, min_dur = 5 * 60))
69+
70+
bw_400hz <- bw180905_53_400hz %>%
3571
mutate(
72+
across(surge:heave,
73+
filter_acc, fs = 400, upper = 10.0,
74+
.names = "{.col}_filt"),
75+
jerk = jerk(cbind(surge_filt, sway_filt, heave_filt),
76+
fs = 400, p = 4, n = 2 * 400 + 1),
77+
jerk_se = shannon_entropy(jerk),
78+
jerk_smooth = tma(jerk_se, 2 * 400),
79+
# Annotate regions
3680
rid_left = approx(bw_elg$start,
3781
bw_elg$region_id,
3882
dt,
@@ -42,44 +86,144 @@ bw10hz <- bw180905_53_10hz %>%
4286
dt,
4387
"constant",
4488
yleft = 0)$y + 1,
45-
motion_class = case_when(
46-
depth < 2 ~ "surface",
47-
rid_left == rid_right ~ "motionless",
48-
TRUE ~ "active"),
49-
dive_id = split_dives(dt, depth,
50-
surface = 2, min_depth = 10, min_dur = 5 * 60)
89+
region_id = ifelse(rid_left == rid_right, rid_left, NA),
90+
# Zero-out signal in non-valid regions (i.e. remove movement artifacts)
91+
jerk_smooth = ifelse(is.na(region_id), 0, jerk_smooth)
5192
)
5293
```
5394

54-
```{r gyr_entropy, fig.cap="Motionless periods were manually identified by looking for periods of low amplitude rotational veloctiy ($\\omega$) around the lateral axis, indicative of cetaceans' dorso-ventral fluking motion."}
55-
divesample <- filter(bw10hz, dive_id == 1)
95+
```{r peak-heur, fig.cap="**A:** Minor peaks (hollow points) in the ballistocardiogram (BCG, line) were not considered heart beats. Only major peaks (solid points) were retained for analysis. The BCG for one motionless period shown here. **B:** We used peak height and prominence (i.e. height relative to the contour surrounding a higher peak) to heuristically differentiate major and minor peaks. For each peak, we calculated the Euclidean distance (in height-prominence space) to the highest peak overall. The peaks in **A** shown here in height-prominence space. **C:** The distance to the highest peak exhibited a bimodal distribution. We chose a distance threshold (dashed red line) corresponding to the valley in the density curve. **D:** All peaks found in the BCG across all motionless periods in height-prominence space. Solid and hollow points as in **A**. The dashed red curve corresponds to the distance threshold in **C**."}
96+
fs_hz <- 400
97+
minperiod <- 2
98+
cue <- bw_400hz$jerk_smooth
99+
peaks <- pracma::findpeaks(cue, minpeakdistance = fs_hz * minperiod)
100+
proms <- peak_prominences(cue, peaks[, 2])
101+
102+
dist_max <- sqrt((max(peaks[, 1]) - peaks[, 1])^2 + (max(proms) - proms)^2)
103+
dist_dens <- stats::density(dist_max)
104+
dist_thr <- dist_dens$x[pracma::findpeaks(-dist_dens$y)[, 2]]
105+
is_beat <- peaks[, 2][dist_max <= dist_thr]
106+
beats <- logical(length(cue))
107+
beats[is_beat] <- TRUE
108+
109+
peak_tbl <- tibble(dt = bw_400hz$dt[peaks[, 2]],
110+
height = peaks[, 1],
111+
prominence = proms,
112+
is_beat = dist_max <= dist_thr)
113+
114+
regionsample <- filter(bw_400hz, region_id == 6)
115+
peaksample <- filter(peak_tbl, between(dt,
116+
first(regionsample$dt),
117+
last(regionsample$dt))) %>%
118+
arrange(dt) %>%
119+
mutate(peak_lbl = seq(n()))
120+
121+
max_height <- max(peaks[, 1])
122+
thr_curve <- tibble(
123+
t = seq(0, 2 * pi, length.out = 1000),
124+
height = max_height + cos(t) * dist_thr,
125+
prominence = max_height + sin(t) * dist_thr,
126+
) %>%
127+
filter(height <= max_height, prominence <= max_height)
128+
129+
pA <- ggplot(regionsample, aes(dt, jerk_smooth)) +
130+
geom_line() +
131+
geom_point(aes(y = height, shape = is_beat), data = peaksample) +
132+
geom_text(aes(y = height, label = peak_lbl),
133+
data = peaksample,
134+
vjust = -0.3) +
135+
scale_shape_manual(values = c(21, 16)) +
136+
labs(y = "Y") +
137+
theme_classic(base_size = 10) +
138+
theme(axis.title.x = element_blank(),
139+
legend.position = "FALSE")
140+
141+
pB <- ggplot(peaksample, aes(height, prominence, shape = is_beat)) +
142+
geom_point() +
143+
ggrepel::geom_text_repel(aes(label = peak_lbl), force = 3) +
144+
scale_shape_manual(values = c(21, 16)) +
145+
scale_x_continuous(n.breaks = 4) +
146+
labs(x = "Peak height",
147+
y = "Peak prominence") +
148+
coord_fixed() +
149+
theme_classic(base_size = 10) +
150+
theme(legend.position = "FALSE")
151+
152+
pC <- ggplot(peak_tbl, aes(dist_max)) +
153+
geom_density() +
154+
geom_vline(xintercept = dist_thr, linetype = 2, color = "red") +
155+
scale_x_continuous(n.breaks = 4) +
156+
labs(x = "Distance from highest peak") +
157+
theme_classic(base_size = 10)
158+
159+
pD <- ggplot(peak_tbl, aes(height, prominence)) +
160+
geom_point(aes(shape = is_beat)) +
161+
geom_line(data = thr_curve, linetype = 2, color = "red") +
162+
scale_shape_manual(values = c(21, 16)) +
163+
labs(x = "Peak height",
164+
y = "Peak prominence") +
165+
coord_fixed() +
166+
theme_classic(base_size = 10) +
167+
theme(legend.position = "FALSE")
168+
169+
cowplot::plot_grid(pA, pB, pC, pD, ncol = 2, labels = "AUTO")
170+
```
171+
172+
```{r ygyr, fig.cap="Motionless periods were manually identified by looking for periods of low amplitude rotational veloctiy ($\\omega$) around the lateral axis, indicative of cetaceans' dorso-ventral fluking motion. The depth and $\\omega$ profiles for the first dive shown here, with motionless periods in pink."}
173+
divesample <- filter(bw_10hz, dive_id == 1)
174+
norm_y <- function(ymin, ymax, sign) {
175+
(ymin + ymax) / 2 + sign * max(ymax - ymin) / 2
176+
}
56177
diveelg <- bw_elg %>%
57-
filter(start >= first(divesample$dt), stop <= last(divesample$dt))
178+
filter(start >= first(divesample$dt), stop <= last(divesample$dt)) %>%
179+
mutate(depth_min2 = norm_y(depth_min, depth_max, -1),
180+
depth_max2 = norm_y(depth_min, depth_max, 1),
181+
ygyr_min2 = norm_y(ygyr_min, ygyr_max, -1),
182+
ygyr_max2 = norm_y(ygyr_min, ygyr_max, 1))
58183
59184
depth_profile <- ggplot(divesample, aes(dt, depth)) +
60185
geom_line() +
61-
geom_rect(aes(xmin = start, xmax = stop, ymin = depth_min, ymax = depth_max),
186+
geom_rect(aes(xmin = start,
187+
xmax = stop,
188+
ymin = depth_min2,
189+
ymax = depth_max2),
62190
diveelg,
63191
inherit.aes = FALSE,
64192
fill = "orchid1",
65193
color = NA,
66194
alpha = 0.5) +
67195
scale_x_datetime(date_labels = "%H:%M:%S") +
68196
scale_y_reverse("Depth (m)") +
69-
theme_classic() +
197+
theme_classic(base_size = 10) +
70198
theme(axis.title.x = element_blank())
71199
ygyr_profile <- ggplot(divesample, aes(dt, gw[, 2])) +
72200
geom_line() +
73-
geom_rect(aes(xmin = start, xmax = stop, ymin = ygyr_min, ymax = ygyr_max),
201+
geom_rect(aes(xmin = start, xmax = stop, ymin = ygyr_min2, ymax = ygyr_max2),
74202
diveelg,
75203
inherit.aes = FALSE,
76204
fill = "orchid1",
77205
color = NA,
78206
alpha = 0.5) +
79207
scale_x_datetime(date_labels = "%H:%M:%S") +
80208
scale_y_continuous(bquote(omega~(rad~s^{-1}))) +
81-
theme_classic() +
209+
theme_classic(base_size = 10) +
210+
theme(axis.title.x = element_blank())
211+
cowplot::plot_grid(depth_profile, ygyr_profile,
212+
ncol = 1,
213+
align = "v",
214+
labels = "AUTO")
215+
```
216+
217+
```{r bw-profile, fig.cap="Two hours of blue whale tag data were used for ballistocardiographic analysis. During these hours, the animal made repeated, shallow dives. Because the BCG can be obscured by dynamic body movements, only relatively motionless periods were retained for analysis (pink). These periods were identified by visual examination of the rotational velocity around the lateral axis."}
218+
ggplot(bw_10hz, aes(dt, depth)) +
219+
geom_line(size = 0.5) +
220+
geom_line(aes(group = region_id),
221+
data = drop_na(bw_10hz, region_id),
222+
size = 0.5,
223+
color = "orchid1") +
224+
scale_x_datetime(date_labels = "%H:%M:%S") +
225+
scale_y_reverse("Depth (m)") +
226+
theme_classic(base_size = 10) +
82227
theme(axis.title.x = element_blank())
83-
cowplot::plot_grid(depth_profile, ygyr_profile, ncol = 1, align = "v")
84228
```
85229

509 KB
Binary file not shown.

0 commit comments

Comments
 (0)