Effect of background noise on schroeder structure detection v3

Fine scale acoustic perception in zebra finches

Author

Marcelo Araya-Salas

Published

June 10, 2025

Source code and data found at https://github.com/maRce10/acoustic-fine-features-zebra-finch

 

1 Purpose

  • Evaluate the effect of degradation on the detection of acoustic fine structural variation in Schroeders

 

Code
source("~/Dropbox/Projects/geographic_call_variation_yellow-naped_amazon/scripts/MRM2.R")

warbleR_options(parallel = 22)

2 Data analysis workflow

flowchart LR
  A[Raw Schroeder\nrecordings] --> B(Aritfically\nDecrease SNR) 
  B --> C(measure\nSchroeder\nsimilarity)
  C --> D(Matrix\nregression\nmodels)
  
style A fill:#44015466
style B fill:#3E4A894D
style C fill:#6DCD594D
style D fill:#FDE7254D

3 Degradation as a function of signal-to-noise ratio

  • Adding synthetic noise to the master sound file annotations to decrease the signal-to-noise
  • Evaluate at which signal-to-noise ratio Schroeder sign can still be distinguished

3.1 On 200ms schroeders

3.1.1 Add synthetic noise

3.1.1.1 Repeated Schroeders

Code
master_annotations <- imp_raven(path = "./data/processed", files = "200ms_schroeders.txt",
    warbler.format = TRUE, all.data = TRUE)

master_annotations$sound.id <- paste0("f0:", master_annotations$f0,
    "_comp:", master_annotations$label)

master_annotations$sound.id[1] <- "start_marker"
master_annotations$sound.id[nrow(master_annotations)] <- "end_marker"

est_master <- selection_table(master_annotations[grep("marker", master_annotations$sound.id,
    invert = TRUE), ], extended = TRUE, mar = 0.1, path = "./data/processed")


# est_schr <-
# readRDS('./data/processed/extended_selection_table_schroeders.RDS')
# est_schr <- readRDS(file.path(path,
# 'extended_sel_table_degradation_exp.RDS')) est_schr$treatment
# <- ifelse(grepl('inside', est_schr$sound.files), 'inside',
# 'outside') est_schr$sound.id <- 1:nrow(est_schr) est_in <-
# est_schr[est_schr$treatment == 'inside', ]

est_master <- signal_to_noise_ratio(est_master, mar = 0.07, cores = 20)

hist(est_master$signal.to.noise.ratio)

adj_snr_schroeder_white <- lapply(30:1, function(i) {
    print(i)
    est_schr_adj <- baRulho::add_noise(X = est_master, target.snr = i,
        precision = 0.1, mar = 0.07, cores = 20, kind = "white", seed = NULL)
    est_schr_adj$target.snr <- i
    return(est_schr_adj)
})

beepr::beep(2)

saveRDS(adj_snr_schroeder_white, "./data/processed/200ms_schroeders_adjusted_snr_white_noise.RDS")

3.1.1.2 Single Schroders

Code
est_schr <- readRDS("./data/processed/extended_selection_table_single_schroeders.RDS")

est_schr$sound.id <- "a"
est_schr <- signal_to_noise_ratio(X = est_schr, mar = 0.01, cores = 20,
    bp = NULL)

hist(est_schr$signal.to.noise.ratio)

adj_snr_schroeder_white <- lapply(30:1, function(i) {
    print(i)
    est_schr_adj <- add_noise(X = est_schr, target.snr = i, precision = 0.1,
        mar = 0.01, cores = 1, bp = NULL, kind = "white", seed = NULL)
    est_schr_adj$target.snr <- i
    return(est_schr_adj)
})

beepr::beep(2)

saveRDS(adj_snr_schroeder_white, "./data/processed/single_schroeder_adjusted_snr_white_noise.RDS")

3.1.2 Check SNR adjusted waves

3.1.2.1 Examples

3.1.2.1.1 Repeated Schroeders

200 Hz, 9 components, positive phase

Code
adj_snr_schroeder <- readRDS("./data/processed/200ms_schroeders_adjusted_snr_white_noise.RDS")

# print(adj_snr_schroeder[[1]]$sound.id[10])

