1
1
---
2
- title : " Supplementary Materials"
2
+ title : " Supplemental material for:"
3
+ subtitle : " An accelerometer-derived ballistocardiogram method for detecting heartrates in free-ranging marine mammals"
3
4
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
5
14
---
6
15
7
16
``` {r setup, echo=FALSE, message=FALSE}
8
17
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,
12
23
dpi = 300
13
24
)
14
25
library(cetaceanbcg)
15
26
library(tidyverse)
16
27
```
17
28
18
- ``` {r motionless }
29
+ ``` {r bw-data }
19
30
bw_elg <- bw180905_53_elg %>%
20
31
mutate(
21
32
depth_min = map2_dbl(start, stop, function(t1, t2) {
@@ -24,15 +35,48 @@ bw_elg <- bw180905_53_elg %>%
24
35
depth_max = map2_dbl(start, stop, function(t1, t2) {
25
36
bw180905_53_10hz[between(bw180905_53_10hz$dt, t1, t2), "depth"] %>% max()
26
37
}),
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
+ }),
27
43
ygyr_min = map2_dbl(start, stop, function(t1, t2) {
28
44
bw180905_53_10hz[between(bw180905_53_10hz$dt, t1, t2), ]$gw[,2] %>% min()
29
45
}),
30
46
ygyr_max = map2_dbl(start, stop, function(t1, t2) {
31
47
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
+ }),
33
54
)
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 %>%
35
71
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
36
80
rid_left = approx(bw_elg$start,
37
81
bw_elg$region_id,
38
82
dt,
@@ -42,44 +86,144 @@ bw10hz <- bw180905_53_10hz %>%
42
86
dt,
43
87
"constant",
44
88
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)
51
92
)
52
93
```
53
94
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
+ }
56
177
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))
58
183
59
184
depth_profile <- ggplot(divesample, aes(dt, depth)) +
60
185
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),
62
190
diveelg,
63
191
inherit.aes = FALSE,
64
192
fill = "orchid1",
65
193
color = NA,
66
194
alpha = 0.5) +
67
195
scale_x_datetime(date_labels = "%H:%M:%S") +
68
196
scale_y_reverse("Depth (m)") +
69
- theme_classic() +
197
+ theme_classic(base_size = 10 ) +
70
198
theme(axis.title.x = element_blank())
71
199
ygyr_profile <- ggplot(divesample, aes(dt, gw[, 2])) +
72
200
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 ),
74
202
diveelg,
75
203
inherit.aes = FALSE,
76
204
fill = "orchid1",
77
205
color = NA,
78
206
alpha = 0.5) +
79
207
scale_x_datetime(date_labels = "%H:%M:%S") +
80
208
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) +
82
227
theme(axis.title.x = element_blank())
83
- cowplot::plot_grid(depth_profile, ygyr_profile, ncol = 1, align = "v")
84
228
```
85
229
0 commit comments