From 5b5892c79b079c88702f9a67afa841ac357fde80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Wed, 11 Sep 2024 08:54:47 +0200 Subject: [PATCH 01/17] adds create_model_map(), a function to list python model constructors --- R/model_map.R | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 R/model_map.R diff --git a/R/model_map.R b/R/model_map.R new file mode 100644 index 0000000..0120074 --- /dev/null +++ b/R/model_map.R @@ -0,0 +1,25 @@ +#' Create a model map based on version and base Python module +#' +#' This function returns a list of model constructors for a specific version and base Python module. +#' +#' @param version Character. The version of the model (e.g., "v2.4"). +#' @param base_module Character. The base Python module path as a string (e.g., "py_birdnet_models"). +#' +#' @keywords internal +#' @return A list of model constructors represented as strings, specific to the version and base module. +#' @examplesIf interactive() +#' py_birdnet_models <- reticulate::import("birdnet.models") +#' model_map <- create_model_map("v2.4", "py_birdnet_models") +create_model_map <- function(version, base_module) { + switch( + version, + "v2.4" = list( + "tflite_v2.4" = paste0(base_module, "$v2m4$AudioModelV2M4TFLite"), + "protobuf_v2.4" = paste0(base_module, "$v2m4$AudioModelV2M4Protobuf"), + "custom_v2.4" = paste0(base_module, "$v2m4$CustomAudioModelV2M4TFLite"), + "raven_v2.4" = paste0(base_module, "$v2m4$CustomAudioModelV2M4Raven"), + "meta_v2.4" = paste0(base_module, "$v2m4$MetaModelV2M4TFLite") + ), + stop("Unsupported version") + ) +} From 40bad7eee78dc8acea11070794ce8eaf9d59bf8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Wed, 11 Sep 2024 10:47:10 +0200 Subject: [PATCH 02/17] generalize model map to `module_map` that can list a bunch of different module paths. Adds functions to get and evaluate those python modules --- R/model_map.R | 25 ------------- R/module_map.R | 100 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 25 deletions(-) delete mode 100644 R/model_map.R create mode 100644 R/module_map.R diff --git a/R/model_map.R b/R/model_map.R deleted file mode 100644 index 0120074..0000000 --- a/R/model_map.R +++ /dev/null @@ -1,25 +0,0 @@ -#' Create a model map based on version and base Python module -#' -#' This function returns a list of model constructors for a specific version and base Python module. -#' -#' @param version Character. The version of the model (e.g., "v2.4"). -#' @param base_module Character. The base Python module path as a string (e.g., "py_birdnet_models"). -#' -#' @keywords internal -#' @return A list of model constructors represented as strings, specific to the version and base module. -#' @examplesIf interactive() -#' py_birdnet_models <- reticulate::import("birdnet.models") -#' model_map <- create_model_map("v2.4", "py_birdnet_models") -create_model_map <- function(version, base_module) { - switch( - version, - "v2.4" = list( - "tflite_v2.4" = paste0(base_module, "$v2m4$AudioModelV2M4TFLite"), - "protobuf_v2.4" = paste0(base_module, "$v2m4$AudioModelV2M4Protobuf"), - "custom_v2.4" = paste0(base_module, "$v2m4$CustomAudioModelV2M4TFLite"), - "raven_v2.4" = paste0(base_module, "$v2m4$CustomAudioModelV2M4Raven"), - "meta_v2.4" = paste0(base_module, "$v2m4$MetaModelV2M4TFLite") - ), - stop("Unsupported version") - ) -} diff --git a/R/module_map.R b/R/module_map.R new file mode 100644 index 0000000..7255c55 --- /dev/null +++ b/R/module_map.R @@ -0,0 +1,100 @@ +#' Create a module map based on version and base Python module +#' +#' This function returns a list of model constructors and miscellaneous paths for a specific version and base Python module. +#' +#' @param version Character. The version of the module (e.g., "v2.4"). +#' @param base_module Character. The base Python module path as a string (e.g., "py_birdnet_models"). +#' +#' @keywords internal +#' @return A list containing 'models' (a list of model constructors) and 'misc' (a list of miscellaneous paths), specific to the version and base module. +#' @examplesIf interactive() +#' py_birdnet_models <- reticulate::import("birdnet.models") +#' module_map <- create_module_map("v2.4", "py_birdnet_models") +create_module_map <- function(version, base_module) { + switch(version, + "v2.4" = list( + "models" = list( + "tflite_v2.4" = paste0(base_module, "$v2m4$AudioModelV2M4TFLite"), + "protobuf_v2.4" = paste0(base_module, "$v2m4$AudioModelV2M4Protobuf"), + "custom_v2.4" = paste0(base_module, "$v2m4$CustomAudioModelV2M4TFLite"), + "raven_v2.4" = paste0(base_module, "$v2m4$CustomAudioModelV2M4Raven"), + "meta_v2.4" = paste0(base_module, "$v2m4$MetaModelV2M4TFLite") + ), + "misc" = list( + "available_languages_v2.4" = paste0(base_module, "$v2m4$model_v2m4_base$AVAILABLE_LANGUAGES") + ) + ), + stop("Unsupported version") + ) +} + + +#' Get a model constructor from the module map +#' +#' This function extracts the model constructor from the module map. +#' +#' @param module_map A list returned from \code{create_module_map()}. +#' @param model_name Character. The name of the model to retrieve (e.g., "tflite_v2.4"). +#' +#' @return A string representing the Python path to the model constructor. +#' @keywords internal +#' @examplesIf interactive() +#' module_map <- create_module_map("v2.4", "py_birdnet_models") +#' tflite_model_path <- get_model_from_module_map(module_map, "tflite_v2.4") +get_model_from_module_map <- function(module_map, model_name) { + models <- module_map$models + + if (!model_name %in% names(models)) { + stop("Invalid model name. Available models are: ", paste(names(models), collapse = ", ")) + } + + return(models[[model_name]]) +} + + + +#' Get miscellaneous information from the module map +#' +#' This function extracts miscellaneous information (e.g., available languages) from the module map. +#' +#' @param module_map A list returned from \code{create_module_map()}. +#' @param misc_name Character. The name of the miscellaneous information to retrieve (e.g., "available_languages_v2.4"). +#' +#' @return A string representing the Python path to the miscellaneous information. +#' @keywords internal +#' @examplesIf interactive() +#' module_map <- create_module_map("v2.4", "py_birdnet_models") +#' available_languages_path <- get_misc_from_module_map(module_map, "available_languages_v2.4") +get_misc_from_module_map <- function(module_map, misc_name) { + misc <- module_map$misc + + if (!misc_name %in% names(misc)) { + stop("Invalid misc name. Available misc items are: ", paste(names(misc), collapse = ", ")) + } + + return(misc[[misc_name]]) +} + + +#' Evaluate a Python path string and return the corresponding Python object +#' +#' This function takes a string representing a Python path (e.g., from \code{get_model_from_module_map()}) +#' and evaluates it to return the corresponding Python object. +#' +#' @param path_string Character. The string representing the Python path (e.g., "py_birdnet_models$v2m4$AudioModelV2M4TFLite"). +#' +#' @return The evaluated Python object or value. +#' @keywords internal +#' @examplesIf interactive() +#' py_birdnet_models <- reticulate::import("birdnet.models") +#' module_map <- create_module_map("v2.4", "py_birdnet_models") +#' model_string <- get_model_from_module_map(module_map, "tflite_v2.4") +#' model_object <- evaluate_python_path(model_string) +evaluate_python_path <- function(path_string) { + tryCatch( + eval(parse(text = path_string)), + error = function(e) { + stop("Failed to evaluate Python path: ", conditionMessage(e)) + } + ) +} From 826dfa26673dfea53b44a11474aa8b2060b6326c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Wed, 11 Sep 2024 10:47:36 +0200 Subject: [PATCH 03/17] adds test for `module_map` --- tests/testthat/test-module_map.R | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/testthat/test-module_map.R diff --git a/tests/testthat/test-module_map.R b/tests/testthat/test-module_map.R new file mode 100644 index 0000000..0d3e9d8 --- /dev/null +++ b/tests/testthat/test-module_map.R @@ -0,0 +1,24 @@ +library(birdnetR) +library(testthat) + +test_that("create_module_map", { + module_map <- create_module_map("v2.4", "py_birdnet_models") + expect_type(module_map, "list") + expect_named(module_map, c("models", "misc")) + expect_named(module_map$models, c("tflite_v2.4", "protobuf_v2.4", "custom_v2.4", "raven_v2.4", "meta_v2.4")) + expect_named(module_map$misc, "available_languages_v2.4") +}) + + +test_that("get_model", { + module_map <- create_module_map("v2.4", "py_birdnet_models") + tflite_model_path <- get_model_from_module_map(module_map, "tflite_v2.4") + expect_type(tflite_model_path, "character") +}) + + +test_that("get_misc", { + module_map <- create_module_map("v2.4", "py_birdnet_models") + available_languages_path <- get_misc_from_module_map(module_map, "available_languages_v2.4") + expect_type(available_languages_path, "character") +}) From c6dcc063a68f967a9f97cd6806ae8c58c97e915d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Thu, 12 Sep 2024 16:13:01 +0200 Subject: [PATCH 04/17] update module map und add a basic `get_element_from_module_map` function --- R/module_map.R | 69 +++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/R/module_map.R b/R/module_map.R index 7255c55..c03bef5 100644 --- a/R/module_map.R +++ b/R/module_map.R @@ -14,65 +14,54 @@ create_module_map <- function(version, base_module) { switch(version, "v2.4" = list( "models" = list( - "tflite_v2.4" = paste0(base_module, "$v2m4$AudioModelV2M4TFLite"), - "protobuf_v2.4" = paste0(base_module, "$v2m4$AudioModelV2M4Protobuf"), - "custom_v2.4" = paste0(base_module, "$v2m4$CustomAudioModelV2M4TFLite"), - "raven_v2.4" = paste0(base_module, "$v2m4$CustomAudioModelV2M4Raven"), - "meta_v2.4" = paste0(base_module, "$v2m4$MetaModelV2M4TFLite") + "tflite" = paste0(base_module, "$v2m4$AudioModelV2M4TFLite"), + "protobuf" = paste0(base_module, "$v2m4$AudioModelV2M4Protobuf"), + "custom" = paste0(base_module, "$v2m4$CustomAudioModelV2M4TFLite"), + "raven" = paste0(base_module, "$v2m4$CustomAudioModelV2M4Raven"), + "meta" = paste0(base_module, "$v2m4$MetaModelV2M4TFLite") ), "misc" = list( - "available_languages_v2.4" = paste0(base_module, "$v2m4$model_v2m4_base$AVAILABLE_LANGUAGES") + "available_languages" = paste0(base_module, "$v2m4$model_v2m4_base$AVAILABLE_LANGUAGES"), + "version_app_data_folder" = paste0(base_module, "$v2m4$model_v2m4_base$get_internal_version_app_data_folder"), + "downloader_tflite" = paste0(base_module, "$v2m4$model_v2m4_tflite$DownloaderTFLite"), + "downloader_protobuf" = paste0(base_module, "$v2m4$model_v2m4_protobuf$DownloaderProtobuf"), + "parser_custom_tflite" = paste0(base_module, "$v2m4$model_v2m4_tflite_custom$CustomTFLiteParser") ) ), stop("Unsupported version") ) } - -#' Get a model constructor from the module map +#' Get an element from a module map regardless of nesting level #' -#' This function extracts the model constructor from the module map. +#' This function retrieves an element from a module map by traversing the nested structure. +#' It takes a variable number of arguments that represent the keys to navigate through the module map. #' #' @param module_map A list returned from \code{create_module_map()}. -#' @param model_name Character. The name of the model to retrieve (e.g., "tflite_v2.4"). +#' @param ... A sequence of keys that represent the path to the desired element in the module map. #' -#' @return A string representing the Python path to the model constructor. +#' @return The element located at the specified path within the module map. #' @keywords internal #' @examplesIf interactive() #' module_map <- create_module_map("v2.4", "py_birdnet_models") -#' tflite_model_path <- get_model_from_module_map(module_map, "tflite_v2.4") -get_model_from_module_map <- function(module_map, model_name) { - models <- module_map$models - - if (!model_name %in% names(models)) { - stop("Invalid model name. Available models are: ", paste(names(models), collapse = ", ")) - } - - return(models[[model_name]]) -} - +#' available_languages_path <- get_element_from_module_map(module_map, "misc", "available_languages") +get_element_from_module_map <- function(module_map, ...) { + # Extract the nested keys + keys <- list(...) + # Start from the top-level module map + element <- module_map -#' Get miscellaneous information from the module map -#' -#' This function extracts miscellaneous information (e.g., available languages) from the module map. -#' -#' @param module_map A list returned from \code{create_module_map()}. -#' @param misc_name Character. The name of the miscellaneous information to retrieve (e.g., "available_languages_v2.4"). -#' -#' @return A string representing the Python path to the miscellaneous information. -#' @keywords internal -#' @examplesIf interactive() -#' module_map <- create_module_map("v2.4", "py_birdnet_models") -#' available_languages_path <- get_misc_from_module_map(module_map, "available_languages_v2.4") -get_misc_from_module_map <- function(module_map, misc_name) { - misc <- module_map$misc - - if (!misc_name %in% names(misc)) { - stop("Invalid misc name. Available misc items are: ", paste(names(misc), collapse = ", ")) + # Traverse the nested structure using the provided keys + for (key in keys) { + if (!is.null(element[[key]])) { + element <- element[[key]] + } else { + stop(paste("Element", key, "not found in the module map")) + } } - return(misc[[misc_name]]) + return(element) } From 2cf541d8b3c8bb0825c960e2a843fb87e55a8fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Thu, 12 Sep 2024 16:15:56 +0200 Subject: [PATCH 05/17] support multiple models using the S3 system --- R/birdnet_interface.R | 512 +++++++++++++++++++++++++++++++++--------- 1 file changed, 407 insertions(+), 105 deletions(-) diff --git a/R/birdnet_interface.R b/R/birdnet_interface.R index db93ed2..2374be3 100644 --- a/R/birdnet_interface.R +++ b/R/birdnet_interface.R @@ -10,18 +10,20 @@ py_pathlib <- NULL py_builtins <- NULL -#' Check the Installed BirdNET Version +#' Check the Installed birdnet Version #' -#' This internal function checks if BirdNET Python is installed and if the version matches the required version. +#' This internal function checks if birdnet Python is installed and if the version matches the requirement. #' If it is not available or if the versions do not match, issue a warning with instructions to update the package. #' #' @keywords internal #' @return None. This function is called for its side effect of stopping execution if the wrong version is installed. .check_birdnet_version <- function() { - - available_py_packages <- tryCatch({ - reticulate::py_list_packages() - }, error = function() NULL) + available_py_packages <- tryCatch( + { + reticulate::py_list_packages() + }, + error = function() NULL + ) if (is.null(available_py_packages)) { message("No Python environment available. To install, use `install_birdnet()`.") @@ -30,6 +32,8 @@ py_builtins <- NULL installed_birdnet_version <- tryCatch( { + # we need to set `package` to NULL, to bin it to a variable. Otherwise R CMD check will throw a note "No visible binding for global variable 'package' " + package <- NULL subset(available_py_packages, package == "birdnet")$version }, error = function(e) NULL @@ -52,8 +56,6 @@ py_builtins <- NULL } - - #' Initialize birdnetR Package #' #' Sets up the Python environment and imports required modules when the birdnetR package is loaded. @@ -75,85 +77,352 @@ py_builtins <- NULL py_birdnet_location_based_prediction <<- reticulate::import("birdnet.location_based_prediction", delay_load = TRUE) py_birdnet_types <<- reticulate::import("birdnet.types", delay_load = TRUE) py_pathlib <<- reticulate::import("pathlib", delay_load = TRUE) - py_builtins <<- import_builtins(delay_load = TRUE) + py_builtins <<- reticulate::import_builtins(delay_load = TRUE) } +#' Create a new BirdNET model object +#' +#' This function creates a new BirdNET model object by wrapping a Python model object and assigning +#' it a class and optional subclass. The model is created as an R object that can be interacted with +#' using R's S3 method dispatch. +#' +#' @param x A Python object representing the BirdNET model. This is typically a Python model +#' object created using the `reticulate` package. +#' @param ... Additional attributes to attach to the BirdNET model object. +#' @param subclass Character. An optional subclass name for the BirdNET model (e.g., "tflite_v2.4"). +#' The subclass is combined with the base class `birdnet_model`. +#' +#' @return An S3 object of class `birdnet_model` (and any specified subclass) containing the Python model object +#' and any additional attributes passed in `...`. +#' +#' @keywords internal +#' @examplesIf interactive() +#' py_birdnet_models <- reticulate::import("birdnet.models") +#' tflite_model <- py_birdnet_models$v2m4$AudioModelV2M4TFLite() +#' birdnet_model <- new_birdnet_model(tflite_model, language = "en_us", version = "v2.4") +new_birdnet_model <- function(x, ..., subclass = character()) { + stopifnot(reticulate::is_py_object(x)) # Ensure that the input is a valid Python object + + class_name <- "birdnet_model" # Base class name for all BirdNET models + subclasse <- paste(class_name, subclass, sep = "_") # Create subclass by combining base class with user-provided subclass + + # Return an S3 object containing the Python model and additional attributes, with the specified class hierarchy + structure( + list("py_model" = x, ...), + class = c(subclasse, "birdnet_model") + ) +} + -#' Get Available Languages for BirdNET Model +#' Dynamically create a BirdNET model #' -#' Retrieve the available languages supported by the BirdNET model. +#' This function dynamically creates a BirdNET model based on the provided model name and version. It retrieves +#' the appropriate Python model constructor from the module map, evaluates the constructor, and returns a wrapped +#' BirdNET model object. #' -#' @return A sorted character vector containing the available language codes. +#' @param model_name Character. The name of the model to create (e.g., "tflite", "protobuf"). +#' @param version Character. The version of the model (e.g., "v2.4"). +#' @param ... Additional arguments passed to the Python model constructor (e.g., `tflite_num_threads`, `language`). +#' +#' @return A BirdNET model object of class `birdnet_model` and its subclasses (e.g., "tflite_v2.4"). +#' @keywords internal +#' @examplesIf interactive() +#' py_birdnet_models <- reticulate::import("birdnet.models") +#' birdnet_model <- model_factory("tflite", "v2.4", tflite_num_threads = 2, language = "en_us") +model_factory <- function(model_name, version, ...) { + # Create module map using the specified version and base Python module + module_map <- create_module_map(version, "py_birdnet_models") + + # Retrieve the model module path from the module map + model_module <- get_element_from_module_map(module_map, "models", model_name) + + # Evaluate the Python model constructor dynamically + model_constructor <- evaluate_python_path(model_module) + + # Try to create the Python model by passing additional arguments + py_model <- tryCatch( + model_constructor(...), + error = function(e) { + stop("Failed to initialize Python model: ", conditionMessage(e)) + } + ) + + # Create a subclass for the model: model_name_version is the specific subclass of model_name + subclasses <- c(version, model_name) + subclasses <- gsub(x = subclasses, pattern = "\\.", replacement = "_") + + # Create and return the BirdNET model object with the subclasses + # passing model_version adds a list element with the version of the model. + new_birdnet_model(py_model, model_version = version, ..., subclass = subclasses) +} + + +#' @title Initialize a BirdNET Model +#' +#' @description +#' +#' The various function of the `birdnet_model_*` family are used to create and initialize diffent BirdNET models. Models will be downloaded if necessary. +#' +#' * [birdnet_model_tflite()]: creates a tflite-model used for species prediction from audio. +#' * [birdnet_model_custom()]: loads a custom model for species prediction from audio. +#' * [birdnet_model_protobuf()]: creates a protobuf model for species prediction from audio that can be run on the GPU (not yet implemented). +#' * [birdnet_model_meta()]: creates a meta model for species prediction from location and time. +#' +#' +#' @details +#' **Species Prediction from audio** +#' +#' Models created from [birdnet_model_tflite()], [birdnet_model_custom()], and [birdnet_model_protobuf()] can be used to predict species within an audio file using [predict_species_from_audio_file()]. \cr +#' +#' **Species prediction from location and time** +#' +#' The [birdnet_model_meta()] model can be used to predict species occurrence at a specific location and time of the year using [predict_species_at_location_and_time()]. +#' +#' @param version character. The version of BirdNET to use (default is "v2.4", no other versions are currently supported). +#' @param language character. Specifies the language code to use for the model's text processing. The language must be one of the available languages supported by the BirdNET model. +#' @param tflite_num_threads integer. The number of threads to use for TensorFlow Lite operations. If NULL (default), the default threading behavior will be used. +#' Will be coerced to an integer if possible. +#' +#' @seealso [available_languages()] [predict_species_from_audio_file()] [predict_species_at_location_and_time()] +#' @return A BirdNET model object. #' @examples -#' available_languages() +#' # Create a TFLite BirdNET model with 2 threads and English (US) language +#' birdnet_model <- birdnet_model_tflite(version = "v2.4", language = "en_us", tflite_num_threads = 2) +#' @name birdnet_model_load +NULL +#> NULL + +#' @rdname birdnet_model_load #' @export -available_languages <- function() { - if (is.null(py_birdnet_models)) { - stop( - "The birdnet.models module has not been loaded. Ensure the Python environment is configured correctly." - ) +birdnet_model_tflite <- function(version = "v2.4", + language = "en_us", + tflite_num_threads = NULL) { + # Validate tflite_num_threads: must be NULL or numeric (will be coerced to integer) + if (!is.null(tflite_num_threads) && + !is.numeric(tflite_num_threads)) { + stop("tflite_num_threads must be a numeric value or NULL.") + } + + # Coerce to integer if tflite_num_threads is provided and numeric + tflite_num_threads <- if (!is.null(tflite_num_threads)) { + as.integer(tflite_num_threads) + } else { + NULL + } + + # Call the model factory to create and return the TFLite model + model_factory( + model_name = "tflite", + version = version, + tflite_num_threads = tflite_num_threads, + language = language + ) +} + +#' @rdname birdnet_model_load +#' @param classifier_folder character. Path to the folder containing the custom classifier. +#' @param classifier_name character. Name of the custom classifier. +#' @export +birdnet_model_custom <- function(version = "v2.4", + classifier_folder, + classifier_name, + tflite_num_threads = NULL) { + # Validate tflite_num_threads: must be NULL or numeric (will be coerced to integer) + if (!is.null(tflite_num_threads) && + !is.numeric(tflite_num_threads)) { + stop("tflite_num_threads must be a numeric value or NULL.") + } + + # Coerce to integer if tflite_num_threads is provided and numeric + tflite_num_threads <- if (!is.null(tflite_num_threads)) { + as.integer(tflite_num_threads) + } else { + NULL } - sort(py_builtins$list(py_birdnet_models$v2m4$model_v2m4_base$AVAILABLE_LANGUAGES)) + + # Call the model factory to create and return the Custom TFLite model + model <- model_factory( + model_name = "custom", + version = version, + py_pathlib$Path(classifier_folder), + classifier_name, + tflite_num_threads = tflite_num_threads + ) + + # Because classifier_folder and classifier_name need to be positional and cannot be named, we need to rename the + # list elements + names(model) <- c("py_model", "model_version", "classifier_folder", "classifier_name", "tflite_num_threads") + model +} + +#' @rdname birdnet_model_load +#' @export +birdnet_model_meta <- function(version = "v2.4", + language = "en_us", + tflite_num_threads = NULL) { + # Validate tflite_num_threads: must be NULL or numeric (will be coerced to integer) + if (!is.null(tflite_num_threads) && + !is.numeric(tflite_num_threads)) { + stop("tflite_num_threads must be a numeric value or NULL.") + } + + # Coerce to integer if tflite_num_threads is provided and numeric + tflite_num_threads <- if (!is.null(tflite_num_threads)) { + as.integer(tflite_num_threads) + } else { + NULL + } + + # Call the model factory to create and return the TFLite model + model_factory( + model_name = "meta", + version = version, + tflite_num_threads = tflite_num_threads, + language = language + ) +} + + +#' @rdname birdnet_model_load +#' @param custom_device character. This parameter allows specifying a custom device on which computations should be performed. If `custom_device` is not specified (i.e., it has the default value None), the program will attempt to use a GPU (e.g., "/device:GPU:0") by default. If no GPU is available, it will fall back to using the CPU. By specifying a device string such as "/device:GPU:0" or "/device:CPU:0", the user can explicitly choose the device on which operations should be executed. +#' @note Currently, all models can only be executed on the CPU. GPU support is not yet available. +#' @export +birdnet_model_protobuf <- function(version = "v2.4", + language = "en_us", + custom_device = NULL) { + # Call the model factory to create and return the Protobuf model + model_factory( + model_name = "protobuf", + version = version, + language = language, + custom_device = custom_device + ) } -#' Initialize the BirdNET Model +#' Initialize the BirdNET Model (Deprecated) #' -#' This function initializes the BirdNET model (v2.4). +#' This function initializes the BirdNET model (v2.4). It is kept for backward compatibility and is deprecated. +#' Use \code{birdnet_model_tflite()} instead for model initialization. #' #' @param tflite_num_threads integer. The number of threads to use for TensorFlow Lite operations. If NULL (default), the default threading behavior will be used. #' Will be coerced to an integer if possible. -#' @param language A character string specifying the language code to use for the model's text processing. The language must be one of the available languages supported by the BirdNET model. +#' @param language Character string specifying the language code to use for the model's text processing. The language must be one of the available languages supported by the BirdNET model. #' @note The `language` parameter must be one of the available languages returned by `available_languages()`. #' @seealso [available_languages()] #' @return An instance of the BirdNET model. #' @export +#' @note This function is kept for backward compatibility. Please use \code{birdnet_model_tflite()} instead. init_model <- - function(tflite_num_threads = NULL, - language = "en_us") { - stopifnot(is.integer(tflite_num_threads) | - is.null(tflite_num_threads)) - # Other Value Errors (e.g. unsupported language) are handled by the python package - - # model <- - # py_birdnet_models$v2m4$AudioModelV2M4TFLite(tflite_num_threads = tflite_num_threads, language = language) - - model <- list( - audio_model = py_birdnet_models$v2m4$model_v2m4_tflite$AudioModelV2M4TFLite, - meta_model = py_birdnet_models$v2m4$model_v2m4_tflite$MetaModelV2M4TFLite, - tflite_num_threads = tflite_num_threads, - language = language - ) + function(tflite_num_threads = NULL, language = "en_us") { + # Deprecation warning + warning("`init_model()` is deprecated. Please use `birdnet_model_tflite()` instead.", call. = FALSE) - return(model) + # Call the updated model initialization function + birdnet_model_tflite(version = "v2.4", language = language, tflite_num_threads = tflite_num_threads) } -#' Get Path to BirdNET Labels File for a Specified Language +#' Get Available Languages for BirdNET Model +#' +#' Retrieve the available languages supported by a specific version of BirdNET. +#' +#' @param version character. The version of BirdNET to use (default is "v2.4", no other versions are currently supported). +#' +#' @return A sorted character vector containing the available language codes. +#' @examples +#' available_languages("v2.4") +#' @export +available_languages <- function(version) { + module_map <- create_module_map(version = version, "py_birdnet_models") + available_languages_path <- get_element_from_module_map(module_map, "misc", "available_languages") + py_object <- evaluate_python_path(available_languages_path) + sort(py_builtins$list(py_object)) +} + + +#' Get Path to a Labels File #' #' This function retrieves the file path to the BirdNET labels file on your system corresponding to a specified language. #' This file contains all class labels supported by the BirdNET model. #' -#' @param language A character string specifying the language code for which the labels path is requested. +#' +#' @param model A BirdNET model object. +#' @param language character. Specifies the language code for which the labels path is returned. #' The language must be one of the available languages supported by the BirdNET model. +#' @param ... Additional arguments passed to the method dispatch function. #' @return A character string representing the file path to the labels file for the specified language. -#' @examples -#' get_labels_path("en_us") +#' @examplesIf interactive() +#' model <- birdnet_model_tflite(version = "v2.4") +#' get_labels_path(model, "fr") #' @note The `language` parameter must be one of the available languages returned by `available_languages()`. -#' @seealso [available_languages()] +#' @seealso [available_languages()] [get_species_from_file()] #' @export -get_labels_path <- function(language) { - if (!(language %in% available_languages())) { - stop(paste( - "`language` must be one of", - paste(available_languages(), collapse = ", ") - )) +get_labels_path <- function(model, ...) { + UseMethod("get_labels_path") +} + +#' Helper function to retrieve the language path for a BirdNET model +#' +#' This function handles the common logic for retrieving the language path for a BirdNET model. +#' It validates the language, creates the necessary paths from the module map, and uses the appropriate +#' downloader to retrieve the path to the language file. +#' +#' @param model A BirdNET model object containing the version information. +#' @param language Character. The language code for which to retrieve the path (e.g., "en_us"). +#' Must be one of the available languages for the given model version. +#' @param downloader_key Character. The key in the module map that specifies the downloader +#' to use (e.g., "downloader_tflite", "downloader_protobuf"). +#' @param subfolder Character. The subfolder in which the language files are stored (e.g., "TFLite", "Protobuf"). +#' +#' @return A character string representing the path to the language file. +#' @keywords internal +#' @examplesIf interactive() +#' model <- birdnet_model_tflite(version = "v2.4", language = "en_us") +#' language_path <- get_language_path(model, "en_us", "downloader_tflite", "TFLite") +get_language_path <- function(model, language, downloader_key, subfolder) { + # Validate that the language is available for the given model version + langs <- available_languages(model$model_version) + + if (!(language %in% langs)) { + stop(paste("`language` must be one of", paste(langs, collapse = ", "))) } - birdnet_app_data <- py_pathlib$Path(py_birdnet_models$v2m4$model_v2m4_base$get_internal_version_app_data_folder(), "TFLite") - downloader <- py_birdnet_models$v2m4$model_v2m4_tflite$DownloaderTFLite(birdnet_app_data) - as.character(downloader$get_language_path(language)) + # Create module map and get the necessary paths + module_map <- create_module_map(version = model$model_version, "py_birdnet_models") + version_app_data_folder_path <- get_element_from_module_map(module_map, "misc", "version_app_data_folder") + downloader <- get_element_from_module_map(module_map, "misc", downloader_key) + + # Evaluate the Python paths + py_app_folder <- evaluate_python_path(version_app_data_folder_path) + py_downloader <- evaluate_python_path(downloader) + + # Call the downloader with the specific subfolder and return the language path + as.character(py_downloader(py_pathlib$Path(py_app_folder(), subfolder))$get_language_path(language)) +} + +#' @rdname get_labels_path +#' @description For a custom model, the path of the custom labels file is returned. +#' @export +#' @method get_labels_path birdnet_model_custom +get_labels_path.birdnet_model_custom <- function(model, ...) { + file.path(model$classifier_folder, paste0(model$classifier_name, ".txt")) +} + + +#' @rdname get_labels_path +#' @export +#' @method get_labels_path birdnet_model_tflite +get_labels_path.birdnet_model_tflite <- function(model, language, ...) { + get_language_path(model, language, "downloader_tflite", "TFLite") +} + +#' @rdname get_labels_path +#' @export +#' @method get_labels_path birdnet_model_protobuf +get_labels_path.birdnet_model_protobuf <- function(model, language, ...) { + get_language_path(model, language, "downloader_protobuf", "Protobuf") } @@ -172,7 +441,8 @@ get_labels_path <- function(language) { #' #' # To access all class labels that are supported in your language, #' # you can read in the respective label file -#' labels_path <- get_labels_path("fr") +#' model <- birdnet_model_tflite(version = "v2.4", language = "en_us") +#' labels_path <- get_labels_path(model, "fr") #' species_list <- get_species_from_file(labels_path) #' head(species_list) get_species_from_file <- function(species_file) { @@ -181,49 +451,77 @@ get_species_from_file <- function(species_file) { py_species_list$items } -#' Predict Species Within an Audio File + +#' Predict species within an audio file using a BirdNET model +#' +#' @description +#' Use a BirdNET model to predict species within an audio file. The model can be a TFLite model, a custom model, or a Protobuf model. #' -#' This function predicts species within an audio file using the BirdNET model. #' #' @details -#' Applying a sigmoid activation function, (`apply_sigmoid=True`) scales the unbound class output of the linear classifier ("logit score") to the range `0-1`. +#' Applying a sigmoid activation function (`apply_sigmoid=TRUE`) scales the unbound class output of the linear classifier ("logit score") to the range `0-1`. #' This confidence score is a unitless, numeric expression of BirdNET’s “confidence” in its prediction (but not the probability of species presence). -#' Sigmoid sensitivity < 1 leads to more higher and lower scoring predictions and a value > 1 leads to more intermediate-scoring predictions. +#' Sigmoid sensitivity < 1 leads to more higher and lower scoring predictions, and a value > 1 leads to more intermediate-scoring predictions. #' -#' For more information on BirdNET confidence scores, the sigmoid activation function and a suggested workflow on how to convert confidence scores to probabilities, see Wood & Kahl, 2024 +#' For more information on BirdNET confidence scores, the sigmoid activation function, and a suggested workflow on how to convert confidence scores to probabilities, see Wood & Kahl, 2024. #' #' @references Wood, C. M., & Kahl, S. (2024). Guidelines for appropriate use of BirdNET scores and other detector outputs. Journal of Ornithology. https://doi.org/10.1007/s10336-024-02144-5 #' -#' @param model BirdNETModel. An instance of the BirdNET model returned by [`init_model()`]. +#' @param model A BirdNET model object. An instance of the BirdNET model (e.g., `birdnet_model_tflite`, `birdnet_model_protobuf`). #' @param audio_file character. The path to the audio file. -#' @param min_confidence numeric. Minimum confidence threshold for predictions. -#' @param batch_size integer. Number of audio samples to process in a batch. -#' @param chunk_overlap_s numeric. Overlapping of chunks in seconds. Must be in the interval \[0.0, 3.0\). -#' @param use_bandpass logical. Whether to apply a bandpass filter. -#' @param bandpass_fmin,bandpass_fmax numeric. Minimum/Maximum frequency for the bandpass filter (in Hz). Ignored if `use_bandpass` is False. -#' @param apply_sigmoid logical. Whether to apply a sigmoid function to the model output. -#' @param sigmoid_sensitivity numeric. Sensitivity parameter for the sigmoid function. Must be in the interval 0.5 - 1.5. Ignored if `apply_sigmoid` is False. -#' @param filter_species NULL, a character vector of length greater than 0 or a list where each element is a single non-empty character string. Used to filter the predictions. If NULL, no filtering is applied. See [`get_species_from_file()`] for more details. -#' @param keep_empty logical. Whether to include empty intervals in the output. -#' @return A data frame with columns: `start`, `end`, `scientific_name`, `common_name`, and `confidence`. -#' Each row represents a single prediction. -#' @seealso [`init_model()`] [`get_species_from_file()`] +#' @param min_confidence numeric. Minimum confidence threshold for predictions (default is 0.1). +#' @param batch_size integer. Number of audio samples to process in a batch (default is 1L). +#' @param chunk_overlap_s numeric. The overlap between audio chunks in seconds (default is 0). Must be in the interval \[0.0, 3.0\]. +#' @param use_bandpass logical. Whether to apply a bandpass filter (default is TRUE). +#' @param bandpass_fmin,bandpass_fmax integer. Minimum and maximum frequencies for the bandpass filter (in Hz). Ignored if `use_bandpass` is FALSE (default is 0L to 15000L). +#' @param apply_sigmoid logical. Whether to apply a sigmoid function to the model output (default is TRUE). +#' @param sigmoid_sensitivity numeric. Sensitivity parameter for the sigmoid function (default is 1). Must be in the interval \[0.5, 1.5\]. Ignored if `apply_sigmoid` is FALSE. +#' @param filter_species NULL, a character vector of length greater than 0, or a list where each element is a single non-empty character string. Used to filter the predictions. If NULL (default), no filtering is applied. +#' @param keep_empty logical. Whether to include empty intervals in the output (default is TRUE). +#' +#' @return A data frame with columns: `start`, `end`, `scientific_name`, `common_name`, and `confidence`. Each row represents a single prediction. +#' +#' @seealso [`get_species_from_file()`] for more details on species filtering. +#' @export +#' @seealso [`predict_species_from_audio_file.birdnet_model`] +#' @examplesIf interactive() +#' library(birdnetR) +#' +#' model <- birdnet_model_tflite(version = "v2.4", language = "en_us") +#' predictions <- predict_species_from_audio_file(model, "path/to/audio.wav", min_confidence = 0.2) +predict_species_from_audio_file <- function(model, + audio_file, + min_confidence = 0.1, + batch_size = 1L, + chunk_overlap_s = 0, + use_bandpass = TRUE, + bandpass_fmin = 0L, + bandpass_fmax = 15000L, + apply_sigmoid = TRUE, + sigmoid_sensitivity = 1, + filter_species = NULL, + keep_empty = TRUE) { + UseMethod("predict_species_from_audio_file") +} + +#' @rdname predict_species_from_audio_file +#' @method predict_species_from_audio_file birdnet_model #' @export -predict_species <- function(model, - audio_file = system.file("extdata", "soundscape.wav", package = "birdnetR"), - min_confidence = 0.1, - batch_size = 1L, - chunk_overlap_s = 0, - use_bandpass = TRUE, - bandpass_fmin = 0L, - bandpass_fmax = 15000L, - apply_sigmoid = TRUE, - sigmoid_sensitivity = 1, - filter_species = NULL, - keep_empty = TRUE) { - # Check argument types. Done mostly in order to return better error messages +predict_species_from_audio_file.birdnet_model <- function( + model, + audio_file, + min_confidence = 0.1, + batch_size = 1L, + chunk_overlap_s = 0, + use_bandpass = TRUE, + bandpass_fmin = 0L, + bandpass_fmax = 15000L, + apply_sigmoid = TRUE, + sigmoid_sensitivity = 1, + filter_species = NULL, + keep_empty = TRUE) { + # Check argument types for better error messages stopifnot(is.list(model)) - stopifnot(inherits(model$audio_model(), "birdnet.models.v2m4.model_v2m4_base.AudioModelBaseV2M4")) stopifnot(is.character(audio_file)) stopifnot(is.numeric(min_confidence)) stopifnot(is.integer(batch_size)) @@ -233,25 +531,24 @@ predict_species <- function(model, stopifnot(is.logical(apply_sigmoid)) stopifnot(is.numeric(sigmoid_sensitivity)) stopifnot(is.logical(keep_empty)) + + # Handle species filter if (!is.null(filter_species)) { stopifnot( "`filter_species` must be NULL, a character vector of length greater than 0 or a list where each element is a single non-empty character string." = is_valid_species_list(filter_species) ) - # if not NULL, convert filter_species to a python set - # Wrap single character strings in a list if necessary, otherwise `set` splits the string into individual characters - if (is.character(filter_species) && - length(filter_species) == 1) { + if (is.character(filter_species) && length(filter_species) == 1) { filter_species <- list(filter_species) } filter_species <- py_builtins$set(filter_species) } - # Main function logic + # Convert path to a Python Path object audio_file <- py_pathlib$Path(audio_file)$expanduser()$resolve(TRUE) - audio_model = model$audio_model(tflite_num_threads = model$tflite_num_threads, language = model$language) + # Main function logic predictions_gen <- py_birdnet_audio_based_prediction$predict_species_within_audio_file( audio_file, min_confidence = min_confidence, @@ -263,12 +560,15 @@ predict_species <- function(model, apply_sigmoid = apply_sigmoid, sigmoid_sensitivity = sigmoid_sensitivity, species_filter = filter_species, - custom_model = audio_model + custom_model = model$py_model ) + predictions <- py_birdnet_types$SpeciesPredictions(predictions_gen) predictions_to_df(predictions, keep_empty = keep_empty) } + + #' Predict species for a given location and time #' #' Uses the BirdNET Species Range Model to estimate the presence of bird species at a specified location and time of year. @@ -280,7 +580,7 @@ predict_species <- function(model, #' For more details, you can view the full discussion here: #' https://github.com/kahst/BirdNET-Analyzer/discussions/234 #' -#' @param model BirdNETModel. An instance of the BirdNET model returned by [`init_model()`]. +#' @param model birdnet_model_meta. An instance of the BirdNET model returned by [`birdnet_model_meta()`]. #' @param latitude numeric. The latitude of the location for species prediction. Must be in the interval \[-90.0, 90.0\]. #' @param longitude numeric. The longitude of the location for species prediction. Must be in the interval \[-180.0, 180.0\]. #' @param week integer. The week of the year for which to predict species. Must be in the interval \[1, 48\] if specified. If NULL, predictions are not limited to a specific week. @@ -288,34 +588,36 @@ predict_species <- function(model, #' #' @return A data frame with columns: `label`, `confidence`. Each row represents a predicted species, with the `confidence` indicating the likelihood of the species being present at the specified location and time. #' @export -#' #' @examplesIf interactive() #' # Predict species in Chemnitz, Germany, that are present all year round -#' model <- init_model(language = "de") +#' model <- birdnet_model_meta(language = "de") #' predict_species_at_location_and_time(model, latitude = 50.8334, longitude = 12.9231) predict_species_at_location_and_time <- function(model, latitude, longitude, week = NULL, min_confidence = 0.03) { - stopifnot(is.list(model)) - stopifnot(inherits(model$meta_model(), "birdnet.models.v2m4.model_v2m4_tflite.MetaModelV2M4TFLite")) + UseMethod("predict_species_at_location_and_time") +} - meta_model <- model$meta_model(tflite_num_threads = model$tflite_num_threads, language = model$language) +#' @rdname predict_species_at_location_and_time +#' @export +#' @method predict_species_at_location_and_time birdnet_model_meta +predict_species_at_location_and_time.birdnet_model_meta <- function(model, + latitude, + longitude, + week = NULL, + min_confidence = 0.03) { + stopifnot(is.list(model)) + stopifnot(inherits(model, "birdnet_model_meta")) predictions <- py_birdnet_location_based_prediction$predict_species_at_location_and_time( latitude, longitude, week = week, min_confidence = min_confidence, - custom_model = meta_model + custom_model = model$py_model ) - # - # predictions <- model$predict_species_at_location_and_time(latitude, - # longitude, - # week = week, - # min_confidence = min_confidence - # ) data.frame( label = names(predictions), From 01d63c7b2580fb3a79680f7d2546eebec9730c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Thu, 12 Sep 2024 16:16:47 +0200 Subject: [PATCH 06/17] adds and updates tests --- tests/testthat/test-birdnet_interface.R | 122 ---------------- tests/testthat/test-get_labels_path.R | 21 ++- tests/testthat/test-module_map.R | 34 +++-- ...st-predict_species_at_location_and_time.R} | 15 +- .../test-predict_species_from_audio_file.R | 134 ++++++++++++++++++ 5 files changed, 191 insertions(+), 135 deletions(-) delete mode 100644 tests/testthat/test-birdnet_interface.R rename tests/testthat/{test-predict_species_location_time.R => test-predict_species_at_location_and_time.R} (84%) create mode 100644 tests/testthat/test-predict_species_from_audio_file.R diff --git a/tests/testthat/test-birdnet_interface.R b/tests/testthat/test-birdnet_interface.R deleted file mode 100644 index 59e0727..0000000 --- a/tests/testthat/test-birdnet_interface.R +++ /dev/null @@ -1,122 +0,0 @@ -library(testthat) -library(birdnetR) - -# Assuming that the BirdNET model and data are set up correctly in the environment. - -test_that("init_model works", { - model <- init_model() - expect_true(!is.null(model)) -}) - -test_that("predict_species works with default parameters", { - model <- init_model() - predictions <- predict_species(model) - expect_true(!is.null(predictions)) - expect_true(nrow(predictions) > 0) -}) - -test_that("predict_species handles custom species list correctly", { - model <- init_model() - - # Single species - custom_species_list <- c("Cyanocitta cristata_Blue Jay") - predictions <- predict_species(model, filter_species = custom_species_list, keep_empty = FALSE) - expect_true(nrow(predictions) >= 0) # Since keep_empty = FALSE, it could be 0 if no match - - # Multiple species - custom_species_list <- c("Cyanocitta cristata_Blue Jay", "Zenaida macroura_Mourning Dove") - predictions <- predict_species(model, filter_species = custom_species_list, keep_empty = FALSE) - expect_true(nrow(predictions) >= 0) # As above, could be 0 if no match -}) - -test_that("predict_species handles bandpass filtering", { - model <- init_model() - - # With bandpass filter - predictions <- predict_species(model, use_bandpass = TRUE, bandpass_fmin = 500L, bandpass_fmax = 15000L) - expect_true(!is.null(predictions)) - expect_true(nrow(predictions) > 0) - - # Without bandpass filter - predictions <- predict_species(model, use_bandpass = FALSE) - expect_true(!is.null(predictions)) - expect_true(nrow(predictions) > 0) -}) - -test_that("predict_species applies sigmoid function correctly", { - model <- init_model() - - # Apply sigmoid - predictions <- predict_species(model, apply_sigmoid = TRUE, sigmoid_sensitivity = 1) - expect_true(!is.null(predictions)) - expect_true(nrow(predictions) > 0) - - # No sigmoid application - predictions <- predict_species(model, apply_sigmoid = FALSE) - expect_true(!is.null(predictions)) - expect_true(nrow(predictions) > 0) -}) - -test_that("predict_species respects minimum confidence threshold", { - model <- init_model() - - # Lower threshold - predictions <- predict_species(model, min_confidence = 0.05) - expect_true(!is.null(predictions)) - expect_true(nrow(predictions) > 0) - expect_true(max(predictions$confidence, na.rm = TRUE) >= 0.05) - - # Higher threshold - predictions <- predict_species(model, min_confidence = 0.5) - expect_true(!is.null(predictions)) - expect_true(nrow(predictions) > 0) - expect_true(max(predictions$confidence, na.rm = TRUE) >= 0.5) -}) - -test_that("predict_species applies overlap", { - model <- init_model() - - # Lower threshold - predictions <- predict_species(model, chunk_overlap_s = 1) - expect_true(!is.null(predictions)) - expect_true(nrow(predictions) > 0) - - expect_equal(sort(unique(predictions$start))[1:4], c(0, 2, 4, 6)) -}) - - -test_that("predict_species keeps empty intervals when specified", { - model <- init_model() - - # Keep empty intervals - predictions_with_empty <- predict_species(model, keep_empty = TRUE) - expect_true(!is.null(predictions_with_empty)) - expect_true(nrow(predictions_with_empty) > 0) - - # Do not keep empty intervals - predictions_wo_empty <- predict_species(model, keep_empty = FALSE) - expect_true(!is.null(predictions_wo_empty)) - expect_true(nrow(predictions_wo_empty ) >= 0) # Could be 0 if no species detected - expect_true(nrow(predictions_with_empty) > nrow(predictions_wo_empty)) -}) - -test_that("predict_species handles invalid inputs gracefully", { - model <- init_model() - - # Invalid species list type - expect_error(predict_species(model, filter_species = 123)) - expect_error(predict_species(model, filter_species = list(c("A", "B")))) - - # Invalid bandpass frequencies - expect_error(predict_species(model, bandpass_fmin = -100L)) - expect_error(predict_species(model, bandpass_fmin = 500L, bandpass_fmax = 100L)) - - # Invalid sigmoid sensitivity - expect_error(predict_species(model, sigmoid_sensitivity = 2)) - - # Invalid batch size - expect_error(predict_species(model, batch_size = 0L)) - - # Invalid file path - expect_error(predict_species(model, audio_file = "nonexistent_file.wav")) -}) diff --git a/tests/testthat/test-get_labels_path.R b/tests/testthat/test-get_labels_path.R index 36f23db..53c10ea 100644 --- a/tests/testthat/test-get_labels_path.R +++ b/tests/testthat/test-get_labels_path.R @@ -1,11 +1,26 @@ library(testthat) +tflite_model <- birdnet_model_tflite(version = "v2.4") +protobuf_model <- birdnet_model_protobuf(version = "v2.4") + + test_that("get_labels_path returns correct path for valid language", { - path <- get_labels_path("en_us") + path <- get_labels_path(model = tflite_model, language = "en_us") + expect_true(basename(path) == "en_us.txt") + expect_true(file.exists(path)) + + path <- get_labels_path(model = protobuf_model, language = "en_us") + expect_true(basename(path) == "en_us.txt") expect_true(file.exists(path)) }) -test_that("get_labels_path throws an error for invalid language", { +test_that("get_labels_path returns correct path for invalid language", { + expect_error(get_labels_path(model = tflite_model, language = "blonk")) + expect_error(get_labels_path(model = protobuf_model, language = "blonk")) +}) + + +test_that("get_labels_path throws an error for character input", { expect_error(get_labels_path("invalid_language")) }) @@ -13,3 +28,5 @@ test_that("get_labels_path handles edge cases like empty string or NULL", { expect_error(get_labels_path("")) expect_error(get_labels_path(NULL)) }) + +# Missing test for get_labels_path with custom model diff --git a/tests/testthat/test-module_map.R b/tests/testthat/test-module_map.R index 0d3e9d8..c444252 100644 --- a/tests/testthat/test-module_map.R +++ b/tests/testthat/test-module_map.R @@ -1,24 +1,38 @@ -library(birdnetR) library(testthat) test_that("create_module_map", { module_map <- create_module_map("v2.4", "py_birdnet_models") expect_type(module_map, "list") expect_named(module_map, c("models", "misc")) - expect_named(module_map$models, c("tflite_v2.4", "protobuf_v2.4", "custom_v2.4", "raven_v2.4", "meta_v2.4")) - expect_named(module_map$misc, "available_languages_v2.4") + expect_named( + module_map$models, + c("tflite", "protobuf", "custom", "raven", "meta") + ) }) - -test_that("get_model", { +test_that("getting eelements from modules mpa", { module_map <- create_module_map("v2.4", "py_birdnet_models") - tflite_model_path <- get_model_from_module_map(module_map, "tflite_v2.4") - expect_type(tflite_model_path, "character") + tflite_model_path <- get_element_from_module_map(module_map, "models", "tflite") + expect_equal( + tflite_model_path, + "py_birdnet_models$v2m4$AudioModelV2M4TFLite" + ) }) -test_that("get_misc", { + +test_that("all mapped modules can be evaluates", { module_map <- create_module_map("v2.4", "py_birdnet_models") - available_languages_path <- get_misc_from_module_map(module_map, "available_languages_v2.4") - expect_type(available_languages_path, "character") + + # Evaluate all modules + for (module in unlist(module_map)) { + expect_s3_class( + evaluate_python_path(module), + c( + "python.builtin.type", + "python.builtin.object", + "python.builtin.set" + ) + ) + } }) diff --git a/tests/testthat/test-predict_species_location_time.R b/tests/testthat/test-predict_species_at_location_and_time.R similarity index 84% rename from tests/testthat/test-predict_species_location_time.R rename to tests/testthat/test-predict_species_at_location_and_time.R index a7f7ee8..c68c0e0 100644 --- a/tests/testthat/test-predict_species_location_time.R +++ b/tests/testthat/test-predict_species_at_location_and_time.R @@ -1,7 +1,20 @@ library(testthat) # Assuming that `init_model` is a function that initializes the BirdNET model -model <- init_model(language = "en_us") + +model <- NULL + +test_that("birdnet_model_meta works", { + model <<- birdnet_model_meta(version = "v2.4") + expect_true(!is.null(model)) +}) + +test_that("birdnet_model structure is correct", { + expect_s3_class(model, "birdnet_model_meta") + expect_s3_class(model$py_model, "birdnet.models.v2m4.model_v2m4_tflite.MetaModelV2M4TFLite") + expect_equal(model$model_version, "v2.4") +}) + test_that("predict_species_at_location_and_time returns a data frame", { result <- predict_species_at_location_and_time(model, latitude = 50.8334, longitude = 12.9231) diff --git a/tests/testthat/test-predict_species_from_audio_file.R b/tests/testthat/test-predict_species_from_audio_file.R new file mode 100644 index 0000000..3f4c17a --- /dev/null +++ b/tests/testthat/test-predict_species_from_audio_file.R @@ -0,0 +1,134 @@ +library(testthat) + +# Assuming that the BirdNET model and data are set up correctly in the environment. + +tflite_model <- NULL +protobuf_model <- NULL +audio_file <- system.file("extdata", "soundscape.wav", package = "birdnetR") + + +test_that("birdnet_model_tflite works", { + tflite_model <<- birdnet_model_tflite(version = "v2.4") + expect_true(!is.null(tflite_model)) +}) + +test_that("birdnet_model_protobuf works", { + protobuf_model <<- birdnet_model_protobuf(version = "v2.4") + expect_true(!is.null(tflite_model)) +}) + + +test_that("birdnet_model structure is correct", { + expect_s3_class(tflite_model, c("birdnet_model_tflite")) + expect_s3_class(tflite_model$py_model, c("python.builtin.object", "birdnet.models.v2m4.model_v2m4_tflite.AudioModelV2M4TFLite" )) + expect_equal(tflite_model$model_version, "v2.4") + + expect_s3_class(protobuf_model, c("birdnet_model_protobuf")) + expect_s3_class(protobuf_model$py_model, c("birdnet.models.v2m4.model_v2m4_protobuf.AudioModelV2M4Protobuf")) + expect_equal(protobuf_model$model_version, "v2.4") +}) + +test_that("predict_species works with default parameters", { + predictions <- predict_species_from_audio_file(tflite_model, audio_file) + expect_true(!is.null(predictions)) + expect_true(nrow(predictions) > 0) + + predictions <- predict_species_from_audio_file(protobuf_model, audio_file) + expect_true(!is.null(predictions)) + expect_true(nrow(predictions) > 0) +}) + +test_that("predict_species handles custom species list correctly", { + # Single species + custom_species_list <- c("Cyanocitta cristata_Blue Jay") + predictions <- predict_species_from_audio_file(tflite_model, audio_file, filter_species = custom_species_list, keep_empty = FALSE) + expect_true(nrow(predictions) >= 0) # Since keep_empty = FALSE, it could be 0 if no match + + # Multiple species + custom_species_list <- c("Cyanocitta cristata_Blue Jay", "Zenaida macroura_Mourning Dove") + predictions <- predict_species_from_audio_file(tflite_model, audio_file, filter_species = custom_species_list, keep_empty = FALSE) + expect_true(nrow(predictions) >= 0) # As above, could be 0 if no match +}) + +test_that("predict_species handles bandpass filtering", { + # With bandpass filter + predictions <- predict_species_from_audio_file(tflite_model, audio_file, use_bandpass = TRUE, bandpass_fmin = 500L, bandpass_fmax = 15000L) + expect_true(!is.null(predictions)) + expect_true(nrow(predictions) > 0) + + # Without bandpass filter + predictions <- predict_species_from_audio_file(tflite_model, audio_file, use_bandpass = FALSE) + expect_true(!is.null(predictions)) + expect_true(nrow(predictions) > 0) +}) + +test_that("predict_species applies sigmoid function correctly", { + # Apply sigmoid + predictions <- predict_species_from_audio_file(tflite_model, audio_file, apply_sigmoid = TRUE, sigmoid_sensitivity = 1) + expect_true(!is.null(predictions)) + expect_true(nrow(predictions) > 0) + + # No sigmoid application + predictions <- predict_species_from_audio_file(tflite_model, audio_file, apply_sigmoid = FALSE) + expect_true(!is.null(predictions)) + expect_true(nrow(predictions) > 0) +}) + +test_that("predict_species respects minimum confidence threshold", { + + # Lower threshold + predictions <- predict_species_from_audio_file(tflite_model, audio_file, min_confidence = 0.05) + expect_true(!is.null(predictions)) + expect_true(nrow(predictions) > 0) + expect_true(max(predictions$confidence, na.rm = TRUE) >= 0.05) + + # Higher threshold + predictions <- predict_species_from_audio_file(tflite_model, audio_file, min_confidence = 0.5) + expect_true(!is.null(predictions)) + expect_true(nrow(predictions) > 0) + expect_true(max(predictions$confidence, na.rm = TRUE) >= 0.5) +}) + +test_that("predict_species applies overlap", { + + # Lower threshold + predictions <- predict_species_from_audio_file(tflite_model, audio_file, chunk_overlap_s = 1) + expect_true(!is.null(predictions)) + expect_true(nrow(predictions) > 0) + + expect_equal(sort(unique(predictions$start))[1:4], c(0, 2, 4, 6)) +}) + + +test_that("predict_species keeps empty intervals when specified", { + # Keep empty intervals + predictions_with_empty <- predict_species_from_audio_file(tflite_model, audio_file, keep_empty = TRUE) + expect_true(!is.null(predictions_with_empty)) + expect_true(nrow(predictions_with_empty) > 0) + + # Do not keep empty intervals + predictions_wo_empty <- predict_species_from_audio_file(tflite_model, audio_file, keep_empty = FALSE) + expect_true(!is.null(predictions_wo_empty)) + expect_true(nrow(predictions_wo_empty) >= 0) # Could be 0 if no species detected + expect_true(nrow(predictions_with_empty) > nrow(predictions_wo_empty)) +}) + +test_that("predict_species handles invalid inputs gracefully", { + + # Invalid species list type + expect_error(predict_species_from_audio_file(tflite_model, audio_file, filter_species = 123)) + expect_error(predict_species_from_audio_file(tflite_model, audio_file,filter_species = list(c("A", "B")))) + + # Invalid bandpass frequencies + expect_error(predict_species_from_audio_file(tflite_model, audio_file, bandpass_fmin = -100L)) + expect_error(predict_species_from_audio_file(tflite_model, audio_file, bandpass_fmin = 500L, bandpass_fmax = 100L)) + + # Invalid sigmoid sensitivity + expect_error(predict_species_from_audio_file(tflite_model, audio_file, sigmoid_sensitivity = 2)) + + # Invalid batch size + expect_error(predict_species_from_audio_file(tflite_model, audio_file, batch_size = 0L)) + + # Invalid file path + expect_error(predict_species_from_audio_file(tflite_model, audio_file = "nonexistent_file.wav")) +}) From 4fa7d301d6cd4d60cfe8103eb74a016c37b40181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Thu, 12 Sep 2024 16:17:13 +0200 Subject: [PATCH 07/17] update docs --- NAMESPACE | 11 ++- man/available_languages.Rd | 9 ++- man/birdnet_model_load.Rd | 80 ++++++++++++++++++ man/create_module_map.Rd | 26 ++++++ man/dot-check_birdnet_version.Rd | 4 +- man/evaluate_python_path.Rd | 27 +++++++ man/get_element_from_module_map.Rd | 27 +++++++ man/get_labels_path.Rd | 28 +++++-- man/get_language_path.Rd | 34 ++++++++ man/get_species_from_file.Rd | 3 +- man/init_model.Rd | 9 ++- man/model_factory.Rd | 30 +++++++ man/new_birdnet_model.Rd | 34 ++++++++ man/predict_species.Rd | 64 --------------- man/predict_species_at_location_and_time.Rd | 13 ++- man/predict_species_from_audio_file.Rd | 89 +++++++++++++++++++++ 16 files changed, 407 insertions(+), 81 deletions(-) create mode 100644 man/birdnet_model_load.Rd create mode 100644 man/create_module_map.Rd create mode 100644 man/evaluate_python_path.Rd create mode 100644 man/get_element_from_module_map.Rd create mode 100644 man/get_language_path.Rd create mode 100644 man/model_factory.Rd create mode 100644 man/new_birdnet_model.Rd delete mode 100644 man/predict_species.Rd create mode 100644 man/predict_species_from_audio_file.Rd diff --git a/NAMESPACE b/NAMESPACE index 94e9cb2..042abb9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,13 +1,22 @@ # Generated by roxygen2: do not edit by hand +S3method(get_labels_path,birdnet_model_custom) +S3method(get_labels_path,birdnet_model_protobuf) +S3method(get_labels_path,birdnet_model_tflite) +S3method(predict_species_at_location_and_time,birdnet_model_meta) +S3method(predict_species_from_audio_file,birdnet_model) export(available_languages) +export(birdnet_model_custom) +export(birdnet_model_meta) +export(birdnet_model_protobuf) +export(birdnet_model_tflite) export(get_labels_path) export(get_species_from_file) export(get_top_prediction) export(init_model) export(install_birdnet) -export(predict_species) export(predict_species_at_location_and_time) +export(predict_species_from_audio_file) import(reticulate) importFrom(reticulate,configure_environment) importFrom(reticulate,import) diff --git a/man/available_languages.Rd b/man/available_languages.Rd index 43db9d4..f24232a 100644 --- a/man/available_languages.Rd +++ b/man/available_languages.Rd @@ -4,14 +4,17 @@ \alias{available_languages} \title{Get Available Languages for BirdNET Model} \usage{ -available_languages() +available_languages(version) +} +\arguments{ +\item{version}{character. The version of BirdNET to use (default is "v2.4", no other versions are currently supported).} } \value{ A sorted character vector containing the available language codes. } \description{ -Retrieve the available languages supported by the BirdNET model. +Retrieve the available languages supported by a specific version of BirdNET. } \examples{ -available_languages() +available_languages("v2.4") } diff --git a/man/birdnet_model_load.Rd b/man/birdnet_model_load.Rd new file mode 100644 index 0000000..397dee4 --- /dev/null +++ b/man/birdnet_model_load.Rd @@ -0,0 +1,80 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/birdnet_interface.R +\name{birdnet_model_load} +\alias{birdnet_model_load} +\alias{birdnet_model_tflite} +\alias{birdnet_model_custom} +\alias{birdnet_model_meta} +\alias{birdnet_model_protobuf} +\title{Initialize a BirdNET Model} +\usage{ +birdnet_model_tflite( + version = "v2.4", + language = "en_us", + tflite_num_threads = NULL +) + +birdnet_model_custom( + version = "v2.4", + classifier_folder, + classifier_name, + tflite_num_threads = NULL +) + +birdnet_model_meta( + version = "v2.4", + language = "en_us", + tflite_num_threads = NULL +) + +birdnet_model_protobuf( + version = "v2.4", + language = "en_us", + custom_device = NULL +) +} +\arguments{ +\item{version}{character. The version of BirdNET to use (default is "v2.4", no other versions are currently supported).} + +\item{language}{character. Specifies the language code to use for the model's text processing. The language must be one of the available languages supported by the BirdNET model.} + +\item{tflite_num_threads}{integer. The number of threads to use for TensorFlow Lite operations. If NULL (default), the default threading behavior will be used. +Will be coerced to an integer if possible.} + +\item{classifier_folder}{character. Path to the folder containing the custom classifier.} + +\item{classifier_name}{character. Name of the custom classifier.} + +\item{custom_device}{character. This parameter allows specifying a custom device on which computations should be performed. If \code{custom_device} is not specified (i.e., it has the default value None), the program will attempt to use a GPU (e.g., "/device:GPU:0") by default. If no GPU is available, it will fall back to using the CPU. By specifying a device string such as "/device:GPU:0" or "/device:CPU:0", the user can explicitly choose the device on which operations should be executed.} +} +\value{ +A BirdNET model object. +} +\description{ +The various function of the \verb{birdnet_model_*} family are used to create and initialize diffent BirdNET models. Models will be downloaded if necessary. +\itemize{ +\item \code{\link[=birdnet_model_tflite]{birdnet_model_tflite()}}: creates a tflite-model used for species prediction from audio. +\item \code{\link[=birdnet_model_custom]{birdnet_model_custom()}}: loads a custom model for species prediction from audio. +\item \code{\link[=birdnet_model_protobuf]{birdnet_model_protobuf()}}: creates a protobuf model for species prediction from audio that can be run on the GPU (not yet implemented). +\item \code{\link[=birdnet_model_meta]{birdnet_model_meta()}}: creates a meta model for species prediction from location and time. +} +} +\details{ +\strong{Species Prediction from audio} + +Models created from \code{\link[=birdnet_model_tflite]{birdnet_model_tflite()}}, \code{\link[=birdnet_model_custom]{birdnet_model_custom()}}, and \code{\link[=birdnet_model_protobuf]{birdnet_model_protobuf()}} can be used to predict species within an audio file using \code{\link[=predict_species_from_audio_file]{predict_species_from_audio_file()}}. \cr + +\strong{Species prediction from location and time} + +The \code{\link[=birdnet_model_meta]{birdnet_model_meta()}} model can be used to predict species occurrence at a specific location and time of the year using \code{\link[=predict_species_at_location_and_time]{predict_species_at_location_and_time()}}. +} +\note{ +Currently, all models can only be executed on the CPU. GPU support is not yet available. +} +\examples{ +# Create a TFLite BirdNET model with 2 threads and English (US) language +birdnet_model <- birdnet_model_tflite(version = "v2.4", language = "en_us", tflite_num_threads = 2) +} +\seealso{ +\code{\link[=available_languages]{available_languages()}} \code{\link[=predict_species_from_audio_file]{predict_species_from_audio_file()}} \code{\link[=predict_species_at_location_and_time]{predict_species_at_location_and_time()}} +} diff --git a/man/create_module_map.Rd b/man/create_module_map.Rd new file mode 100644 index 0000000..81720c8 --- /dev/null +++ b/man/create_module_map.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_map.R +\name{create_module_map} +\alias{create_module_map} +\title{Create a module map based on version and base Python module} +\usage{ +create_module_map(version, base_module) +} +\arguments{ +\item{version}{Character. The version of the module (e.g., "v2.4").} + +\item{base_module}{Character. The base Python module path as a string (e.g., "py_birdnet_models").} +} +\value{ +A list containing 'models' (a list of model constructors) and 'misc' (a list of miscellaneous paths), specific to the version and base module. +} +\description{ +This function returns a list of model constructors and miscellaneous paths for a specific version and base Python module. +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +py_birdnet_models <- reticulate::import("birdnet.models") +module_map <- create_module_map("v2.4", "py_birdnet_models") +\dontshow{\}) # examplesIf} +} +\keyword{internal} diff --git a/man/dot-check_birdnet_version.Rd b/man/dot-check_birdnet_version.Rd index 10a7058..304be74 100644 --- a/man/dot-check_birdnet_version.Rd +++ b/man/dot-check_birdnet_version.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/birdnet_interface.R \name{.check_birdnet_version} \alias{.check_birdnet_version} -\title{Check the Installed BirdNET Version} +\title{Check the Installed birdnet Version} \usage{ .check_birdnet_version() } @@ -10,7 +10,7 @@ None. This function is called for its side effect of stopping execution if the wrong version is installed. } \description{ -This internal function checks if BirdNET Python is installed and if the version matches the required version. +This internal function checks if birdnet Python is installed and if the version matches the requirement. If it is not available or if the versions do not match, issue a warning with instructions to update the package. } \keyword{internal} diff --git a/man/evaluate_python_path.Rd b/man/evaluate_python_path.Rd new file mode 100644 index 0000000..f381b49 --- /dev/null +++ b/man/evaluate_python_path.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_map.R +\name{evaluate_python_path} +\alias{evaluate_python_path} +\title{Evaluate a Python path string and return the corresponding Python object} +\usage{ +evaluate_python_path(path_string) +} +\arguments{ +\item{path_string}{Character. The string representing the Python path (e.g., "py_birdnet_models$v2m4$AudioModelV2M4TFLite").} +} +\value{ +The evaluated Python object or value. +} +\description{ +This function takes a string representing a Python path (e.g., from \code{get_model_from_module_map()}) +and evaluates it to return the corresponding Python object. +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +py_birdnet_models <- reticulate::import("birdnet.models") +module_map <- create_module_map("v2.4", "py_birdnet_models") +model_string <- get_model_from_module_map(module_map, "tflite_v2.4") +model_object <- evaluate_python_path(model_string) +\dontshow{\}) # examplesIf} +} +\keyword{internal} diff --git a/man/get_element_from_module_map.Rd b/man/get_element_from_module_map.Rd new file mode 100644 index 0000000..b2454e1 --- /dev/null +++ b/man/get_element_from_module_map.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module_map.R +\name{get_element_from_module_map} +\alias{get_element_from_module_map} +\title{Get an element from a module map regardless of nesting level} +\usage{ +get_element_from_module_map(module_map, ...) +} +\arguments{ +\item{module_map}{A list returned from \code{create_module_map()}.} + +\item{...}{A sequence of keys that represent the path to the desired element in the module map.} +} +\value{ +The element located at the specified path within the module map. +} +\description{ +This function retrieves an element from a module map by traversing the nested structure. +It takes a variable number of arguments that represent the keys to navigate through the module map. +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +module_map <- create_module_map("v2.4", "py_birdnet_models") +available_languages_path <- get_element_from_module_map(module_map, "misc", "available_languages") +\dontshow{\}) # examplesIf} +} +\keyword{internal} diff --git a/man/get_labels_path.Rd b/man/get_labels_path.Rd index 348054b..da0a5da 100644 --- a/man/get_labels_path.Rd +++ b/man/get_labels_path.Rd @@ -2,12 +2,25 @@ % Please edit documentation in R/birdnet_interface.R \name{get_labels_path} \alias{get_labels_path} -\title{Get Path to BirdNET Labels File for a Specified Language} +\alias{get_labels_path.birdnet_model_custom} +\alias{get_labels_path.birdnet_model_tflite} +\alias{get_labels_path.birdnet_model_protobuf} +\title{Get Path to a Labels File} \usage{ -get_labels_path(language) +get_labels_path(model, ...) + +\method{get_labels_path}{birdnet_model_custom}(model, ...) + +\method{get_labels_path}{birdnet_model_tflite}(model, language, ...) + +\method{get_labels_path}{birdnet_model_protobuf}(model, language, ...) } \arguments{ -\item{language}{A character string specifying the language code for which the labels path is requested. +\item{model}{A BirdNET model object.} + +\item{...}{Additional arguments passed to the method dispatch function.} + +\item{language}{character. Specifies the language code for which the labels path is returned. The language must be one of the available languages supported by the BirdNET model.} } \value{ @@ -16,13 +29,18 @@ A character string representing the file path to the labels file for the specifi \description{ This function retrieves the file path to the BirdNET labels file on your system corresponding to a specified language. This file contains all class labels supported by the BirdNET model. + +For a custom model, the path of the custom labels file is returned. } \note{ The \code{language} parameter must be one of the available languages returned by \code{available_languages()}. } \examples{ -get_labels_path("en_us") +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +model <- birdnet_model_tflite(version = "v2.4") +get_labels_path(model, "fr") +\dontshow{\}) # examplesIf} } \seealso{ -\code{\link[=available_languages]{available_languages()}} +\code{\link[=available_languages]{available_languages()}} \code{\link[=get_species_from_file]{get_species_from_file()}} } diff --git a/man/get_language_path.Rd b/man/get_language_path.Rd new file mode 100644 index 0000000..6fd524d --- /dev/null +++ b/man/get_language_path.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/birdnet_interface.R +\name{get_language_path} +\alias{get_language_path} +\title{Helper function to retrieve the language path for a BirdNET model} +\usage{ +get_language_path(model, language, downloader_key, subfolder) +} +\arguments{ +\item{model}{A BirdNET model object containing the version information.} + +\item{language}{Character. The language code for which to retrieve the path (e.g., "en_us"). +Must be one of the available languages for the given model version.} + +\item{downloader_key}{Character. The key in the module map that specifies the downloader +to use (e.g., "downloader_tflite", "downloader_protobuf").} + +\item{subfolder}{Character. The subfolder in which the language files are stored (e.g., "TFLite", "Protobuf").} +} +\value{ +A character string representing the path to the language file. +} +\description{ +This function handles the common logic for retrieving the language path for a BirdNET model. +It validates the language, creates the necessary paths from the module map, and uses the appropriate +downloader to retrieve the path to the language file. +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +model <- birdnet_model_tflite(version = "v2.4", language = "en_us") +language_path <- get_language_path(model, "en_us", "downloader_tflite", "TFLite") +\dontshow{\}) # examplesIf} +} +\keyword{internal} diff --git a/man/get_species_from_file.Rd b/man/get_species_from_file.Rd index c1611cb..60d745a 100644 --- a/man/get_species_from_file.Rd +++ b/man/get_species_from_file.Rd @@ -21,7 +21,8 @@ get_species_from_file(system.file("extdata", "species_list.txt", package = "bird # To access all class labels that are supported in your language, # you can read in the respective label file -labels_path <- get_labels_path("fr") +model <- birdnet_model_tflite(version = "v2.4", language = "en_us") +labels_path <- get_labels_path(model, "fr") species_list <- get_species_from_file(labels_path) head(species_list) } diff --git a/man/init_model.Rd b/man/init_model.Rd index f8f4456..65d29d5 100644 --- a/man/init_model.Rd +++ b/man/init_model.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/birdnet_interface.R \name{init_model} \alias{init_model} -\title{Initialize the BirdNET Model} +\title{Initialize the BirdNET Model (Deprecated)} \usage{ init_model(tflite_num_threads = NULL, language = "en_us") } @@ -10,16 +10,19 @@ init_model(tflite_num_threads = NULL, language = "en_us") \item{tflite_num_threads}{integer. The number of threads to use for TensorFlow Lite operations. If NULL (default), the default threading behavior will be used. Will be coerced to an integer if possible.} -\item{language}{A character string specifying the language code to use for the model's text processing. The language must be one of the available languages supported by the BirdNET model.} +\item{language}{Character string specifying the language code to use for the model's text processing. The language must be one of the available languages supported by the BirdNET model.} } \value{ An instance of the BirdNET model. } \description{ -This function initializes the BirdNET model (v2.4). +This function initializes the BirdNET model (v2.4). It is kept for backward compatibility and is deprecated. +Use \code{birdnet_model_tflite()} instead for model initialization. } \note{ The \code{language} parameter must be one of the available languages returned by \code{available_languages()}. + +This function is kept for backward compatibility. Please use \code{birdnet_model_tflite()} instead. } \seealso{ \code{\link[=available_languages]{available_languages()}} diff --git a/man/model_factory.Rd b/man/model_factory.Rd new file mode 100644 index 0000000..3241758 --- /dev/null +++ b/man/model_factory.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/birdnet_interface.R +\name{model_factory} +\alias{model_factory} +\title{Dynamically create a BirdNET model} +\usage{ +model_factory(model_name, version, ...) +} +\arguments{ +\item{model_name}{Character. The name of the model to create (e.g., "tflite", "protobuf").} + +\item{version}{Character. The version of the model (e.g., "v2.4").} + +\item{...}{Additional arguments passed to the Python model constructor (e.g., \code{tflite_num_threads}, \code{language}).} +} +\value{ +A BirdNET model object of class \code{birdnet_model} and its subclasses (e.g., "tflite_v2.4"). +} +\description{ +This function dynamically creates a BirdNET model based on the provided model name and version. It retrieves +the appropriate Python model constructor from the module map, evaluates the constructor, and returns a wrapped +BirdNET model object. +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +py_birdnet_models <- reticulate::import("birdnet.models") +birdnet_model <- model_factory("tflite", "v2.4", tflite_num_threads = 2, language = "en_us") +\dontshow{\}) # examplesIf} +} +\keyword{internal} diff --git a/man/new_birdnet_model.Rd b/man/new_birdnet_model.Rd new file mode 100644 index 0000000..b4452a7 --- /dev/null +++ b/man/new_birdnet_model.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/birdnet_interface.R +\name{new_birdnet_model} +\alias{new_birdnet_model} +\title{Create a new BirdNET model object} +\usage{ +new_birdnet_model(x, ..., subclass = character()) +} +\arguments{ +\item{x}{A Python object representing the BirdNET model. This is typically a Python model +object created using the \code{reticulate} package.} + +\item{...}{Additional attributes to attach to the BirdNET model object.} + +\item{subclass}{Character. An optional subclass name for the BirdNET model (e.g., "tflite_v2.4"). +The subclass is combined with the base class \code{birdnet_model}.} +} +\value{ +An S3 object of class \code{birdnet_model} (and any specified subclass) containing the Python model object +and any additional attributes passed in \code{...}. +} +\description{ +This function creates a new BirdNET model object by wrapping a Python model object and assigning +it a class and optional subclass. The model is created as an R object that can be interacted with +using R's S3 method dispatch. +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +py_birdnet_models <- reticulate::import("birdnet.models") +tflite_model <- py_birdnet_models$v2m4$AudioModelV2M4TFLite() +birdnet_model <- new_birdnet_model(tflite_model, language = "en_us", version = "v2.4") +\dontshow{\}) # examplesIf} +} +\keyword{internal} diff --git a/man/predict_species.Rd b/man/predict_species.Rd deleted file mode 100644 index 498af40..0000000 --- a/man/predict_species.Rd +++ /dev/null @@ -1,64 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/birdnet_interface.R -\name{predict_species} -\alias{predict_species} -\title{Predict Species Within an Audio File} -\usage{ -predict_species( - model, - audio_file = system.file("extdata", "soundscape.wav", package = "birdnetR"), - min_confidence = 0.1, - batch_size = 1L, - chunk_overlap_s = 0, - use_bandpass = TRUE, - bandpass_fmin = 0L, - bandpass_fmax = 15000L, - apply_sigmoid = TRUE, - sigmoid_sensitivity = 1, - filter_species = NULL, - keep_empty = TRUE -) -} -\arguments{ -\item{model}{BirdNETModel. An instance of the BirdNET model returned by \code{\link[=init_model]{init_model()}}.} - -\item{audio_file}{character. The path to the audio file.} - -\item{min_confidence}{numeric. Minimum confidence threshold for predictions.} - -\item{batch_size}{integer. Number of audio samples to process in a batch.} - -\item{chunk_overlap_s}{numeric. Overlapping of chunks in seconds. Must be in the interval [0.0, 3.0\).} - -\item{use_bandpass}{logical. Whether to apply a bandpass filter.} - -\item{bandpass_fmin, bandpass_fmax}{numeric. Minimum/Maximum frequency for the bandpass filter (in Hz). Ignored if \code{use_bandpass} is False.} - -\item{apply_sigmoid}{logical. Whether to apply a sigmoid function to the model output.} - -\item{sigmoid_sensitivity}{numeric. Sensitivity parameter for the sigmoid function. Must be in the interval 0.5 - 1.5. Ignored if \code{apply_sigmoid} is False.} - -\item{filter_species}{NULL, a character vector of length greater than 0 or a list where each element is a single non-empty character string. Used to filter the predictions. If NULL, no filtering is applied. See \code{\link[=get_species_from_file]{get_species_from_file()}} for more details.} - -\item{keep_empty}{logical. Whether to include empty intervals in the output.} -} -\value{ -A data frame with columns: \code{start}, \code{end}, \code{scientific_name}, \code{common_name}, and \code{confidence}. -Each row represents a single prediction. -} -\description{ -This function predicts species within an audio file using the BirdNET model. -} -\details{ -Applying a sigmoid activation function, (\code{apply_sigmoid=True}) scales the unbound class output of the linear classifier ("logit score") to the range \code{0-1}. -This confidence score is a unitless, numeric expression of BirdNET’s “confidence” in its prediction (but not the probability of species presence). -Sigmoid sensitivity < 1 leads to more higher and lower scoring predictions and a value > 1 leads to more intermediate-scoring predictions. - -For more information on BirdNET confidence scores, the sigmoid activation function and a suggested workflow on how to convert confidence scores to probabilities, see Wood & Kahl, 2024 -} -\references{ -Wood, C. M., & Kahl, S. (2024). Guidelines for appropriate use of BirdNET scores and other detector outputs. Journal of Ornithology. https://doi.org/10.1007/s10336-024-02144-5 -} -\seealso{ -\code{\link[=init_model]{init_model()}} \code{\link[=get_species_from_file]{get_species_from_file()}} -} diff --git a/man/predict_species_at_location_and_time.Rd b/man/predict_species_at_location_and_time.Rd index a1b3b76..bc4311c 100644 --- a/man/predict_species_at_location_and_time.Rd +++ b/man/predict_species_at_location_and_time.Rd @@ -2,6 +2,7 @@ % Please edit documentation in R/birdnet_interface.R \name{predict_species_at_location_and_time} \alias{predict_species_at_location_and_time} +\alias{predict_species_at_location_and_time.birdnet_model_meta} \title{Predict species for a given location and time} \usage{ predict_species_at_location_and_time( @@ -11,9 +12,17 @@ predict_species_at_location_and_time( week = NULL, min_confidence = 0.03 ) + +\method{predict_species_at_location_and_time}{birdnet_model_meta}( + model, + latitude, + longitude, + week = NULL, + min_confidence = 0.03 +) } \arguments{ -\item{model}{BirdNETModel. An instance of the BirdNET model returned by \code{\link[=init_model]{init_model()}}.} +\item{model}{birdnet_model_meta. An instance of the BirdNET model returned by \code{\link[=birdnet_model_meta]{birdnet_model_meta()}}.} \item{latitude}{numeric. The latitude of the location for species prediction. Must be in the interval [-90.0, 90.0].} @@ -39,7 +48,7 @@ https://github.com/kahst/BirdNET-Analyzer/discussions/234 \examples{ \dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} # Predict species in Chemnitz, Germany, that are present all year round -model <- init_model(language = "de") +model <- birdnet_model_meta(language = "de") predict_species_at_location_and_time(model, latitude = 50.8334, longitude = 12.9231) \dontshow{\}) # examplesIf} } diff --git a/man/predict_species_from_audio_file.Rd b/man/predict_species_from_audio_file.Rd new file mode 100644 index 0000000..b601615 --- /dev/null +++ b/man/predict_species_from_audio_file.Rd @@ -0,0 +1,89 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/birdnet_interface.R +\name{predict_species_from_audio_file} +\alias{predict_species_from_audio_file} +\alias{predict_species_from_audio_file.birdnet_model} +\title{Predict species within an audio file using a BirdNET model} +\usage{ +predict_species_from_audio_file( + model, + audio_file, + min_confidence = 0.1, + batch_size = 1L, + chunk_overlap_s = 0, + use_bandpass = TRUE, + bandpass_fmin = 0L, + bandpass_fmax = 15000L, + apply_sigmoid = TRUE, + sigmoid_sensitivity = 1, + filter_species = NULL, + keep_empty = TRUE +) + +\method{predict_species_from_audio_file}{birdnet_model}( + model, + audio_file, + min_confidence = 0.1, + batch_size = 1L, + chunk_overlap_s = 0, + use_bandpass = TRUE, + bandpass_fmin = 0L, + bandpass_fmax = 15000L, + apply_sigmoid = TRUE, + sigmoid_sensitivity = 1, + filter_species = NULL, + keep_empty = TRUE +) +} +\arguments{ +\item{model}{A BirdNET model object. An instance of the BirdNET model (e.g., \code{birdnet_model_tflite}, \code{birdnet_model_protobuf}).} + +\item{audio_file}{character. The path to the audio file.} + +\item{min_confidence}{numeric. Minimum confidence threshold for predictions (default is 0.1).} + +\item{batch_size}{integer. Number of audio samples to process in a batch (default is 1L).} + +\item{chunk_overlap_s}{numeric. The overlap between audio chunks in seconds (default is 0). Must be in the interval [0.0, 3.0].} + +\item{use_bandpass}{logical. Whether to apply a bandpass filter (default is TRUE).} + +\item{bandpass_fmin, bandpass_fmax}{integer. Minimum and maximum frequencies for the bandpass filter (in Hz). Ignored if \code{use_bandpass} is FALSE (default is 0L to 15000L).} + +\item{apply_sigmoid}{logical. Whether to apply a sigmoid function to the model output (default is TRUE).} + +\item{sigmoid_sensitivity}{numeric. Sensitivity parameter for the sigmoid function (default is 1). Must be in the interval [0.5, 1.5]. Ignored if \code{apply_sigmoid} is FALSE.} + +\item{filter_species}{NULL, a character vector of length greater than 0, or a list where each element is a single non-empty character string. Used to filter the predictions. If NULL (default), no filtering is applied.} + +\item{keep_empty}{logical. Whether to include empty intervals in the output (default is TRUE).} +} +\value{ +A data frame with columns: \code{start}, \code{end}, \code{scientific_name}, \code{common_name}, and \code{confidence}. Each row represents a single prediction. +} +\description{ +Use a BirdNET model to predict species within an audio file. The model can be a TFLite model, a custom model, or a Protobuf model. +} +\details{ +Applying a sigmoid activation function (\code{apply_sigmoid=TRUE}) scales the unbound class output of the linear classifier ("logit score") to the range \code{0-1}. +This confidence score is a unitless, numeric expression of BirdNET’s “confidence” in its prediction (but not the probability of species presence). +Sigmoid sensitivity < 1 leads to more higher and lower scoring predictions, and a value > 1 leads to more intermediate-scoring predictions. + +For more information on BirdNET confidence scores, the sigmoid activation function, and a suggested workflow on how to convert confidence scores to probabilities, see Wood & Kahl, 2024. +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +library(birdnetR) + +model <- birdnet_model_tflite(version = "v2.4", language = "en_us") +predictions <- predict_species_from_audio_file(model, "path/to/audio.wav", min_confidence = 0.2) +\dontshow{\}) # examplesIf} +} +\references{ +Wood, C. M., & Kahl, S. (2024). Guidelines for appropriate use of BirdNET scores and other detector outputs. Journal of Ornithology. https://doi.org/10.1007/s10336-024-02144-5 +} +\seealso{ +\code{\link[=get_species_from_file]{get_species_from_file()}} for more details on species filtering. + +\code{\link{predict_species_from_audio_file.birdnet_model}} +} From 9e42cb3ccb58700a72eb818a38eee2ad9d3dcb10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Thu, 12 Sep 2024 16:18:11 +0200 Subject: [PATCH 08/17] ignore dev/ folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9a01975..ab1e3e4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ _other/ inst/doc docs +dev/ From aeff0ac9b5153251609ec980389556dec05fb260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Thu, 12 Sep 2024 16:33:30 +0200 Subject: [PATCH 09/17] update README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c41b548..7688f5a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ birdnetR is geared towards providing a robust workflow for ecological data analy Please note that birdnetR is under active development, so you might encounter changes that could affect your current workflow. We recommend checking for updates regularly. - +For more information, please visit the [birdnetR website](https://birdnet-team.github.io/birdnetR/). ## Citation @@ -74,14 +74,14 @@ Here's a simple example of how to use this package to predict bird species from # Load the package library(birdnetR) -# Initialize the BirdNET model -model <- init_model() +# Initialize a BirdNET model +model <- birdnet_model_tflite() # Path to the audio file (replace with your own file path) -audio_path <- "path/to/your/soundscape.wav" +audio_path <- system.file("extdata", "soundscape.wav", package = "birdnetR") # Predict species within the audio file -predictions <- predict_species(model, audio_path) +predictions <- predict_species_from_audio_file(model, audio_path) # Get most probable prediction within each time interval get_top_prediction(predictions) From 2ae83fdaad237be2e64d1a0a991b5822d053da3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Fri, 13 Sep 2024 09:04:05 +0200 Subject: [PATCH 10/17] rename `get_species_from_file` tp `read_labels` --- NAMESPACE | 2 +- R/birdnet_interface.R | 10 +++++----- man/get_labels_path.Rd | 2 +- man/predict_species_from_audio_file.Rd | 2 +- man/{get_species_from_file.Rd => read_labels.Rd} | 10 +++++----- tests/testthat/test-is_valid_species_list.R | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) rename man/{get_species_from_file.Rd => read_labels.Rd} (77%) diff --git a/NAMESPACE b/NAMESPACE index 042abb9..367ac60 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -11,12 +11,12 @@ export(birdnet_model_meta) export(birdnet_model_protobuf) export(birdnet_model_tflite) export(get_labels_path) -export(get_species_from_file) export(get_top_prediction) export(init_model) export(install_birdnet) export(predict_species_at_location_and_time) export(predict_species_from_audio_file) +export(read_labels) import(reticulate) importFrom(reticulate,configure_environment) importFrom(reticulate,import) diff --git a/R/birdnet_interface.R b/R/birdnet_interface.R index 2374be3..e1e3f59 100644 --- a/R/birdnet_interface.R +++ b/R/birdnet_interface.R @@ -357,7 +357,7 @@ available_languages <- function(version) { #' model <- birdnet_model_tflite(version = "v2.4") #' get_labels_path(model, "fr") #' @note The `language` parameter must be one of the available languages returned by `available_languages()`. -#' @seealso [available_languages()] [get_species_from_file()] +#' @seealso [available_languages()] [read_labels()] #' @export get_labels_path <- function(model, ...) { UseMethod("get_labels_path") @@ -437,15 +437,15 @@ get_labels_path.birdnet_model_protobuf <- function(model, language, ...) { #' @seealso [available_languages()] [get_labels_path()] #' @examples #' # Read a custom species file -#' get_species_from_file(system.file("extdata", "species_list.txt", package = "birdnetR")) +#' read_labels(system.file("extdata", "species_list.txt", package = "birdnetR")) #' #' # To access all class labels that are supported in your language, #' # you can read in the respective label file #' model <- birdnet_model_tflite(version = "v2.4", language = "en_us") #' labels_path <- get_labels_path(model, "fr") -#' species_list <- get_species_from_file(labels_path) +#' species_list <- read_labels(labels_path) #' head(species_list) -get_species_from_file <- function(species_file) { +read_labels <- function(species_file) { species_file_path <- py_pathlib$Path(species_file)$expanduser()$resolve(TRUE) py_species_list <- py_birdnet_utils$get_species_from_file(species_file_path) py_species_list$items @@ -481,7 +481,7 @@ get_species_from_file <- function(species_file) { #' #' @return A data frame with columns: `start`, `end`, `scientific_name`, `common_name`, and `confidence`. Each row represents a single prediction. #' -#' @seealso [`get_species_from_file()`] for more details on species filtering. +#' @seealso [`read_labels()`] for more details on species filtering. #' @export #' @seealso [`predict_species_from_audio_file.birdnet_model`] #' @examplesIf interactive() diff --git a/man/get_labels_path.Rd b/man/get_labels_path.Rd index da0a5da..a9f0b2a 100644 --- a/man/get_labels_path.Rd +++ b/man/get_labels_path.Rd @@ -42,5 +42,5 @@ get_labels_path(model, "fr") \dontshow{\}) # examplesIf} } \seealso{ -\code{\link[=available_languages]{available_languages()}} \code{\link[=get_species_from_file]{get_species_from_file()}} +\code{\link[=available_languages]{available_languages()}} \code{\link[=read_labels]{read_labels()}} } diff --git a/man/predict_species_from_audio_file.Rd b/man/predict_species_from_audio_file.Rd index b601615..86c0218 100644 --- a/man/predict_species_from_audio_file.Rd +++ b/man/predict_species_from_audio_file.Rd @@ -83,7 +83,7 @@ predictions <- predict_species_from_audio_file(model, "path/to/audio.wav", min_c Wood, C. M., & Kahl, S. (2024). Guidelines for appropriate use of BirdNET scores and other detector outputs. Journal of Ornithology. https://doi.org/10.1007/s10336-024-02144-5 } \seealso{ -\code{\link[=get_species_from_file]{get_species_from_file()}} for more details on species filtering. +\code{\link[=read_labels]{read_labels()}} for more details on species filtering. \code{\link{predict_species_from_audio_file.birdnet_model}} } diff --git a/man/get_species_from_file.Rd b/man/read_labels.Rd similarity index 77% rename from man/get_species_from_file.Rd rename to man/read_labels.Rd index 60d745a..09e5119 100644 --- a/man/get_species_from_file.Rd +++ b/man/read_labels.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/birdnet_interface.R -\name{get_species_from_file} -\alias{get_species_from_file} +\name{read_labels} +\alias{read_labels} \title{Read species labels from a file} \usage{ -get_species_from_file(species_file) +read_labels(species_file) } \arguments{ \item{species_file}{Path to species file.} @@ -17,13 +17,13 @@ This is a convenience function to read species labels from a file. } \examples{ # Read a custom species file -get_species_from_file(system.file("extdata", "species_list.txt", package = "birdnetR")) +read_labels(system.file("extdata", "species_list.txt", package = "birdnetR")) # To access all class labels that are supported in your language, # you can read in the respective label file model <- birdnet_model_tflite(version = "v2.4", language = "en_us") labels_path <- get_labels_path(model, "fr") -species_list <- get_species_from_file(labels_path) +species_list <- read_labels(labels_path) head(species_list) } \seealso{ diff --git a/tests/testthat/test-is_valid_species_list.R b/tests/testthat/test-is_valid_species_list.R index a2261fb..8d6d6bb 100644 --- a/tests/testthat/test-is_valid_species_list.R +++ b/tests/testthat/test-is_valid_species_list.R @@ -33,7 +33,7 @@ test_that("is_valid_species_list identifies valid vectors and lists", { expect_false(is_valid_species_list(list(a = NULL, b = "a"))) }) -test_that("is_valid_species_list works with get_species_from_file", { - species_list <- get_species_from_file(system.file("extdata", "species_list.txt", package = "birdnetR")) +test_that("is_valid_species_list works with read_labels", { + species_list <- read_labels(system.file("extdata", "species_list.txt", package = "birdnetR")) expect_true(is_valid_species_list(species_list)) }) From 8d5866fbf62d89f9f911ddb8facccc8dc1dd16e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Fri, 13 Sep 2024 10:03:16 +0200 Subject: [PATCH 11/17] rename `get_labels_path` to `labels_path` --- NAMESPACE | 8 +++---- R/birdnet_interface.R | 28 +++++++++++----------- man/{get_labels_path.Rd => labels_path.Rd} | 20 ++++++++-------- man/read_labels.Rd | 4 ++-- tests/testthat/test-get_labels_path.R | 24 +++++++++---------- 5 files changed, 42 insertions(+), 42 deletions(-) rename man/{get_labels_path.Rd => labels_path.Rd} (73%) diff --git a/NAMESPACE b/NAMESPACE index 367ac60..5ebe7e0 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,8 +1,8 @@ # Generated by roxygen2: do not edit by hand -S3method(get_labels_path,birdnet_model_custom) -S3method(get_labels_path,birdnet_model_protobuf) -S3method(get_labels_path,birdnet_model_tflite) +S3method(labels_path,birdnet_model_custom) +S3method(labels_path,birdnet_model_protobuf) +S3method(labels_path,birdnet_model_tflite) S3method(predict_species_at_location_and_time,birdnet_model_meta) S3method(predict_species_from_audio_file,birdnet_model) export(available_languages) @@ -10,10 +10,10 @@ export(birdnet_model_custom) export(birdnet_model_meta) export(birdnet_model_protobuf) export(birdnet_model_tflite) -export(get_labels_path) export(get_top_prediction) export(init_model) export(install_birdnet) +export(labels_path) export(predict_species_at_location_and_time) export(predict_species_from_audio_file) export(read_labels) diff --git a/R/birdnet_interface.R b/R/birdnet_interface.R index e1e3f59..e3feeaf 100644 --- a/R/birdnet_interface.R +++ b/R/birdnet_interface.R @@ -355,12 +355,12 @@ available_languages <- function(version) { #' @return A character string representing the file path to the labels file for the specified language. #' @examplesIf interactive() #' model <- birdnet_model_tflite(version = "v2.4") -#' get_labels_path(model, "fr") +#' labels_path(model, "fr") #' @note The `language` parameter must be one of the available languages returned by `available_languages()`. #' @seealso [available_languages()] [read_labels()] #' @export -get_labels_path <- function(model, ...) { - UseMethod("get_labels_path") +labels_path <- function(model, ...) { + UseMethod("labels_path") } #' Helper function to retrieve the language path for a BirdNET model @@ -402,26 +402,26 @@ get_language_path <- function(model, language, downloader_key, subfolder) { as.character(py_downloader(py_pathlib$Path(py_app_folder(), subfolder))$get_language_path(language)) } -#' @rdname get_labels_path +#' @rdname labels_path #' @description For a custom model, the path of the custom labels file is returned. #' @export -#' @method get_labels_path birdnet_model_custom -get_labels_path.birdnet_model_custom <- function(model, ...) { +#' @method labels_path birdnet_model_custom +labels_path.birdnet_model_custom <- function(model, ...) { file.path(model$classifier_folder, paste0(model$classifier_name, ".txt")) } -#' @rdname get_labels_path +#' @rdname labels_path #' @export -#' @method get_labels_path birdnet_model_tflite -get_labels_path.birdnet_model_tflite <- function(model, language, ...) { +#' @method labels_path birdnet_model_tflite +labels_path.birdnet_model_tflite <- function(model, language, ...) { get_language_path(model, language, "downloader_tflite", "TFLite") } -#' @rdname get_labels_path +#' @rdname labels_path #' @export -#' @method get_labels_path birdnet_model_protobuf -get_labels_path.birdnet_model_protobuf <- function(model, language, ...) { +#' @method labels_path birdnet_model_protobuf +labels_path.birdnet_model_protobuf <- function(model, language, ...) { get_language_path(model, language, "downloader_protobuf", "Protobuf") } @@ -434,7 +434,7 @@ get_labels_path.birdnet_model_protobuf <- function(model, language, ...) { #' #' @return A vector with class labels e.g. c("Cyanocitta cristata_Blue Jay", "Zenaida macroura_Mourning Dove") #' @export -#' @seealso [available_languages()] [get_labels_path()] +#' @seealso [available_languages()] [labels_path()] #' @examples #' # Read a custom species file #' read_labels(system.file("extdata", "species_list.txt", package = "birdnetR")) @@ -442,7 +442,7 @@ get_labels_path.birdnet_model_protobuf <- function(model, language, ...) { #' # To access all class labels that are supported in your language, #' # you can read in the respective label file #' model <- birdnet_model_tflite(version = "v2.4", language = "en_us") -#' labels_path <- get_labels_path(model, "fr") +#' labels_path <- labels_path(model, "fr") #' species_list <- read_labels(labels_path) #' head(species_list) read_labels <- function(species_file) { diff --git a/man/get_labels_path.Rd b/man/labels_path.Rd similarity index 73% rename from man/get_labels_path.Rd rename to man/labels_path.Rd index a9f0b2a..d88c40e 100644 --- a/man/get_labels_path.Rd +++ b/man/labels_path.Rd @@ -1,19 +1,19 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/birdnet_interface.R -\name{get_labels_path} -\alias{get_labels_path} -\alias{get_labels_path.birdnet_model_custom} -\alias{get_labels_path.birdnet_model_tflite} -\alias{get_labels_path.birdnet_model_protobuf} +\name{labels_path} +\alias{labels_path} +\alias{labels_path.birdnet_model_custom} +\alias{labels_path.birdnet_model_tflite} +\alias{labels_path.birdnet_model_protobuf} \title{Get Path to a Labels File} \usage{ -get_labels_path(model, ...) +labels_path(model, ...) -\method{get_labels_path}{birdnet_model_custom}(model, ...) +\method{labels_path}{birdnet_model_custom}(model, ...) -\method{get_labels_path}{birdnet_model_tflite}(model, language, ...) +\method{labels_path}{birdnet_model_tflite}(model, language, ...) -\method{get_labels_path}{birdnet_model_protobuf}(model, language, ...) +\method{labels_path}{birdnet_model_protobuf}(model, language, ...) } \arguments{ \item{model}{A BirdNET model object.} @@ -38,7 +38,7 @@ The \code{language} parameter must be one of the available languages returned by \examples{ \dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} model <- birdnet_model_tflite(version = "v2.4") -get_labels_path(model, "fr") +labels_path(model, "fr") \dontshow{\}) # examplesIf} } \seealso{ diff --git a/man/read_labels.Rd b/man/read_labels.Rd index 09e5119..7afda4c 100644 --- a/man/read_labels.Rd +++ b/man/read_labels.Rd @@ -22,10 +22,10 @@ read_labels(system.file("extdata", "species_list.txt", package = "birdnetR")) # To access all class labels that are supported in your language, # you can read in the respective label file model <- birdnet_model_tflite(version = "v2.4", language = "en_us") -labels_path <- get_labels_path(model, "fr") +labels_path <- labels_path(model, "fr") species_list <- read_labels(labels_path) head(species_list) } \seealso{ -\code{\link[=available_languages]{available_languages()}} \code{\link[=get_labels_path]{get_labels_path()}} +\code{\link[=available_languages]{available_languages()}} \code{\link[=labels_path]{labels_path()}} } diff --git a/tests/testthat/test-get_labels_path.R b/tests/testthat/test-get_labels_path.R index 53c10ea..7ea5931 100644 --- a/tests/testthat/test-get_labels_path.R +++ b/tests/testthat/test-get_labels_path.R @@ -4,29 +4,29 @@ tflite_model <- birdnet_model_tflite(version = "v2.4") protobuf_model <- birdnet_model_protobuf(version = "v2.4") -test_that("get_labels_path returns correct path for valid language", { - path <- get_labels_path(model = tflite_model, language = "en_us") +test_that("labels_path returns correct path for valid language", { + path <- labels_path(model = tflite_model, language = "en_us") expect_true(basename(path) == "en_us.txt") expect_true(file.exists(path)) - path <- get_labels_path(model = protobuf_model, language = "en_us") + path <- labels_path(model = protobuf_model, language = "en_us") expect_true(basename(path) == "en_us.txt") expect_true(file.exists(path)) }) -test_that("get_labels_path returns correct path for invalid language", { - expect_error(get_labels_path(model = tflite_model, language = "blonk")) - expect_error(get_labels_path(model = protobuf_model, language = "blonk")) +test_that("labels_path returns correct path for invalid language", { + expect_error(labels_path(model = tflite_model, language = "blonk")) + expect_error(labels_path(model = protobuf_model, language = "blonk")) }) -test_that("get_labels_path throws an error for character input", { - expect_error(get_labels_path("invalid_language")) +test_that("labels_path throws an error for character input", { + expect_error(labels_path("invalid_language")) }) -test_that("get_labels_path handles edge cases like empty string or NULL", { - expect_error(get_labels_path("")) - expect_error(get_labels_path(NULL)) +test_that("labels_path handles edge cases like empty string or NULL", { + expect_error(labels_path("")) + expect_error(labels_path(NULL)) }) -# Missing test for get_labels_path with custom model +# Missing test for labels_path with custom model From 2155802f8b43d1ba7665d7847bba920510465112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Fri, 13 Sep 2024 10:04:07 +0200 Subject: [PATCH 12/17] use a scalar logical operator in conditional expression (check if birdnet is installed) --- R/birdnet_interface.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/birdnet_interface.R b/R/birdnet_interface.R index e3feeaf..d9a35ef 100644 --- a/R/birdnet_interface.R +++ b/R/birdnet_interface.R @@ -39,7 +39,7 @@ py_builtins <- NULL error = function(e) NULL ) - if (is.null(installed_birdnet_version) | length(installed_birdnet_version) == 0) { + if (is.null(installed_birdnet_version) || length(installed_birdnet_version) == 0) { message("No version of birdnet found. To install, use `install_birdnet()`.") return() } From 6c102359b4bfea3b7a38e9189b728c2418f9d936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Fri, 13 Sep 2024 10:05:21 +0200 Subject: [PATCH 13/17] adds `--as-cran` to PAckageCheckArgs --- birdnet.Rproj | 1 + 1 file changed, 1 insertion(+) diff --git a/birdnet.Rproj b/birdnet.Rproj index 270314b..be6e327 100644 --- a/birdnet.Rproj +++ b/birdnet.Rproj @@ -18,4 +18,5 @@ StripTrailingWhitespace: Yes BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source +PackageCheckArgs: --as-cran PackageRoxygenize: rd,collate,namespace From 994c0ec7e267d93d5534a5c13270ef5e4f9d557f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Fri, 13 Sep 2024 12:00:26 +0200 Subject: [PATCH 14/17] code formatting and adding inetractive condition to examples --- R/birdnet-package.R | 1 - R/birdnet_interface.R | 100 +++++++++++------- R/helpers.R | 17 +-- R/install.R | 8 +- R/utils.R | 8 +- man/available_languages.Rd | 2 + man/birdnet_model_load.Rd | 2 + man/read_labels.Rd | 2 + .../test-predict_species_from_audio_file.R | 7 +- 9 files changed, 87 insertions(+), 60 deletions(-) diff --git a/R/birdnet-package.R b/R/birdnet-package.R index 715cb7a..bca3938 100644 --- a/R/birdnet-package.R +++ b/R/birdnet-package.R @@ -15,4 +15,3 @@ #' @importFrom reticulate use_python_version ## usethis namespace: end NULL - diff --git a/R/birdnet_interface.R b/R/birdnet_interface.R index d9a35ef..2da7d0b 100644 --- a/R/birdnet_interface.R +++ b/R/birdnet_interface.R @@ -22,7 +22,9 @@ py_builtins <- NULL { reticulate::py_list_packages() }, - error = function() NULL + error = function() { + NULL + } ) if (is.null(available_py_packages)) { @@ -36,10 +38,13 @@ py_builtins <- NULL package <- NULL subset(available_py_packages, package == "birdnet")$version }, - error = function(e) NULL + error = function(e) { + NULL + } ) - if (is.null(installed_birdnet_version) || length(installed_birdnet_version) == 0) { + if (is.null(installed_birdnet_version) || + length(installed_birdnet_version) == 0) { message("No version of birdnet found. To install, use `install_birdnet()`.") return() } @@ -108,10 +113,7 @@ new_birdnet_model <- function(x, ..., subclass = character()) { subclasse <- paste(class_name, subclass, sep = "_") # Create subclass by combining base class with user-provided subclass # Return an S3 object containing the Python model and additional attributes, with the specified class hierarchy - structure( - list("py_model" = x, ...), - class = c(subclasse, "birdnet_model") - ) + structure(list("py_model" = x, ...), class = c(subclasse, "birdnet_model")) } @@ -150,11 +152,19 @@ model_factory <- function(model_name, version, ...) { # Create a subclass for the model: model_name_version is the specific subclass of model_name subclasses <- c(version, model_name) - subclasses <- gsub(x = subclasses, pattern = "\\.", replacement = "_") + subclasses <- gsub( + x = subclasses, + pattern = "\\.", + replacement = "_" + ) # Create and return the BirdNET model object with the subclasses # passing model_version adds a list element with the version of the model. - new_birdnet_model(py_model, model_version = version, ..., subclass = subclasses) + new_birdnet_model(py_model, + model_version = version, + ..., + subclass = subclasses + ) } @@ -186,7 +196,7 @@ model_factory <- function(model_name, version, ...) { #' #' @seealso [available_languages()] [predict_species_from_audio_file()] [predict_species_at_location_and_time()] #' @return A BirdNET model object. -#' @examples +#' @examplesIf interactive() #' # Create a TFLite BirdNET model with 2 threads and English (US) language #' birdnet_model <- birdnet_model_tflite(version = "v2.4", language = "en_us", tflite_num_threads = 2) #' @name birdnet_model_load @@ -199,8 +209,7 @@ birdnet_model_tflite <- function(version = "v2.4", language = "en_us", tflite_num_threads = NULL) { # Validate tflite_num_threads: must be NULL or numeric (will be coerced to integer) - if (!is.null(tflite_num_threads) && - !is.numeric(tflite_num_threads)) { + if (!is.null(tflite_num_threads) && !is.numeric(tflite_num_threads)) { stop("tflite_num_threads must be a numeric value or NULL.") } @@ -229,8 +238,7 @@ birdnet_model_custom <- function(version = "v2.4", classifier_name, tflite_num_threads = NULL) { # Validate tflite_num_threads: must be NULL or numeric (will be coerced to integer) - if (!is.null(tflite_num_threads) && - !is.numeric(tflite_num_threads)) { + if (!is.null(tflite_num_threads) && !is.numeric(tflite_num_threads)) { stop("tflite_num_threads must be a numeric value or NULL.") } @@ -252,7 +260,13 @@ birdnet_model_custom <- function(version = "v2.4", # Because classifier_folder and classifier_name need to be positional and cannot be named, we need to rename the # list elements - names(model) <- c("py_model", "model_version", "classifier_folder", "classifier_name", "tflite_num_threads") + names(model) <- c( + "py_model", + "model_version", + "classifier_folder", + "classifier_name", + "tflite_num_threads" + ) model } @@ -262,8 +276,7 @@ birdnet_model_meta <- function(version = "v2.4", language = "en_us", tflite_num_threads = NULL) { # Validate tflite_num_threads: must be NULL or numeric (will be coerced to integer) - if (!is.null(tflite_num_threads) && - !is.numeric(tflite_num_threads)) { + if (!is.null(tflite_num_threads) && !is.numeric(tflite_num_threads)) { stop("tflite_num_threads must be a numeric value or NULL.") } @@ -315,12 +328,20 @@ birdnet_model_protobuf <- function(version = "v2.4", #' @export #' @note This function is kept for backward compatibility. Please use \code{birdnet_model_tflite()} instead. init_model <- - function(tflite_num_threads = NULL, language = "en_us") { + function(tflite_num_threads = NULL, + language = "en_us") { # Deprecation warning - warning("`init_model()` is deprecated. Please use `birdnet_model_tflite()` instead.", call. = FALSE) + warning( + "`init_model()` is deprecated. Please use `birdnet_model_tflite()` instead.", + call. = FALSE + ) # Call the updated model initialization function - birdnet_model_tflite(version = "v2.4", language = language, tflite_num_threads = tflite_num_threads) + birdnet_model_tflite( + version = "v2.4", + language = language, + tflite_num_threads = tflite_num_threads + ) } @@ -331,7 +352,7 @@ init_model <- #' @param version character. The version of BirdNET to use (default is "v2.4", no other versions are currently supported). #' #' @return A sorted character vector containing the available language codes. -#' @examples +#' @examplesIf interactive() #' available_languages("v2.4") #' @export available_languages <- function(version) { @@ -381,7 +402,10 @@ labels_path <- function(model, ...) { #' @examplesIf interactive() #' model <- birdnet_model_tflite(version = "v2.4", language = "en_us") #' language_path <- get_language_path(model, "en_us", "downloader_tflite", "TFLite") -get_language_path <- function(model, language, downloader_key, subfolder) { +get_language_path <- function(model, + language, + downloader_key, + subfolder) { # Validate that the language is available for the given model version langs <- available_languages(model$model_version) @@ -407,7 +431,10 @@ get_language_path <- function(model, language, downloader_key, subfolder) { #' @export #' @method labels_path birdnet_model_custom labels_path.birdnet_model_custom <- function(model, ...) { - file.path(model$classifier_folder, paste0(model$classifier_name, ".txt")) + file.path( + model$classifier_folder, + paste0(model$classifier_name, ".txt") + ) } @@ -435,7 +462,7 @@ labels_path.birdnet_model_protobuf <- function(model, language, ...) { #' @return A vector with class labels e.g. c("Cyanocitta cristata_Blue Jay", "Zenaida macroura_Mourning Dove") #' @export #' @seealso [available_languages()] [labels_path()] -#' @examples +#' @examplesIf interactive() #' # Read a custom species file #' read_labels(system.file("extdata", "species_list.txt", package = "birdnetR")) #' @@ -507,19 +534,18 @@ predict_species_from_audio_file <- function(model, #' @rdname predict_species_from_audio_file #' @method predict_species_from_audio_file birdnet_model #' @export -predict_species_from_audio_file.birdnet_model <- function( - model, - audio_file, - min_confidence = 0.1, - batch_size = 1L, - chunk_overlap_s = 0, - use_bandpass = TRUE, - bandpass_fmin = 0L, - bandpass_fmax = 15000L, - apply_sigmoid = TRUE, - sigmoid_sensitivity = 1, - filter_species = NULL, - keep_empty = TRUE) { +predict_species_from_audio_file.birdnet_model <- function(model, + audio_file, + min_confidence = 0.1, + batch_size = 1L, + chunk_overlap_s = 0, + use_bandpass = TRUE, + bandpass_fmin = 0L, + bandpass_fmax = 15000L, + apply_sigmoid = TRUE, + sigmoid_sensitivity = 1, + filter_species = NULL, + keep_empty = TRUE) { # Check argument types for better error messages stopifnot(is.list(model)) stopifnot(is.character(audio_file)) diff --git a/R/helpers.R b/R/helpers.R index 0d6f61c..a5996f9 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -55,19 +55,20 @@ get_top_prediction <- function(data, filter = NULL) { if (!is.data.frame(data)) { stop("The 'data' argument must be a data frame.") } - required_columns <- c("start", - "end", - "scientific_name", - "common_name", - "confidence") + required_columns <- c( + "start", + "end", + "scientific_name", + "common_name", + "confidence" + ) if (!all(required_columns %in% names(data))) { stop(paste( "Data frame must contain the following columns:", paste(required_columns, collapse = ", ") )) } - if (!is.null(filter) && - (!is.list(filter) || !all(c("start", "end") %in% names(filter)))) { + if (!is.null(filter) && (!is.list(filter) || !all(c("start", "end") %in% names(filter)))) { stop("The 'filter' must be a list containing 'start' and 'end'.") } @@ -79,7 +80,7 @@ get_top_prediction <- function(data, filter = NULL) { if (!is.null(filter)) { # Apply the filter condition if specified data <- data[data$start == filter$start & - data$end == filter$end, ] + data$end == filter$end, ] } # Split data by start and end columns to find the max confidence in each interval diff --git a/R/install.R b/R/install.R index 69f3d97..6f3f205 100644 --- a/R/install.R +++ b/R/install.R @@ -31,11 +31,9 @@ #' #' @export install_birdnet <- function( - ..., - envname = "r-birdnet", - new_env = identical(envname, "r-birdnet") -) { - + ..., + envname = "r-birdnet", + new_env = identical(envname, "r-birdnet")) { # Try to use python 3.11. the request is taken as a hint only, and scanning for other versions will still proceed reticulate::use_python_version(.suggested_python_version(), required = FALSE) diff --git a/R/utils.R b/R/utils.R index 55172f5..0cd5e01 100644 --- a/R/utils.R +++ b/R/utils.R @@ -30,7 +30,7 @@ predictions_to_df <- function(predictions, keep_empty = FALSE) { # Fill empty elements with NA if keep_empty is TRUE predictions[i][[1]] <- list("NA_NA" = NA_real_) } else { - return() # Skip this element if keep_empty is FALSE + return() # Skip this element if keep_empty is FALSE } } # Convert the current prediction element to a data frame @@ -119,10 +119,10 @@ is_valid_species_list <- function(obj) { # Check if the object is a non-empty list where each element is a single character string is_list_single_elements <- is.list(obj) && length(obj) > 0 && - all(sapply(obj, function(x) - is.character(x) && length(x) == 1 && length(x) != 0)) + all(sapply(obj, function(x) { + is.character(x) && length(x) == 1 && length(x) != 0 + })) # Return TRUE if either condition is met return(is_vector || is_list_single_elements) } - diff --git a/man/available_languages.Rd b/man/available_languages.Rd index f24232a..93bbe83 100644 --- a/man/available_languages.Rd +++ b/man/available_languages.Rd @@ -16,5 +16,7 @@ A sorted character vector containing the available language codes. Retrieve the available languages supported by a specific version of BirdNET. } \examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} available_languages("v2.4") +\dontshow{\}) # examplesIf} } diff --git a/man/birdnet_model_load.Rd b/man/birdnet_model_load.Rd index 397dee4..b13a479 100644 --- a/man/birdnet_model_load.Rd +++ b/man/birdnet_model_load.Rd @@ -72,8 +72,10 @@ The \code{\link[=birdnet_model_meta]{birdnet_model_meta()}} model can be used to Currently, all models can only be executed on the CPU. GPU support is not yet available. } \examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} # Create a TFLite BirdNET model with 2 threads and English (US) language birdnet_model <- birdnet_model_tflite(version = "v2.4", language = "en_us", tflite_num_threads = 2) +\dontshow{\}) # examplesIf} } \seealso{ \code{\link[=available_languages]{available_languages()}} \code{\link[=predict_species_from_audio_file]{predict_species_from_audio_file()}} \code{\link[=predict_species_at_location_and_time]{predict_species_at_location_and_time()}} diff --git a/man/read_labels.Rd b/man/read_labels.Rd index 7afda4c..490db35 100644 --- a/man/read_labels.Rd +++ b/man/read_labels.Rd @@ -16,6 +16,7 @@ A vector with class labels e.g. c("Cyanocitta cristata_Blue Jay", "Zenaida macro This is a convenience function to read species labels from a file. } \examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} # Read a custom species file read_labels(system.file("extdata", "species_list.txt", package = "birdnetR")) @@ -25,6 +26,7 @@ model <- birdnet_model_tflite(version = "v2.4", language = "en_us") labels_path <- labels_path(model, "fr") species_list <- read_labels(labels_path) head(species_list) +\dontshow{\}) # examplesIf} } \seealso{ \code{\link[=available_languages]{available_languages()}} \code{\link[=labels_path]{labels_path()}} diff --git a/tests/testthat/test-predict_species_from_audio_file.R b/tests/testthat/test-predict_species_from_audio_file.R index 3f4c17a..9bf34a5 100644 --- a/tests/testthat/test-predict_species_from_audio_file.R +++ b/tests/testthat/test-predict_species_from_audio_file.R @@ -20,7 +20,7 @@ test_that("birdnet_model_protobuf works", { test_that("birdnet_model structure is correct", { expect_s3_class(tflite_model, c("birdnet_model_tflite")) - expect_s3_class(tflite_model$py_model, c("python.builtin.object", "birdnet.models.v2m4.model_v2m4_tflite.AudioModelV2M4TFLite" )) + expect_s3_class(tflite_model$py_model, c("python.builtin.object", "birdnet.models.v2m4.model_v2m4_tflite.AudioModelV2M4TFLite")) expect_equal(tflite_model$model_version, "v2.4") expect_s3_class(protobuf_model, c("birdnet_model_protobuf")) @@ -75,7 +75,6 @@ test_that("predict_species applies sigmoid function correctly", { }) test_that("predict_species respects minimum confidence threshold", { - # Lower threshold predictions <- predict_species_from_audio_file(tflite_model, audio_file, min_confidence = 0.05) expect_true(!is.null(predictions)) @@ -90,7 +89,6 @@ test_that("predict_species respects minimum confidence threshold", { }) test_that("predict_species applies overlap", { - # Lower threshold predictions <- predict_species_from_audio_file(tflite_model, audio_file, chunk_overlap_s = 1) expect_true(!is.null(predictions)) @@ -114,10 +112,9 @@ test_that("predict_species keeps empty intervals when specified", { }) test_that("predict_species handles invalid inputs gracefully", { - # Invalid species list type expect_error(predict_species_from_audio_file(tflite_model, audio_file, filter_species = 123)) - expect_error(predict_species_from_audio_file(tflite_model, audio_file,filter_species = list(c("A", "B")))) + expect_error(predict_species_from_audio_file(tflite_model, audio_file, filter_species = list(c("A", "B")))) # Invalid bandpass frequencies expect_error(predict_species_from_audio_file(tflite_model, audio_file, bandpass_fmin = -100L)) From e07f44522a36081cee49f9179e4a2b3fc5ee1fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Fri, 13 Sep 2024 13:14:55 +0200 Subject: [PATCH 15/17] update Get Started guide --- vignettes/birdnetR.Rmd | 163 ++++++++++++++++++++++++++++++----------- 1 file changed, 122 insertions(+), 41 deletions(-) diff --git a/vignettes/birdnetR.Rmd b/vignettes/birdnetR.Rmd index 5b25d48..3a0295d 100644 --- a/vignettes/birdnetR.Rmd +++ b/vignettes/birdnetR.Rmd @@ -19,7 +19,7 @@ knitr::opts_chunk$set( library(birdnetR) ``` -The birdnetR package provides a comprehensive interface for utilizing the BirdNET Python package within R. This guide will walk you through the basic steps of setting up the package, initializing models, and using various functions to analyze audio files for (bird) species identification. +The `birdnetR` package provides a comprehensive interface for utilizing the `birdnet` Python package within R. This guide will walk you through the basic steps of setting up the package, initializing models, and using various functions to analyze audio files for (bird) species identification. ## Installation @@ -49,7 +49,6 @@ Next, install `birdnet`, which will set up a Python virtual environment named `r ```{r load_and_install_birdnet} library(birdnetR) install_birdnet() - ``` @@ -81,7 +80,6 @@ library(birdnetR) path_venv <- "/path/to/existing/venv" install_birdnet(envname = path_venv) reticulate::use_virtualenv(path_venv) - ``` @@ -90,34 +88,87 @@ If you prefer to store your virtual environment in the project folder, `reticula ## Usage +### Initialize a BirdNET model +To begin using the BirdNET model, it must first be initialized. During this step, the required model is downloaded if necessary, loaded into memory, and prepared for making predictions. + +Several model variations are available, including the TensorFlow Lite model, which is smaller and more lightweight, and the Protobuf model, which is larger but capable of running on GPU hardware for faster performance. + +You can also load a custom model if one is available. For information on training custom models, please refer to the BirdNET-Analyzer repository. + +```{r init_model} +# The models are defined using the birdnet_model_* family of functions. +# See ?birdnet_model_load for more details. + +# Initialize the TensorFlow Lite model +birdnet_model_tflite("v2.4") + +# Initialize the Protobuf model +birdnet_model_protobuf("v2.4") + + +``` + +To load a custom model, provide the path to the folder containing the model files and the classifier name. +Custom classifiers are still based on a specific version of the BirdNET model, so you need to specify the version as well. + +```{r init_custom_model} +classifier_folder <- "/path/to/custom/model" +classifier_name <- "Custom_Classifier" + +birdnet_model_custom("v2.4", classifier_folder = classifier_folder, classifier_name = classifier_name) + +``` + + + ### Identify species in an audio file -Using BirdNET, you can identify bird species within an audio file. The function returns predictions for every 3-second snippet in the file that exceed the specified `min_confidence` threshold. Each row in the resulting data frame represents a single prediction for a specific 3-second interval. +With BirdNET, you can identify bird species present in an audio file. The function returns predictions for each 3-second snippet of the audio that exceeds the specified min_confidence threshold. Each row in the resulting data frame represents a single prediction for a specific 3-second interval. ```{r species_in_audio} -# Load the package library(birdnetR) -# Initialize the BirdNET model -model <- init_model() +# Initialize the TFLite BirdNET Model +model <- birdnet_model_tflite("v2.4") -# Path to the exemplary audio file (replace with your own file path) +# Path to an example audio file (replace with your own file path) audio_path <- system.file("extdata", "soundscape.wav", package = "birdnetR") -# Predict species within the audio file -predict_species(model, audio_path, min_confidence = 0.3, keep_empty = TRUE) +# Predict species in the audio file +predictions <- predict_species_from_audio_file(model, audio_path, min_confidence = 0.3, keep_empty = FALSE) + +# Example output: +# start end scientific_name common_name confidence +# 0 3 Poecile atricapillus Black-capped Chickadee 0.8140557 +# 3 6 Poecile atricapillus Black-capped Chickadee 0.3082857 +# 9 12 Haemorhous mexicanus House Finch 0.6393781 +# 18 21 Cyanocitta cristata Blue Jay 0.4352708 +# 18 21 Clamator coromandus Chestnut-winged Cuckoo 0.3225890 +# 21 24 Cyanocitta cristata Blue Jay 0.3290859 +# ... ``` -If there are multiple predictions above the confidence threshold within the same time interval, you will see multiple rows for that interval. To filter and keep only the most probable prediction per interval, you can use the convenience function provided in the package. +If there are multiple predictions above the confidence threshold for the same time interval, you will see multiple rows for that interval. To keep only the most probable prediction per interval, you can use the package's convenience function. ```{r top_predictions} +# Get the top prediction for each interval get_top_prediction(predictions) +# Example output: +# start end scientific_name common_name confidence +# 0 3 Poecile atricapillus Black-capped Chickadee 0.8140557 +# 3 6 Poecile atricapillus Black-capped Chickadee 0.3082857 +# 9 12 Haemorhous mexicanus House Finch 0.6393781 +# 18 21 Cyanocitta cristata Blue Jay 0.4352708 +# 21 24 Cyanocitta cristata Blue Jay 0.3290859 + +# Note: Fewer rows appear for the interval 18-21 as only the top prediction is retained. + ``` ### Using a custom species list -You may not always need to identify all 6,000+ species available in the model. To focus on species relevant to your project, you can use a custom species list containing only the necessary class labels. +In many cases, you may not need to identify all 6,000+ species available in the model. To focus on species relevant to your project, you can use a custom species list containing only the necessary class labels. Providing a custom species list will limit the output to that set of species. Class labels follow a specific format, consisting of the scientific name and the common name, separated by an underscore, like this: ```{r class_label_example} @@ -126,66 +177,96 @@ Class labels follow a specific format, consisting of the scientific name and the ``` -To create a custom species list, ensure each class label is on a separate line in a `.txt` file. You can refer to the example included in this package or check out the full list of species that BirdNET was trained on. +To create a custom species list, ensure each class label is placed on a separate line in a .txt file. You can refer to the example included in this package or consult the full list of species that BirdNET was trained on. The exact labels are model-specific and can be retrieved using the `labels_path` function. The `read_labels` function can conveniently load the labels from the file. ```{r label_file_paths} -# Path to the label file including all BirdNET classes -# use this file as a template to create your custom species list but don't change it. -get_labels_path(language = "en_us") - -# Path to the example custom species list with a reduced number of class -system.file("extdata", "species_list.txt", package = "birdnetR") +# Retrieve the path to the full list of BirdNET classes. +# Use this as a template for creating your custom species list, but don't modify this file directly. +labels_path(model, language = "en_us") +# /.../birdnet/models/v2.4/TFLite/labels/en_us.txt" + +# Path to the example custom species list with a reduced number of species +custom_species_list <- system.file("extdata", "species_list.txt", package = "birdnetR") +read_labels(custom_species_list) + +# [1] "Accipiter cooperii_Cooper's Hawk" "Agelaius phoeniceus_Red-winged Blackbird" +# [3] "Anas platyrhynchos_Mallard" "Anas rubripes_American Black Duck" +# [5] "Ardea herodias_Great Blue Heron" "Baeolophus bicolor_Tufted Titmouse" +# [7] "Branta canadensis_Canada Goose" "Bucephala albeola_Bufflehead" +# [9] "Bucephala clangula_Common Goldeneye" "Buteo jamaicensis_Red-tailed Hawk" +# ... ``` -```{r use_custom_species_list} -# read in your custom species list -species_list_file <- system.file("extdata", "species_list.txt", package = "birdnetR") -custom_species_list <- get_species_from_file(species_list_file) +To use the custom species list, pass it as an argument to the `predict_species_from_audio_file` function. Since this is just a character vector, you can also pass the vector directly to the function. -# Predict using the provided class labels only -predict_species(model, audio_path, filter_species = custom_species_list, min_confidence = 0.3, keep_empty = FALSE) +```{r use_custom_species_list} +predict_species_from_audio_file(model, audio_path, filter_species = c("Cyanocitta cristata_Blue Jay", "Junco hyemalis_Dark-eyed Junco"), min_confidence = 0.3, keep_empty = FALSE) -# It is the same to supply a vector of class labels -predict_species(model, audio_path, filter_species = c("Cyanocitta cristata_Blue Jay", "Junco hyemalis_Dark-eyed Junco"), min_confidence = 0.3, keep_empty = FALSE) +# Example output: +# start end scientific_name common_name confidence +# 18 21 Cyanocitta cristata Blue Jay 0.4352708 +# 21 24 Cyanocitta cristata Blue Jay 0.3290859 +# 33 36 Junco hyemalis Dark-eyed Junco 0.4590625 +# 36 39 Junco hyemalis Dark-eyed Junco 0.3536855 +# 42 45 Junco hyemalis Dark-eyed Junco 0.7375432 ``` -### Predict species occurence with the meta model -BirdNET includes a Meta Model that can predict the occurrence of bird species at a specific location and time of the year. This function returns a data frame containing class labels and their corresponding confidence values, which indicate the likelihood of species presence. These labels can be used to create a custom species list for further analysis. +### Predict species occurence with the meta model +BirdNET includes a Meta Model that predicts the likelihood of bird species occurrence at a specific location and time of year. This function returns a data frame containing class labels and corresponding confidence values, which indicate the probability of species presence. These labels can also be used to create a custom species list for further analysis. ```{r use_meta_model} -# predict species occurrence in Ithaca, NY -predicted_species <- predict_species_at_location_and_time(model, latitude = 42.5, longitude = -76.45, week = 4) +# load the meta model +meta_model <- birdnet_model_meta("v2.4") + +# predict species occurrence in Ithaca, NY in week 4 of the year +predict_species_at_location_and_time(meta_model, latitude = 42.5, longitude = -76.45, week = 4) + +# Example output: +# label confidence +# Cyanocitta cristata_Blue Jay 0.92886776 +# Poecile atricapillus_Black-capped Chickadee 0.90332001 +# Sitta carolinensis_White-breasted Nuthatch 0.83232993 +# Cardinalis cardinalis_Northern Cardinal 0.82705086 +# Junco hyemalis_Dark-eyed Junco 0.82440305 +# Zenaida macroura_Mourning Dove 0.80619872 +# Corvus brachyrhynchos_American Crow 0.80580002 +# Dryobates pubescens_Downy Woodpecker 0.79495054 +# Spinus tristis_American Goldfinch 0.72782934 +# Baeolophus bicolor_Tufted Titmouse 0.63683629 -# Predict using the predicted class labels only -predict_species(model, audio_path, filter_species = predicted_species$label, min_confidence = 0.3, keep_empty = FALSE) ``` -For more detailed information, refer to the help file: `?predict_species_at_location_and_time`. +For more detailed information on how the Meta Model works, refer to the help file: `?predict_species_at_location_and_time`. ### Translating common species names The birdnetR package allows you to translate common bird species names into several different languages. To check which languages are supported, you can use the following command: ```{r languages} -available_languages() - +# supply the version of the BirdNET model you are using +available_languages("v2.4") ``` To output the common names in your preferred language, initialize the model with the language parameter set to your desired language code: ```{r} -init_model(language = "fr") +birdnet_model_tflite("v2.4", language = "fr") ``` If you want to view the class labels in a specific language, you can retrieve and inspect them using these commands: ```{r labels_language} - -labels_path_lang <- get_labels_path(language = "fr") -get_species_from_file(labels_path_lang) - +labels_path_lang <- labels_path(model, language = "fr") +read_labels(labels_path_lang) + +# Example output: +# [1] "Abroscopus albogularis_Bouscarle à moustaches" "Abroscopus schisticeps_Bouscarle à face noire" "Abroscopus superciliaris_Bouscarle à sourcils blancs" +# [4] "Aburria aburri_Pénélope aburri" "Acanthagenys rufogularis_Méliphage à bavette" "Acanthidops bairdi_Bec-en-cheville gris" +# [7] "Acanthis cabaret_Sizerin cabaret" "Acanthis flammea_Sizerin flammé" "Acanthis hornemanni_Sizerin blanchâtre" +# [10] "Acanthisitta chloris_Xénique grimpeur" "Acanthiza apicalis_Acanthize troglodyte" "Acanthiza chrysorrhoa_Acanthize à croupion jaune" +# [13] "Acanthiza ewingii_Acanthize de Tasmanie" "Acanthiza inornata_Acanthize sobre" "Acanthiza lineata_Acanthize ridé" ``` From 62ac2063473dec1c8a85d768a62484d055f29c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Fri, 13 Sep 2024 14:52:45 +0200 Subject: [PATCH 16/17] =?UTF-8?q?deprecate=20=C3=ACnit=5Fmodel()`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- R/birdnetR-deprecated.R | 12 ++++++++++++ R/birdnet_interface.R | 29 +++++++++++------------------ man/birdnetR-deprecated.Rd | 17 +++++++++++++++++ man/init_model.Rd | 6 +++--- 4 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 R/birdnetR-deprecated.R create mode 100644 man/birdnetR-deprecated.Rd diff --git a/R/birdnetR-deprecated.R b/R/birdnetR-deprecated.R new file mode 100644 index 0000000..41a99a9 --- /dev/null +++ b/R/birdnetR-deprecated.R @@ -0,0 +1,12 @@ +#' Deprecated Functions in the birdnetR Package +#' +#' These functions are deprecated and will be removed in future versions of the birdnetR package. +#' Please use the alternatives listed below. +#' +#' @name birdnetR-deprecated +#' @keywords internal +#' @section Deprecated functions: +#' \describe{ +#' \item{\code{\link{init_model}}}{This function is deprecated. Use \code{\link{birdnet_model_tflite}} instead.} +#' } +NULL diff --git a/R/birdnet_interface.R b/R/birdnet_interface.R index 2da7d0b..f7a92b0 100644 --- a/R/birdnet_interface.R +++ b/R/birdnet_interface.R @@ -317,32 +317,25 @@ birdnet_model_protobuf <- function(version = "v2.4", #' Initialize the BirdNET Model (Deprecated) #' #' This function initializes the BirdNET model (v2.4). It is kept for backward compatibility and is deprecated. -#' Use \code{birdnet_model_tflite()} instead for model initialization. +#' Use [birdnet_model_tflite()] instead for model initialization. #' #' @param tflite_num_threads integer. The number of threads to use for TensorFlow Lite operations. If NULL (default), the default threading behavior will be used. #' Will be coerced to an integer if possible. #' @param language Character string specifying the language code to use for the model's text processing. The language must be one of the available languages supported by the BirdNET model. #' @note The `language` parameter must be one of the available languages returned by `available_languages()`. -#' @seealso [available_languages()] +#' @seealso [available_languages()] [birdnet_model_tflite()] #' @return An instance of the BirdNET model. #' @export -#' @note This function is kept for backward compatibility. Please use \code{birdnet_model_tflite()} instead. -init_model <- - function(tflite_num_threads = NULL, - language = "en_us") { - # Deprecation warning - warning( - "`init_model()` is deprecated. Please use `birdnet_model_tflite()` instead.", - call. = FALSE - ) +#' @note This function is kept for backward compatibility. Please use [birdnet_model_tflite()] instead. +init_model <- function(tflite_num_threads = NULL, language = "en_us") { + .Deprecated("birdnet_model_tflite", package = "birdnetR") + birdnet_model_tflite( + version = "v2.4", + language = language, + tflite_num_threads = tflite_num_threads + ) +} - # Call the updated model initialization function - birdnet_model_tflite( - version = "v2.4", - language = language, - tflite_num_threads = tflite_num_threads - ) - } #' Get Available Languages for BirdNET Model diff --git a/man/birdnetR-deprecated.Rd b/man/birdnetR-deprecated.Rd new file mode 100644 index 0000000..38a2a87 --- /dev/null +++ b/man/birdnetR-deprecated.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/birdnetR-deprecated.R +\name{birdnetR-deprecated} +\alias{birdnetR-deprecated} +\title{Deprecated Functions in the birdnetR Package} +\description{ +These functions are deprecated and will be removed in future versions of the birdnetR package. +Please use the alternatives listed below. +} +\section{Deprecated functions}{ + +\describe{ +\item{\code{\link{init_model}}}{This function is deprecated. Use \code{\link{birdnet_model_tflite}} instead.} +} +} + +\keyword{internal} diff --git a/man/init_model.Rd b/man/init_model.Rd index 65d29d5..10d64c8 100644 --- a/man/init_model.Rd +++ b/man/init_model.Rd @@ -17,13 +17,13 @@ An instance of the BirdNET model. } \description{ This function initializes the BirdNET model (v2.4). It is kept for backward compatibility and is deprecated. -Use \code{birdnet_model_tflite()} instead for model initialization. +Use \code{\link[=birdnet_model_tflite]{birdnet_model_tflite()}} instead for model initialization. } \note{ The \code{language} parameter must be one of the available languages returned by \code{available_languages()}. -This function is kept for backward compatibility. Please use \code{birdnet_model_tflite()} instead. +This function is kept for backward compatibility. Please use \code{\link[=birdnet_model_tflite]{birdnet_model_tflite()}} instead. } \seealso{ -\code{\link[=available_languages]{available_languages()}} +\code{\link[=available_languages]{available_languages()}} \code{\link[=birdnet_model_tflite]{birdnet_model_tflite()}} } From 7cc5507d34e8a6b53b1d8614378974addfedec98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCnther?= Date: Fri, 13 Sep 2024 14:52:55 +0200 Subject: [PATCH 17/17] add news --- NEWS.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/NEWS.md b/NEWS.md index 97a39f7..a2a4ddb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,20 @@ +# birdnetR 0.2.0 + +This update brings significant changes and improvements, including support for loading pre-existing and custom-trained models, aligning the package with birdnet `0.1.6`. + +#### breaking changes: +- The `init_model()` function is now **deprecated** and will be removed in the next version. Please use the `birdnet_model_*` function family for model initialization. +- `available_languages()` **update**: A new argument has been added to `available_languages()` to specify the BirdNET version, making it more flexible for different model versions. +- **Renaming** `get_labels_path` to `labels_path()`. It now requires a model object as its first argument. +- `predict_species()` was **renamed** to `predict_species_from_audio_file()` +- `predict_species_at_location_and_time()` was **changed** to requirer a model object as first argument. + +#### New features: + * **Support for Custom Models:** You can now load custom-trained models + * **A new set of functions** (`birdnet_model_*`) to load pre-existing and custom-trained models. These functions offer a more flexible approach to model loading. See `?birdnet_model_load` for more details. + * **S3 Object-Oriented System:** The models are now implemented as S3 classes, and most of the functionality related to these models is provided through methods. This update makes the API cleaner and more consistent, and allows for better extensibility in future versions. + + # birdnetR 0.1.2 Uses `birdnet v0.1.6` under the hood to fix an issue when downloading models.