par(mar = c(0, 0, 0, 0), mfrow = c(5, 6))
for (i in 1:30) {
    warbleR:::spectro_wrblr_int(wave = read_sound_file(adj_snr_schroeder[[i]],
        10, from = 0, to = Inf), flim = c(0, 5), osc = FALSE, scale = FALSE,
        axisY = FALSE, axisX = FALSE, grid = FALSE, palette = viridis::mako,
        collevels = seq(-110, 0, 5))
    text(0.2, 4, paste("SNR:", adj_snr_schroeder[[i]]$target.snr[1]),
        col = "black", cex = 1.3)
}

500 Hz, 16 components, positive phase

Code
# print(adj_snr_schroeder[[1]]$sound.id[150])

par(mar = c(0, 0, 0, 0), mfrow = c(5, 6))
for (i in 1:30) {
    warbleR:::spectro_wrblr_int(wave = read_sound_file(adj_snr_schroeder[[i]],
        150, from = 0, to = Inf), flim = c(0, 12), osc = FALSE, scale = FALSE,
        axisY = FALSE, axisX = FALSE, grid = FALSE, palette = viridis::mako,
        collevels = seq(-110, 0, 5))
    text(0.2, 11, paste("SNR:", adj_snr_schroeder[[i]]$target.snr[1]),
        col = "black", cex = 1.3)
}

700 Hz, 24 components, positive phase

Code
# print(adj_snr_schroeder[[1]]$sound.id[250])

par(mar = c(0, 0, 0, 0), mfrow = c(5, 6))
for (i in 1:30) {
    warbleR:::spectro_wrblr_int(wave = read_sound_file(adj_snr_schroeder[[i]],
        250, from = 0, to = Inf), flim = c(0, 20), osc = FALSE, scale = FALSE,
        axisY = FALSE, axisX = FALSE, grid = FALSE, palette = viridis::mako,
        collevels = seq(-110, 0, 5))
    text(0.2, 18, paste("SNR:", adj_snr_schroeder[[i]]$target.snr[1]),
        col = "black", cex = 1.3)
}

3.1.2.1.2 Single Schroeders

200 Hz, 9 components, positive phase

Code
adj_snr_schroeder <- readRDS("./data/processed/single_schroeder_adjusted_snr_white_noise.RDS")

# print(adj_snr_schroeder[[1]]$sound.id[10])

par(mar = c(0, 0, 0, 0), mfrow = c(5, 6))
for (i in 1:30) {

    wv <- read_sound_file(adj_snr_schroeder[[i]], 10)@left

    plot(wv, type = "l", lwd = 2, col = mako(10)[6], xlab = "", ylab = "",
        axes = FALSE, ylim = c(-2, 2))
    box()

    text(length(wv)/2, 1.7, paste("SNR:", adj_snr_schroeder[[i]]$target.snr[1]),
        col = "black", cex = 1.3)
}

200 Hz, 9 components, negative phase

Code
# print(adj_snr_schroeder[[1]]$sound.id[9])

par(mar = c(0, 0, 0, 0), mfrow = c(5, 6))
for (i in 1:30) {

    wv <- read_sound_file(adj_snr_schroeder[[i]], 9)@left

    plot(wv, type = "l", lwd = 2, col = mako(10)[6], xlab = "", ylab = "",
        axes = FALSE, ylim = c(-2, 2))
    box()

    text(length(wv)/2, 1.7, paste("SNR:", adj_snr_schroeder[[i]]$target.snr[1]),
        col = "black", cex = 1.3)
}

500 Hz, 16 components, positive phase

Code
# print(adj_snr_schroeder[[1]]$sound.files[150])

par(mar = c(0, 0, 0, 0), mfrow = c(5, 6))
for (i in 1:30) {

    wv <- read_sound_file(adj_snr_schroeder[[i]], 150)@left

    plot(wv, type = "l", lwd = 2, col = mako(10)[6], xlab = "", ylab = "",
        axes = FALSE, ylim = c(-2, 2))
    box()

    text(length(wv)/2, 1.7, paste("SNR:", adj_snr_schroeder[[i]]$target.snr[1]),
        col = "black", cex = 1.3)
}

