diff --git a/.gitignore b/.gitignore index 9a01975..ab1e3e4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ _other/ inst/doc docs +dev/ diff --git a/DESCRIPTION b/DESCRIPTION index 07ed6fb..e64fa79 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: birdnetR Type: Package Title: Identifying bird species by their sounds -Version: 0.1.1 +Version: 0.2.0 Authors@R: c( person("Felix", "Günther", email = "felix.guenther@informatik.tu-chemnitz.de", role = c("cre")), person("Stefan", "Kahl", email = "stefan.kahl@cornell.edu", role = c("aut")), diff --git a/NAMESPACE b/NAMESPACE index 94e9cb2..5ebe7e0 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,13 +1,22 @@ # Generated by roxygen2: do not edit by hand +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) -export(get_labels_path) -export(get_species_from_file) +export(birdnet_model_custom) +export(birdnet_model_meta) +export(birdnet_model_protobuf) +export(birdnet_model_tflite) export(get_top_prediction) export(init_model) export(install_birdnet) -export(predict_species) +export(labels_path) 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/NEWS.md b/NEWS.md index e3b8d5f..a2a4ddb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,25 @@ +# 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. +No new functionality has yet been implemented. + # birdnetR 0.1.1 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/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 cf3908f..31be883 100644 --- a/R/birdnet_interface.R +++ b/R/birdnet_interface.R @@ -3,22 +3,29 @@ # Import the necessary Python modules layzily in .onLoad py_birdnet_models <- NULL py_birdnet_utils <- NULL +py_birdnet_audio_based_prediction <- NULL +py_birdnet_location_based_prediction <- NULL +py_birdnet_types <- NULL 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()`.") @@ -27,12 +34,17 @@ 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 + 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() } @@ -49,8 +61,6 @@ py_builtins <- NULL } - - #' Initialize birdnetR Package #' #' Sets up the Python environment and imports required modules when the birdnetR package is loaded. @@ -68,78 +78,371 @@ py_builtins <- NULL delay_load = list(before_load = function() .check_birdnet_version()) ) py_birdnet_utils <<- reticulate::import("birdnet.utils", delay_load = TRUE) + py_birdnet_audio_based_prediction <<- reticulate::import("birdnet.audio_based_prediction", delay_load = TRUE) + 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 -#' Get Available Languages for BirdNET Model + 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")) +} + + +#' 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. -#' @examples -#' available_languages() +#' @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. +#' @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 +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 + } + + # 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.") } - sort(py_builtins$list(py_birdnet_models$model_v2m4$AVAILABLE_LANGUAGES)) + + # 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 [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()] +#' @seealso [available_languages()] [birdnet_model_tflite()] #' @return An instance of the BirdNET model. #' @export -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$ModelV2M4(tflite_num_threads = tflite_num_threads, language = language) - return(model) - } +#' @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 + ) +} + + + +#' 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. +#' @examplesIf interactive() +#' 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 BirdNET Labels File for a Specified Language +#' 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") +#' 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()] [read_labels()] #' @export -get_labels_path <- function(language) { - if (!(language %in% available_languages())) { - stop(paste( - "`language` must be one of", - paste(available_languages(), collapse = ", ") - )) +labels_path <- function(model, ...) { + UseMethod("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_birdnet_utils$get_birdnet_app_data_folder() - downloader <- py_birdnet_models$model_v2m4$Downloader(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 labels_path +#' @description For a custom model, the path of the custom labels file is returned. +#' @export +#' @method labels_path birdnet_model_custom +labels_path.birdnet_model_custom <- function(model, ...) { + file.path( + model$classifier_folder, + paste0(model$classifier_name, ".txt") + ) +} + + +#' @rdname labels_path +#' @export +#' @method labels_path birdnet_model_tflite +labels_path.birdnet_model_tflite <- function(model, language, ...) { + get_language_path(model, language, "downloader_tflite", "TFLite") +} + +#' @rdname labels_path +#' @export +#' @method labels_path birdnet_model_protobuf +labels_path.birdnet_model_protobuf <- function(model, language, ...) { + get_language_path(model, language, "downloader_protobuf", "Protobuf") } @@ -151,64 +454,93 @@ get_labels_path <- function(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()] -#' @examples +#' @seealso [available_languages()] [labels_path()] +#' @examplesIf interactive() #' # 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 -#' labels_path <- get_labels_path("fr") -#' species_list <- get_species_from_file(labels_path) +#' 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) -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 } -#' 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 [`read_labels()`] for more details on species filtering. #' @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 - stopifnot(inherits(model, "birdnet.models.model_v2m4.ModelV2M4")) +#' @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_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)) stopifnot(is.numeric(min_confidence)) stopifnot(is.integer(batch_size)) @@ -218,25 +550,25 @@ 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) - predictions <- model$predict_species_within_audio_file( + # Main function logic + predictions_gen <- py_birdnet_audio_based_prediction$predict_species_within_audio_file( audio_file, min_confidence = min_confidence, batch_size = batch_size, @@ -246,11 +578,16 @@ predict_species <- function(model, bandpass_fmax = bandpass_fmax, apply_sigmoid = apply_sigmoid, sigmoid_sensitivity = sigmoid_sensitivity, - filter_species = filter_species + species_filter = filter_species, + 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. @@ -262,7 +599,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. @@ -270,23 +607,37 @@ 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(inherits(model, "birdnet.models.model_v2m4.ModelV2M4")) + UseMethod("predict_species_at_location_and_time") +} - predictions <- model$predict_species_at_location_and_time(latitude, +#' @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 + min_confidence = min_confidence, + custom_model = model$py_model ) + data.frame( label = names(predictions), confidence = unlist(predictions), 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 0ece3fb..6f3f205 100644 --- a/R/install.R +++ b/R/install.R @@ -6,7 +6,7 @@ #' @return A string representing the required BirdNET version. #' @keywords internal .required_birdnet_version <- function() { - "0.1.1" + "0.1.6" } #' Get the Suggested Python Version @@ -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/module_map.R b/R/module_map.R new file mode 100644 index 0000000..c03bef5 --- /dev/null +++ b/R/module_map.R @@ -0,0 +1,89 @@ +#' 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" = 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" = 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 an element from a module map regardless of nesting level +#' +#' 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 ... A sequence of keys that represent the path to the desired element in the module map. +#' +#' @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") +#' 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 + + # 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(element) +} + + +#' 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)) + } + ) +} 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/README.md b/README.md index 7edea44..7dc3678 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,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 @@ -75,14 +75,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) 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 diff --git a/man/available_languages.Rd b/man/available_languages.Rd index 43db9d4..93bbe83 100644 --- a/man/available_languages.Rd +++ b/man/available_languages.Rd @@ -4,14 +4,19 @@ \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() +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +available_languages("v2.4") +\dontshow{\}) # examplesIf} } 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/birdnet_model_load.Rd b/man/birdnet_model_load.Rd new file mode 100644 index 0000000..b13a479 --- /dev/null +++ b/man/birdnet_model_load.Rd @@ -0,0 +1,82 @@ +% 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{ +\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/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 deleted file mode 100644 index 348054b..0000000 --- a/man/get_labels_path.Rd +++ /dev/null @@ -1,28 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% 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} -\usage{ -get_labels_path(language) -} -\arguments{ -\item{language}{A character string specifying the language code for which the labels path is requested. -The language must be one of the available languages supported by the BirdNET model.} -} -\value{ -A character string representing the file path to the labels file for the specified language. -} -\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. -} -\note{ -The \code{language} parameter must be one of the available languages returned by \code{available_languages()}. -} -\examples{ -get_labels_path("en_us") -} -\seealso{ -\code{\link[=available_languages]{available_languages()}} -} 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/init_model.Rd b/man/init_model.Rd index f8f4456..10d64c8 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,17 +10,20 @@ 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{\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{\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()}} } diff --git a/man/labels_path.Rd b/man/labels_path.Rd new file mode 100644 index 0000000..d88c40e --- /dev/null +++ b/man/labels_path.Rd @@ -0,0 +1,46 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/birdnet_interface.R +\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{ +labels_path(model, ...) + +\method{labels_path}{birdnet_model_custom}(model, ...) + +\method{labels_path}{birdnet_model_tflite}(model, language, ...) + +\method{labels_path}{birdnet_model_protobuf}(model, language, ...) +} +\arguments{ +\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{ +A character string representing the file path to the labels file for the specified language. +} +\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{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +model <- birdnet_model_tflite(version = "v2.4") +labels_path(model, "fr") +\dontshow{\}) # examplesIf} +} +\seealso{ +\code{\link[=available_languages]{available_languages()}} \code{\link[=read_labels]{read_labels()}} +} 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..86c0218 --- /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[=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 58% rename from man/get_species_from_file.Rd rename to man/read_labels.Rd index c1611cb..490db35 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.} @@ -16,15 +16,18 @@ 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 -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 -labels_path <- get_labels_path("fr") -species_list <- get_species_from_file(labels_path) +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[=get_labels_path]{get_labels_path()}} +\code{\link[=available_languages]{available_languages()}} \code{\link[=labels_path]{labels_path()}} } 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..7ea5931 100644 --- a/tests/testthat/test-get_labels_path.R +++ b/tests/testthat/test-get_labels_path.R @@ -1,15 +1,32 @@ library(testthat) -test_that("get_labels_path returns correct path for valid language", { - path <- get_labels_path("en_us") +tflite_model <- birdnet_model_tflite(version = "v2.4") +protobuf_model <- birdnet_model_protobuf(version = "v2.4") + + +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 <- 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", { - expect_error(get_labels_path("invalid_language")) +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 handles edge cases like empty string or NULL", { - expect_error(get_labels_path("")) - expect_error(get_labels_path(NULL)) + +test_that("labels_path throws an error for character input", { + expect_error(labels_path("invalid_language")) +}) + +test_that("labels_path handles edge cases like empty string or NULL", { + expect_error(labels_path("")) + expect_error(labels_path(NULL)) }) + +# Missing test for labels_path with custom model 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)) }) diff --git a/tests/testthat/test-module_map.R b/tests/testthat/test-module_map.R new file mode 100644 index 0000000..c444252 --- /dev/null +++ b/tests/testthat/test-module_map.R @@ -0,0 +1,38 @@ +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", "protobuf", "custom", "raven", "meta") + ) +}) + +test_that("getting eelements from modules mpa", { + module_map <- create_module_map("v2.4", "py_birdnet_models") + tflite_model_path <- get_element_from_module_map(module_map, "models", "tflite") + expect_equal( + tflite_model_path, + "py_birdnet_models$v2m4$AudioModelV2M4TFLite" + ) +}) + + + +test_that("all mapped modules can be evaluates", { + module_map <- create_module_map("v2.4", "py_birdnet_models") + + # 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..9bf34a5 --- /dev/null +++ b/tests/testthat/test-predict_species_from_audio_file.R @@ -0,0 +1,131 @@ +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")) +}) 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é" ```