500 Hz, 16 components, negative phase

Code
# print(adj_snr_schroeder[[1]]$sound.files[149])

par(mar = c(0, 0, 0, 0), mfrow = c(5, 6))
for (i in 1:30) {

    wv <- read_sound_file(adj_snr_schroeder[[i]], 149)@left

    plot(wv, type = "l", lwd = 2, col = mako(10)[6], xlab = "", ylab = "",
        axes = FALSE, ylim = c(-2, 2))
    box()

    text(length(wv)/2, 1.7, paste("SNR:", adj_snr_schroeder[[i]]$target.snr[1]),
        col = "black", cex = 1.3)
}

700 Hz, 24 components, positive phase

Code
# print(adj_snr_schroeder[[1]]$sound.files[250])

par(mar = c(0, 0, 0, 0), mfrow = c(5, 6))
for (i in 1:30) {

    wv <- read_sound_file(adj_snr_schroeder[[i]], 250)@left

    plot(wv, type = "l", lwd = 2, col = mako(10)[6], xlab = "", ylab = "",
        axes = FALSE, ylim = c(-2, 2))
    box()

    text(length(wv)/2, 1.7, paste("SNR:", adj_snr_schroeder[[i]]$target.snr[1]),
        col = "black", cex = 1.3)
}

700 Hz, 24 components, negative phase

Code
# print(adj_snr_schroeder[[1]]$sound.files[249])

par(mar = c(0, 0, 0, 0), mfrow = c(5, 6))
for (i in 1:30) {

    wv <- read_sound_file(adj_snr_schroeder[[i]], 249)@left

    plot(wv, type = "l", lwd = 2, col = mako(10)[6], xlab = "", ylab = "",
        axes = FALSE, ylim = c(-2, 2))
    box()

    text(length(wv)/2, 1.7, paste("SNR:", adj_snr_schroeder[[i]]$target.snr[1]),
        col = "black", cex = 1.3)
}

3.1.3 Measure waveform similarity

3.1.3.1 On repeated Schroeders

Code
adj_snr_schroeder <- readRDS("./data/processed/200ms_schroeders_adjusted_snr_white_noise.RDS")


dist_white_noise <- lapply(adj_snr_schroeder, function(schroeders) {

    print(schroeders$target.snr[1])
    dtw_dist <- waveform_similarity(X = schroeders, parallel = 22,
        output = "rectangular", sim.method = "DTW", type = "sliding",
        wl = 32)

    cor_dist <- waveform_similarity(X = schroeders, parallel = 22,
        output = "rectangular", sim.method = "correlation", type = "sliding",
        wl = 32)

    dtw_dist$cor <- cor_dist$similarity

    names(dtw_dist) <- c("schr1", "schr2", "dtw", "cor")

    dtw_dist$target.snr <- schroeders$target.snr[1]

    return(dtw_dist)
})


beepr::beep(2)

saveRDS(dist_white_noise, "./data/processed/waveform_similarity_adjusted_snr_200ms_schroeders_white_noise.RDS")

3.1.3.2 On single Schroeders

Code
adj_snr_schroeder <- readRDS("./data/processed/single_schroeder_adjusted_snr_white_noise.RDS")

rep_schroeder <- readRDS("./data/processed/200ms_schroeders_adjusted_snr_white_noise.RDS")[[1]]

dist_white_noise <- lapply(adj_snr_schroeder, function(schroeders) {

    schroeders$sound.id <- sapply(schroeders$sound.files, function(x) {
        a <- strsplit(x, "_")[[1]]
        paste(paste0("f0:", gsub("-", " ", a[2])), gsub("ncomp-",
            "comp:", a[3]), substr(a[4], 0, 1), sep = "_")
    })

    schroeders$bottom.freq <- sapply(schroeders$sound.id, function(x) rep_schroeder$bottom.freq[rep_schroeder$sound.id ==
        x])

    schroeders$top.freq <- sapply(schroeders$sound.id, function(x) rep_schroeder$top.freq[rep_schroeder$sound.id ==
        x])

    print(schroeders$target.snr[1])
    dtw_dist <- waveform_similarity(X = schroeders, parallel = 22,
        output = "rectangular", sim.method = "DTW", type = "sliding",
        wl = 32)

    cor_dist <- waveform_similarity(X = schroeders, parallel = 22,
        output = "rectangular", sim.method = "correlation", type = "sliding",
        wl = 32)

    dtw_dist$cor <- cor_dist$similarity

    names(dtw_dist) <- c("schr1", "schr2", "dtw", "cor")

    dtw_dist$target.snr <- schroeders$target.snr[1]

    return(dtw_dist)
})

beepr::beep(2)

saveRDS(dist_white_noise, "./data/processed/waveform_similarity_adjusted_snr_single_schroeder_white_noise.RDS")

3.1.3.3 Statistical analysis

Modeling: - Multiple Regression on distance Matrices - Model:
\[\begin{align*} Dissimilarity &\sim frequency + components + phase \end{align*}\] - Response values scaled to make effect sizes comparable across models - Predictors were coded as pairwise binary matrices in which 0 means that calls in a dyad belong to the same level and 1 means calls belong to different levels - One model for each environment treatment as well as on the original model sounds

3.1.3.3.1 On repeated Schroeders
3.1.3.3.1.1 DTW
Code
dtw_dists_snr_list <- readRDS("./data/processed/waveform_similarity_adjusted_snr_200ms_schroeders_white_noise_v2.RDS")

adj_snr_schroeder1 <- readRDS("./data/processed/200ms_schroeders_adjusted_snr_white_noise.RDS")[[1]]

adj_snr_schroeder1$sound.id <- gsub("f0:f0 ", "f0:", adj_snr_schroeder1$sound.id)


dtw_wv_mod_list <- warbleR:::pblapply_wrblr_int(dtw_dists_snr_list,
    cl = 20, function(x) {

        # scale between 0 and 1
        x$dtw <- (x$dtw - min(x$dtw))/(max(x$dtw) - min(x$dtw))
        target_snr <- x$target.snr
        x$target.snr <- NULL

        x$schr1 <- sapply(x$schr1, function(x) adj_snr_schroeder1$sound.id[adj_snr_schroeder1$sound.files ==
            gsub("-1$", "", x)])
        x$schr2 <- sapply(x$schr2, function(x) adj_snr_schroeder1$sound.id[adj_snr_schroeder1$sound.files ==
            gsub("-1$", "", x)])

        dtw_tri <- PhenotypeSpace::rectangular_to_triangular(x[, c("schr1",
            "schr2", "dtw")])

        freq_bi_tri <- as.dist(binary_triangular_matrix(group = sapply(strsplit(rownames(dtw_tri),
            "_"), "[[", 1)))

        comp_bi_tri <- as.dist(binary_triangular_matrix(group = gsub("_n|_p",
            "", sapply(strsplit(rownames(dtw_tri), "_"), "[[", 2))))

        sign_bi_tri <- as.dist(binary_triangular_matrix(group = sapply(strsplit(rownames(dtw_tri),
            "_"), "[[", 3)))

        rect_var <- cbind(as.dist(dtw_tri), freq_bi_tri, comp_bi_tri,
            sign_bi_tri)

        colnames(rect_var) <- c("dtw_dist", "frequency", "components",
            "phase")

        dtw_wv_mod <- MRM2(formula = dtw_dist ~ frequency + components +
            phase, nperm = 10000, mat = rect_var)

        return(dtw_wv_mod)
    })

names(dtw_wv_mod_list) <- paste0("snr:", sapply(dtw_dists_snr_list,
    function(x) x$target.snr[1]))

saveRDS(dtw_wv_mod_list, "./data/processed/matrix_regression_dtw_distance_varying_snr_white_noise_32wl.RDS")
3.1.3.3.1.2 Correlation
Code
dtw_dists_snr_list <- readRDS("./data/processed/waveform_similarity_adjusted_snr_200ms_schroeders_white_noise.RDS")

adj_snr_schroeder1 <- readRDS("./data/processed/200ms_schroeders_adjusted_snr_white_noise.RDS")[[1]]

adj_snr_schroeder1$sound.id <- gsub("f0:f0 ", "f0:", adj_snr_schroeder1$sound.id)


cor_wv_mod_list <- warbleR:::pblapply_wrblr_int(dtw_dists_snr_list,
    cl = 20, function(x) {

        # convert to distance
        x$cor <- 1 - x$cor

        # scale between 0 and 1
        x$cor <- (x$cor - min(x$cor))/(max(x$cor) - min(x$cor))
        target_snr <- x$target.snr
        x$target.snr <- NULL

        x$schr1 <- sapply(x$schr1, function(x) adj_snr_schroeder1$sound.id[adj_snr_schroeder1$sound.files ==
            gsub("-1$", "", x)])
        x$schr2 <- sapply(x$schr2, function(x) adj_snr_schroeder1$sound.id[adj_snr_schroeder1$sound.files ==
            gsub("-1$", "", x)])

        cor_tri <- PhenotypeSpace::rectangular_to_triangular(x[, c("schr1",
            "schr2", "cor")])

        freq_bi_tri <- as.dist(binary_triangular_matrix(group = sapply(strsplit(rownames(cor_tri),
            "_"), "[[", 1)))

        comp_bi_tri <- as.dist(binary_triangular_matrix(group = gsub("_n|_p",
            "", sapply(strsplit(rownames(cor_tri), "_"), "[[", 2))))

        sign_bi_tri <- as.dist(binary_triangular_matrix(group = sapply(strsplit(rownames(cor_tri),
            "_"), "[[", 3)))

        rect_var <- cbind(as.dist(cor_tri), freq_bi_tri, comp_bi_tri,
            sign_bi_tri)

        colnames(rect_var) <- c("cor_dist", "frequency", "components",
            "phase")

        cor_wv_mod <- MRM2(formula = cor_dist ~ frequency + components +
            phase, nperm = 10000, mat = rect_var)

        return(cor_wv_mod)
    })

names(cor_wv_mod_list) <- paste0("snr:", sapply(dtw_dists_snr_list,
    function(x) x$target.snr[1]))

saveRDS(cor_wv_mod_list, "./data/processed/matrix_regression_correlation_varying_snr_white_noise.RDS")
3.1.3.3.2 On single Schroeders
3.1.3.3.2.1 DTW
Code
sim_dists_snr_list <- readRDS("./data/processed/waveform_similarity_adjusted_snr_single_schroeder_white_noise.RDS")

adj_snr_schroeder1 <- readRDS("./data/processed/200ms_schroeders_adjusted_snr_white_noise.RDS")[[1]]

adj_snr_schroeder1$sound.id <- gsub("f0:f0 ", "f0:", adj_snr_schroeder1$sound.id)


dtw_wv_mod_list <- warbleR:::pblapply_wrblr_int(sim_dists_snr_list,
    cl = 20, function(x) {

        # scale between 0 and 1
        x$dtw <- (x$dtw - min(x$dtw))/(max(x$dtw) - min(x$dtw))
        target_snr <- x$target.snr
        x$target.snr <- NULL

        x$schr1 <- substr(sapply(strsplit(x$schr1, ".wav"), "[[",
            1), start = 5, stop = 1000)
        x$schr2 <- substr(sapply(strsplit(x$schr2, ".wav"), "[[",
            1), start = 5, stop = 1000)

        dtw_tri <- PhenotypeSpace::rectangular_to_triangular(x[, c("schr1",
            "schr2", "dtw")])

        freq_bi_tri <- as.dist(binary_triangular_matrix(group = sapply(strsplit(rownames(dtw_tri),
            "_"), "[[", 1)))

        comp_bi_tri <- as.dist(binary_triangular_matrix(group = gsub("_n|_p",
            "", sapply(strsplit(rownames(dtw_tri), "_"), "[[", 2))))

        sign_bi_tri <- as.dist(binary_triangular_matrix(group = sapply(strsplit(rownames(dtw_tri),
            "_"), "[[", 3)))

        rect_var <- cbind(as.dist(dtw_tri), freq_bi_tri, comp_bi_tri,
            sign_bi_tri)

        colnames(rect_var) <- c("dtw_dist", "frequency", "components",
            "phase")

        dtw_wv_mod <- MRM2(formula = dtw_dist ~ frequency + components +
            phase, nperm = 10000, mat = rect_var)

        return(dtw_wv_mod)
    })

names(dtw_wv_mod_list) <- paste0("snr:", sapply(sim_dists_snr_list,
    function(x) x$target.snr[1]))

saveRDS(dtw_wv_mod_list, "./data/processed/matrix_regression_dtw_distance_varying_snr_white_noise_single_shroeder_32wl.RDS")
3.1.3.3.2.2 Correlation
Code
cor_snr_list <- readRDS("./data/processed/waveform_similarity_adjusted_snr_single_schroeder_white_noise.RDS")

adj_snr_schroeder1 <- readRDS("./data/processed/200ms_schroeders_adjusted_snr_white_noise.RDS")[[1]]

adj_snr_schroeder1$sound.id <- gsub("f0:f0 ", "f0:", adj_snr_schroeder1$sound.id)


cor_wv_mod_list <- warbleR:::pblapply_wrblr_int(cor_snr_list, cl = 20,
    function(x) {

        # convert to distance
        x$cor <- 1 - x$cor

        # scale between 0 and 1
        x$cor <- (x$cor - min(x$cor))/(max(x$cor) - min(x$cor))
        target_snr <- x$target.snr
        x$target.snr <- NULL

        x$schr1 <- substr(sapply(strsplit(x$schr1, ".wav"), "[[",
            1), start = 5, stop = 1000)
        x$schr2 <- substr(sapply(strsplit(x$schr2, ".wav"), "[[",
            1), start = 5, stop = 1000)

        cor_tri <- PhenotypeSpace::rectangular_to_triangular(x[, c("schr1",
            "schr2", "cor")])

        freq_bi_tri <- as.dist(binary_triangular_matrix(group = sapply(strsplit(rownames(cor_tri),
            "_"), "[[", 1)))

        comp_bi_tri <- as.dist(binary_triangular_matrix(group = gsub("_n|_p",
            "", sapply(strsplit(rownames(cor_tri), "_"), "[[", 2))))

        sign_bi_tri <- as.dist(binary_triangular_matrix(group = sapply(strsplit(rownames(cor_tri),
            "_"), "[[", 3)))

        rect_var <- cbind(as.dist(cor_tri), freq_bi_tri, comp_bi_tri,
            sign_bi_tri)

        colnames(rect_var) <- c("cor_dist", "frequency", "components",
            "phase")

        cor_wv_mod <- MRM2(formula = cor_dist ~ frequency + components +
            phase, nperm = 10000, mat = rect_var)

        return(cor_wv_mod)
    })

names(cor_wv_mod_list) <- paste0("snr:", sapply(cor_snr_list, function(x) x$target.snr[1]))

saveRDS(cor_wv_mod_list, "./data/processed/matrix_regression_correlation_varying_snr_white_noise_single_shroeder.RDS")

3.2 Results

3.2.1 Read model fits

Code
mod_list <- c(dtw_rep = "matrix_regression_dtw_distance_varying_snr_white_noise_32wl.RDS",
    cor_rep = "matrix_regression_correlation_varying_snr_white_noise.RDS",
    dtw_single = "matrix_regression_dtw_distance_varying_snr_white_noise_single_shroeder_32wl.RDS",
    cor_single = "matrix_regression_correlation_varying_snr_white_noise_single_shroeder.RDS")


out <- lapply(names(mod_list), function(y) {
    # print(y)
    x <- mod_list[[y]]

    # repeat Schroeder DTW models
    mod <- readRDS(file.path("./data/processed/", x))

    estimates <- do.call(rbind, lapply(seq_along(mod), function(x) {
        Y <- data.frame(mod[[x]]$coef[-1, ])
        Y$predictor <- rownames(Y)
        Y$model <- as.numeric(gsub("snr:", "", names(mod)[x]))
        names(Y) <- c("coef", "p", "predictor", "model")
        return(Y)
    }))

    estimates$model_f <- factor(estimates$model, levels = 1:30)
    estimates$type <- y

    return(estimates)
})

estimates <- do.call(rbind, out)

estimates$meth <- ifelse(grepl("dtw", estimates$type), "waveform DTW",
    "waveform correlation")
estimates$type <- ifelse(grepl("single", estimates$type), "single cycle",
    "repeated cycle")

ggplot(estimates[estimates$predictor == "phase", ], aes(y = coef,
    x = model, color = meth, lty = type)) + geom_line(linewidth = 1.6) +
    # geom_smooth() +
labs(x = "Signal-to-noise ratio (dB)", y = "Effect size", color = "Metric",
    lty = "Input data") + scale_color_viridis_d(alpha = 0.7, begin = 0.2,
    end = 0.8, option = "G") + theme_classic()

4 Takeaways

 

Session information

R version 4.5.0 (2025-04-11)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.10.0 
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.10.0  LAPACK version 3.10.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=es_CR.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=es_CR.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=es_CR.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=es_CR.UTF-8 LC_IDENTIFICATION=C       

time zone: America/Costa_Rica
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] numform_0.7.0        ecodist_2.1.3        PhenotypeSpace_0.1.0
 [4] ggplot2_3.5.2        baRulho_2.1.5        ohun_1.0.2          
 [7] Rraven_1.0.14        viridis_0.6.5        viridisLite_0.4.2   
[10] warbleR_1.1.35       NatureSounds_1.0.5   tuneR_1.4.7         
[13] seewave_2.2.3        formatR_1.14         knitr_1.50          
[16] kableExtra_1.4.0    

loaded via a namespace (and not attached):
 [1] DBI_1.2.3              bitops_1.0-9           deldir_2.0-4          
 [4] pbapply_1.7-2          gridExtra_2.3          remotes_2.5.0         
 [7] permute_0.9-7          testthat_3.2.3         rlang_1.1.6           
[10] magrittr_2.0.3         e1071_1.7-16           compiler_4.5.0        
[13] spatstat.geom_3.3-6    mgcv_1.9-3             png_0.1-8             
[16] systemfonts_1.2.3      vctrs_0.6.5            Sim.DiffProc_4.9      
[19] stringr_1.5.1          pkgconfig_2.0.3        crayon_1.5.3          
[22] fastmap_1.2.0          backports_1.5.0        labeling_0.4.3        
[25] rmarkdown_2.29         xfun_0.52              jsonlite_2.0.0        
[28] goftest_1.2-3          spatstat.utils_3.1-4   terra_1.8-42          
[31] Deriv_4.1.6            parallel_4.5.0         cluster_2.1.8.1       
[34] R6_2.6.1               stringi_1.8.7          RColorBrewer_1.1-3    
[37] spatstat.data_3.1-6    spatstat.univar_3.1-2  brio_1.1.5            
[40] Rcpp_1.0.14            tensor_1.5             Matrix_1.7-3          
[43] splines_4.5.0          igraph_2.1.4           tidyselect_1.2.1      
[46] rstudioapi_0.17.1      abind_1.4-8            yaml_2.3.10           
[49] vegan_2.6-10           codetools_0.2-20       dtw_1.23-1            
[52] spatstat.random_3.4-1  spatstat.explore_3.4-2 curl_6.3.0            
[55] lattice_0.22-7         tibble_3.3.0           withr_3.0.2           
[58] evaluate_1.0.3         signal_1.8-1           sf_1.0-20             
[61] sketchy_1.0.5          units_0.8-7            proxy_0.4-27          
[64] polyclip_1.10-7        xml2_1.3.8             pillar_1.10.2         
[67] packrat_0.9.2          KernSmooth_2.23-26     checkmate_2.3.2       
[70] generics_0.1.4         sp_2.2-0               RCurl_1.98-1.17       
[73] scales_1.4.0           class_7.3-23           glue_1.8.0            
[76] tools_4.5.0            xaringanExtra_0.8.0    grid_4.5.0            
[79] nlme_3.1-168           raster_3.6-32          cli_3.6.5             
[82] spatstat.sparse_3.1-0  svglite_2.1.3          dplyr_1.1.4           
[85] gtable_0.3.6           fftw_1.0-9             digest_0.6.37         
[88] classInt_0.4-11        rjson_0.2.23           htmlwidgets_1.6.4     
[91] farver_2.1.2           htmltools_0.5.8.1      lifecycle_1.0.4       
[94] httr_1.4.7             MASS_7.3-65