From 325788481e08af0c079bebcab0ee0b1da5f05e57 Mon Sep 17 00:00:00 2001 From: Kyra Swanson Date: Wed, 28 Jan 2026 12:14:02 -0800 Subject: [PATCH 01/10] move install outside of .onLoad --- NAMESPACE | 1 + R/install.R | 127 +++++++++++++++++++----------- R/zzz.R | 57 +++++++++++--- man/animl_install.Rd | 17 +--- man/animl_install_instructions.Rd | 11 +++ man/check_python.Rd | 4 +- man/create_pyenv.Rd | 4 +- man/load_animl_py.Rd | 17 +++- 8 files changed, 159 insertions(+), 79 deletions(-) create mode 100644 man/animl_install_instructions.Rd diff --git a/NAMESPACE b/NAMESPACE index 63dd3f3..9a4ecd7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,7 @@ export(WorkingDirectory) export(animl_install) +export(animl_install_instructions) export(build_file_manifest) export(check_python) export(classify) diff --git a/R/install.R b/R/install.R index e5916d8..f01ad8c 100644 --- a/R/install.R +++ b/R/install.R @@ -1,46 +1,91 @@ # VARIABLE FOR VERSION ANIML_VERSION <- "3.1.1" -#' Create a miniconda environment for animl and install animl-py +#' Load animl-py if available #' #' @param py_env name of python environment #' @param animl_version version of animl to install #' @param python_version version of python to install -#' @param confirm allow user input #' #' @returns none #' @export #' #' @examples -#' \dontrun{animl_install("animl_env", ANIML_VERSION, python_version="3.9", confirm=TRUE)} +#' \dontrun{animl_install("animl_env", ANIML_VERSION, python_version="3.12")} +load_animl_py <- function(py_env = "animl_env", + animl_version = ANIML_VERSION, + python_version = "3.12") { + # 1. Load environment if exists + packageStartupMessage(sprintf("1. Loading Python Environment (%s)...", py_env)) + try_error <- try(reticulate::use_virtualenv(py_env, required = TRUE), silent=TRUE) + + # 2. Install if not exists + if (inherits(try_error, "try-error")) { + #packageStartupMessage(try_error) + msg <- sprintf(paste0("%s python environment not found. Run animl::animl_install().\n", + "See `?animl::animl_install_instructions` for more detail."), py_env) + packageStartupMessage(msg) + } + # conda env exists + else{ + # check animl-py installed + packageStartupMessage("\n2. Checking animl-py version...") + if(reticulate::py_module_available("animl")){ + animl_py <- reticulate::import("animl", delay_load = TRUE) + py_version <- animl_py$'__version__' + # check version match + if (!identical(animl_version, py_version)){ + # tell user to update animl-py + packageStartupMessage(paste0("animl-py version conflicts with current version.\n + To update animl-py, run animl::update_animl_py().")) + } + # correct version + else{ + packageStartupMessage(paste0("animl successfully loaded.")) + assign("animl_py", animl_py, envir = parent.env(environment())) + } + + } + else{ + # animl_env exists but animl-py not installed + packageStartupMessage(paste0("Python environment found but animl-py not installed.\n", + "See `?animl::animl_install_instructions`.")) + } + } +} + + +#' Load animl-py if available +#' +#' @return animl-py module +#' @export +#' +#' @examples +#' \dontrun{animl_py <- load_animl_py()} animl_install <- function(py_env = "animl_env", animl_version = ANIML_VERSION, python_version = "3.12", confirm=TRUE) { # 1. Load environment if exists packageStartupMessage(sprintf("1. Loading Python Environment (%s)...", py_env)) - try_error <- try(reticulate::use_condaenv(py_env, required = TRUE), silent=TRUE) + try_error <- try(reticulate::use_virtualenv(py_env, required = TRUE), silent=TRUE) # 2. Install if not exists if (inherits(try_error, "try-error")) { packageStartupMessage(try_error) - #packageStartupMessage(sprintf("%s not found \n", py_env)) # 2. Create new environment packageStartupMessage("\n", sprintf("2. Creating a Python Environment (%s)", py_env)) animl_path <- tryCatch(expr = create_pyenv(python_version = python_version, py_env = py_env), error = function(e) stop(e, "An error occur when animl_install was creating the Python Environment.", "Check that you've accepted the conda TOS and restart the R session, before trying again.")) - #print(animl_path) # 3. Install animl-py packageStartupMessage("\n3. Installing animl-py...") package <- sprintf("animl==%s", animl_version) reticulate::py_install(package, envname=py_env, pip=TRUE) packageStartupMessage("animl successfully installed. Restart R session to see changes.\n") - invisible(TRUE) - return(FALSE) } - # conda env exists + # env exists else{ # check animl-py installed packageStartupMessage("\n2. Checking animl-py version...") @@ -54,31 +99,11 @@ animl_install <- function(py_env = "animl_env", else{ packageStartupMessage("\n3. Installing animl-py...") package <- sprintf("animl==%s", animl_version) - reticulate::use_condaenv(py_env) + reticulate::use_virtualenv(py_env) reticulate::py_install(package, pip=TRUE) } - return(TRUE) - } -} - - -#' Load animl-py if available -#' -#' @return animl-py module -#' @export -#' -#' @examples -#' \dontrun{animl_py <- load_animl_py()} -load_animl_py <- function() { - if(reticulate::py_module_available("animl")){ - animl_py <- reticulate::import("animl", delay_load = TRUE) - packageStartupMessage("animl-py loaded successfully.") - return(animl_py) - } - else{ - packageStartupMessage('Animl load failed') - packageStartupMessage(reticulate::py_config()) } + invisible() } @@ -95,16 +120,20 @@ load_animl_py <- function() { update_animl_py <- function(py_env = "animl_env", animl_version = ANIML_VERSION) { # load animl-py, check version - packageStartupMessage("animl-py version mismatch, reinstalling...") - reticulate::use_condaenv(py_env) + packageStartupMessage("3. animl-py version mismatch, reinstalling...") + reticulate::use_virtualenv(py_env) reticulate::py_install(sprintf("animl==%s", animl_version), pip=TRUE) + packageStartupMessage("") + # assign animl-py for use + animl_py <- reticulate::import("animl", delay_load = TRUE) + assign("animl_py", animl_py, envir = parent.env(environment())) } - #' Check that the python version is compatible with the current version of animl-py #' +#' @param python_version version of python to install #' @param initialize load reticulate library if true #' #' @returns none @@ -112,23 +141,28 @@ update_animl_py <- function(py_env = "animl_env", #' #' @examples #' \dontrun{check_python(initialize=FALSE)} -check_python <- function(initialize = TRUE) { +check_python <- function(python_version="3.12", initialize = TRUE) { python_test <- reticulate::py_available(initialize=initialize) - if (python_test) { py_version <- reticulate::py_discover_config()[["version"]] + if (python_test) { + py_version <- reticulate::py_discover_config()[["version"]] + # check if correct version + if (utils::compareVersion(as.character(py_version), "3.12") == -1) { + packageStartupMessage(sprintf("Python %s not found, installing.", py_version)) + reticulate::install_python(version=python_version) + } + else{ + packageStartupMessage(sprintf("Found Python version %s compatible with animl.", py_version)) + } } - else { stop("Unable to find a Python installation.", - "Please install Python befor running animl_initiaialzer().", - "For more details run reticulate::py_discover_config()") - } - if (utils::compareVersion(as.character(py_version), "3.12") == -1) { - stop("animl needs Python >=3.12") + else { + packageStartupMessage(sprintf("Python %s not found, installing.", py_version)) + reticulate::install_python(version=python_version) } - packageStartupMessage(sprintf("Python version %s compatible with animl.", py_version)) } -#' Create the miniconda environment animl_env +#' Create the environment animl_env #' #' @param python_version python version for new environment #' @param py_env name of python environment @@ -141,8 +175,7 @@ check_python <- function(initialize = TRUE) { create_pyenv <- function(python_version, py_env = "animl_env") { #Check is Python is greather than 3.9 check_python(initialize=TRUE) - pyenv_path <- reticulate::conda_create(py_env, python_version = python_version) - print(pyenv_path) + pyenv_path <- reticulate::virtualenv_create(py_env, python_version = python_version) pyenv_path } @@ -157,5 +190,5 @@ create_pyenv <- function(python_version, py_env = "animl_env") { #' @examples #' \dontrun{delete_pyenv('animl_env')} delete_pyenv <- function(py_env = "animl_env") { - try(reticulate::conda_remove(py_env), silent = TRUE) + try(reticulate::virtualenv_remove(py_env), silent = TRUE) } diff --git a/R/zzz.R b/R/zzz.R index 299c8e6..27b51af 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,19 +1,50 @@ .onLoad <- function(libname, pkgname) { - animl_env_avail <- animl_install() - if (animl_env_avail) { - animl_py <- load_animl_py() - assign("animl_py", animl_py, envir = parent.env(environment())) - } - else{ - .stop_animl_failed() - } + load_animl_py() + invisible() } -.stop_animl_failed <- function(){ + +.onAttach <- function(libname, pkgname) { + # Only give a very small hint to interactive users (not required) if (interactive()) { - warning('animl_env load failed') - } else { - # non-interactive / scripts should get a clean error - stop('animl_env load failed', call. = FALSE) + if (requireNamespace("reticulate", quietly = TRUE)) { + pkgmsg <- paste0( + "animl: requires Python 3.12 and animl-py 3.1.1. If animl fails to load, ", + "see `?animl::animl_install_instructions`." + ) + } else { + pkgmsg <- paste0( + "animl: For all features install the 'reticulate' package ", + "(install.packages('reticulate')) and follow `?animl::animl_install_instructions`." + ) + } + packageStartupMessage(pkgmsg) } + invisible() +} + + +#' Installation Instructions for animl-r Python dependencies +#' +#' +#' @export +animl_install_instructions <- function() { + cat( + "animl: instructions to prepare a Python environment for optional features\n\n", + "Recommended (conda):\n", + " conda create -n animl-py python=3.10\n", + " conda activate animl-py\n", + " conda install -c conda-forge numpy pandas # add other required pkgs\n", + " In R: reticulate::use_condaenv('animl-py', required = TRUE)\n\n", + "Virtualenv/pip alternative:\n", + " python -m venv ~/venvs/animl-py\n", + " source ~/venvs/animl-py/bin/activate\n", + " pip install --upgrade pip\n", + " pip install numpy pandas # add other required pkgs\n", + " In R: reticulate::use_python('~/venvs/animl-py/bin/python', required = TRUE)\n\n", + "Note: Do NOT rely on automatic installation from inside the package; ", + "install Python packages manually and configure reticulate.", + sep = "" + ) + invisible(NULL) } \ No newline at end of file diff --git a/man/animl_install.Rd b/man/animl_install.Rd index b8c10eb..27ea579 100644 --- a/man/animl_install.Rd +++ b/man/animl_install.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/install.R \name{animl_install} \alias{animl_install} -\title{Create a miniconda environment for animl and install animl-py} +\title{Load animl-py if available} \usage{ animl_install( py_env = "animl_env", @@ -11,21 +11,12 @@ animl_install( confirm = TRUE ) } -\arguments{ -\item{py_env}{name of python environment} - -\item{animl_version}{version of animl to install} - -\item{python_version}{version of python to install} - -\item{confirm}{allow user input} -} \value{ -none +animl-py module } \description{ -Create a miniconda environment for animl and install animl-py +Load animl-py if available } \examples{ -\dontrun{animl_install("animl_env", ANIML_VERSION, python_version="3.9", confirm=TRUE)} +\dontrun{animl_py <- load_animl_py()} } diff --git a/man/animl_install_instructions.Rd b/man/animl_install_instructions.Rd new file mode 100644 index 0000000..fc8f62d --- /dev/null +++ b/man/animl_install_instructions.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/zzz.R +\name{animl_install_instructions} +\alias{animl_install_instructions} +\title{Installation Instructions for animl-r Python dependencies} +\usage{ +animl_install_instructions() +} +\description{ +Installation Instructions for animl-r Python dependencies +} diff --git a/man/check_python.Rd b/man/check_python.Rd index 299c2c7..a2172b7 100644 --- a/man/check_python.Rd +++ b/man/check_python.Rd @@ -4,9 +4,11 @@ \alias{check_python} \title{Check that the python version is compatible with the current version of animl-py} \usage{ -check_python(initialize = TRUE) +check_python(python_version = "3.12", initialize = TRUE) } \arguments{ +\item{python_version}{version of python to install} + \item{initialize}{load reticulate library if true} } \value{ diff --git a/man/create_pyenv.Rd b/man/create_pyenv.Rd index cbaa398..69f6ce0 100644 --- a/man/create_pyenv.Rd +++ b/man/create_pyenv.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/install.R \name{create_pyenv} \alias{create_pyenv} -\title{Create the miniconda environment animl_env} +\title{Create the environment animl_env} \usage{ create_pyenv(python_version, py_env = "animl_env") } @@ -15,7 +15,7 @@ create_pyenv(python_version, py_env = "animl_env") python path for new environment } \description{ -Create the miniconda environment animl_env +Create the environment animl_env } \examples{ \dontrun{create_env("3.12", py_env='animl_env')} diff --git a/man/load_animl_py.Rd b/man/load_animl_py.Rd index 27664fa..9568329 100644 --- a/man/load_animl_py.Rd +++ b/man/load_animl_py.Rd @@ -4,14 +4,25 @@ \alias{load_animl_py} \title{Load animl-py if available} \usage{ -load_animl_py() +load_animl_py( + py_env = "animl_env", + animl_version = ANIML_VERSION, + python_version = "3.12" +) +} +\arguments{ +\item{py_env}{name of python environment} + +\item{animl_version}{version of animl to install} + +\item{python_version}{version of python to install} } \value{ -animl-py module +none } \description{ Load animl-py if available } \examples{ -\dontrun{animl_py <- load_animl_py()} +\dontrun{animl_install("animl_env", ANIML_VERSION, python_version="3.12")} } From e2c627ba0e6ffcce065cbd1e8b7ceb3e35f05811 Mon Sep 17 00:00:00 2001 From: Kyra Swanson Date: Wed, 28 Jan 2026 15:55:30 -0800 Subject: [PATCH 02/10] venv instead of conda, change arg names --- R/install.R | 186 +++++++++++++++++++++++--------------- R/zzz.R | 15 ++- examples/Workflow_animl.R | 2 +- man/animl_install.Rd | 12 +-- man/create_pyenv.Rd | 15 ++- man/delete_pyenv.Rd | 4 +- man/load_animl_py.Rd | 10 +- man/update_animl_py.Rd | 12 +-- 8 files changed, 141 insertions(+), 115 deletions(-) diff --git a/R/install.R b/R/install.R index f01ad8c..66ed61e 100644 --- a/R/install.R +++ b/R/install.R @@ -3,8 +3,7 @@ ANIML_VERSION <- "3.1.1" #' Load animl-py if available #' -#' @param py_env name of python environment -#' @param animl_version version of animl to install +#' @param envname name of python environment #' @param python_version version of python to install #' #' @returns none @@ -12,44 +11,45 @@ ANIML_VERSION <- "3.1.1" #' #' @examples #' \dontrun{animl_install("animl_env", ANIML_VERSION, python_version="3.12")} -load_animl_py <- function(py_env = "animl_env", - animl_version = ANIML_VERSION, +load_animl_py <- function(envname = "animl_env", python_version = "3.12") { # 1. Load environment if exists - packageStartupMessage(sprintf("1. Loading Python Environment (%s)...", py_env)) - try_error <- try(reticulate::use_virtualenv(py_env, required = TRUE), silent=TRUE) + packageStartupMessage(sprintf("1. Loading Python Environment (%s)...", envname)) + try_error <- try(reticulate::use_virtualenv(envname, required = TRUE), silent=TRUE) # 2. Install if not exists if (inherits(try_error, "try-error")) { #packageStartupMessage(try_error) msg <- sprintf(paste0("%s python environment not found. Run animl::animl_install().\n", - "See `?animl::animl_install_instructions` for more detail."), py_env) + "See `?animl::animl_install_instructions` for more detail."), envname) packageStartupMessage(msg) + return(NULL) } - # conda env exists + # env exists else{ # check animl-py installed packageStartupMessage("\n2. Checking animl-py version...") if(reticulate::py_module_available("animl")){ animl_py <- reticulate::import("animl", delay_load = TRUE) - py_version <- animl_py$'__version__' + current_version <- animl_py$'__version__' # check version match - if (!identical(animl_version, py_version)){ + if (!identical(ANIML_VERSION, current_version)){ # tell user to update animl-py packageStartupMessage(paste0("animl-py version conflicts with current version.\n To update animl-py, run animl::update_animl_py().")) + return(NULL) } # correct version else{ packageStartupMessage(paste0("animl successfully loaded.")) - assign("animl_py", animl_py, envir = parent.env(environment())) + return(animl_py) } - } + # animl_env exists but animl-py not installed else{ - # animl_env exists but animl-py not installed packageStartupMessage(paste0("Python environment found but animl-py not installed.\n", "See `?animl::animl_install_instructions`.")) + return(NULL) } } } @@ -57,79 +57,116 @@ load_animl_py <- function(py_env = "animl_env", #' Load animl-py if available #' +#' @param envname name of python environment +#' @param python_version version of python to install +#' #' @return animl-py module #' @export #' #' @examples #' \dontrun{animl_py <- load_animl_py()} -animl_install <- function(py_env = "animl_env", - animl_version = ANIML_VERSION, - python_version = "3.12", - confirm=TRUE) { +animl_install <- function(envname = "animl_env", python_version = "3.12") { + if (!interactive()) { + stop("animl_install() must be run interactively.",call. = FALSE) + } + if (!requireNamespace("reticulate", quietly = TRUE)) { + stop("Please install the 'reticulate' package first: install.packages('reticulate')", call. = FALSE) + } + # 1. Load environment if exists - packageStartupMessage(sprintf("1. Loading Python Environment (%s)...", py_env)) - try_error <- try(reticulate::use_virtualenv(py_env, required = TRUE), silent=TRUE) + packageStartupMessage(sprintf("1. Loading Python Environment (%s)...", envname)) + try_error <- try(reticulate::use_virtualenv(envname, required = TRUE), silent=TRUE) # 2. Install if not exists if (inherits(try_error, "try-error")) { packageStartupMessage(try_error) # 2. Create new environment - packageStartupMessage("\n", sprintf("2. Creating a Python Environment (%s)", py_env)) - animl_path <- tryCatch(expr = create_pyenv(python_version = python_version, py_env = py_env), - error = function(e) stop(e, "An error occur when animl_install was creating the Python Environment.", - "Check that you've accepted the conda TOS and restart the R session, before trying again.")) - # 3. Install animl-py - packageStartupMessage("\n3. Installing animl-py...") - package <- sprintf("animl==%s", animl_version) - reticulate::py_install(package, envname=py_env, pip=TRUE) - + packageStartupMessage("\n", sprintf("2. Creating a Python Environment (%s)", envname)) + animl_path <- tryCatch(expr = create_pyenv(python_version = python_version, envname = envname), + error = function(e) stop(e, "An error occur when animl_install was creating the Python Environment.")) packageStartupMessage("animl successfully installed. Restart R session to see changes.\n") } - # env exists + # animl_env exists else{ # check animl-py installed packageStartupMessage("\n2. Checking animl-py version...") if(reticulate::py_module_available("animl")){ animl_py <- reticulate::import("animl", delay_load = TRUE) - py_version <- animl_py$'__version__' + current_version <- animl_py$'__version__' # check version match - if (!identical(animl_version, py_version)){update_animl_py()} + if (!identical(ANIML_VERSION, current_version)){ + update_animl_py() + } } # animl-py not yet installed else{ packageStartupMessage("\n3. Installing animl-py...") - package <- sprintf("animl==%s", animl_version) - reticulate::use_virtualenv(py_env) + package <- sprintf("animl==%s", ANIML_VERSION) reticulate::py_install(package, pip=TRUE) + packageStartupMessage("animl successfully installed. Restart R session to see changes.\n") } } invisible() } -#' Update animl-py version +#' Update animl-py version for the given environment #' -#' @param py_env name of python environment -#' @param animl_version version of animl to install +#' @param envname name of python environment #' #' @returns None #' @export #' #' @examples -#' \dontrun{update_animl_py(py_env = "animl_env", animl_version = ANIML_VERSION)} -update_animl_py <- function(py_env = "animl_env", - animl_version = ANIML_VERSION) { +#' \dontrun{update_animl_py(py_env = "animl_env")} +update_animl_py <- function(envname = "animl_env") { + if (!interactive()) { + stop(paste0("update_animl_py() must be run interactively.", + "For non-interactive/CI installs, use system installers or CI actions.", + call. = FALSE)) + } + if (!requireNamespace("reticulate", quietly=TRUE)) { + stop("Please install the 'reticulate' package first: install.packages('reticulate')", call. = FALSE) + } + # load animl-py, check version - packageStartupMessage("3. animl-py version mismatch, reinstalling...") - reticulate::use_virtualenv(py_env) - reticulate::py_install(sprintf("animl==%s", animl_version), pip=TRUE) - packageStartupMessage("") - # assign animl-py for use - animl_py <- reticulate::import("animl", delay_load = TRUE) - assign("animl_py", animl_py, envir = parent.env(environment())) + packageStartupMessage("animl-py version mismatch, reinstalling...") + reticulate::use_virtualenv(envname) + reticulate::py_install(sprintf("animl==%s", ANIML_VERSION), pip=TRUE) + packageStartupMessage("animl successfully installed. Restart R session to see changes.\n") } +#' Install python if necessary and create the environment animl_env +#' +#' @param envname name of the conda environment to create / use (default "animl-py") +#' @param python_version python version to add to environment +#' +#' @return invisible TRUE on success, otherwise stops or returns FALSE invisibly on failure +#' @export +create_pyenv <- function(envname = "animl_env", python_version = "3.12") { + if (!interactive()) { + stop(paste0("create_pyenv() must be run interactively.", + "For non-interactive/CI installs, use system installers or CI actions.", + call. = FALSE)) + } + if (!requireNamespace("reticulate", quietly=TRUE)) { + stop("Please install the 'reticulate' package first: install.packages('reticulate')", call. = FALSE) + } + + # 1) Check is Python is installed and greater than 3.12 + check_python(initialize=TRUE) + + # 2) Create venv + packageStartupMessage(paste0("Creating virtual environment '", envname, "' ...")) + reticulate::virtualenv_create(envname=envname, python_version=python_version) + + # 3) Install animl-py + packageStartupMessage("\n3. Installing animl-py...") + package <- sprintf("animl==%s", ANIML_VERSION) + reticulate::py_install(package, envname=envname, pip=TRUE) +} + #' Check that the python version is compatible with the current version of animl-py #' @@ -141,54 +178,57 @@ update_animl_py <- function(py_env = "animl_env", #' #' @examples #' \dontrun{check_python(initialize=FALSE)} -check_python <- function(python_version="3.12", initialize = TRUE) { +check_python <- function(python_version = "3.12", initialize = TRUE) { + if (!interactive()) { + stop(paste0("check_python() must be run interactively.", + "For non-interactive/CI installs, use system installers or CI actions.", + call. = FALSE)) + } + if (!requireNamespace("reticulate", quietly=TRUE)) { + stop("Please install the 'reticulate' package first: install.packages('reticulate')", call. = FALSE) + } + # check if python is available python_test <- reticulate::py_available(initialize=initialize) if (python_test) { - py_version <- reticulate::py_discover_config()[["version"]] + current_version <- reticulate::py_discover_config()[["version"]] # check if correct version - if (utils::compareVersion(as.character(py_version), "3.12") == -1) { - packageStartupMessage(sprintf("Python %s not found, installing.", py_version)) + if (!identical(as.character(current_version), "3.12")) { + packageStartupMessage(sprintf("Python %s not found, installing...", python_version)) reticulate::install_python(version=python_version) } + # 3.12 installed else{ - packageStartupMessage(sprintf("Found Python version %s compatible with animl.", py_version)) + packageStartupMessage(sprintf("Found Python version %s compatible with animl.", current_version)) } } + # no python installed at all else { - packageStartupMessage(sprintf("Python %s not found, installing.", py_version)) + packageStartupMessage(sprintf("Python %s not found, installing...", python_version)) reticulate::install_python(version=python_version) } + invisible() } -#' Create the environment animl_env -#' -#' @param python_version python version for new environment -#' @param py_env name of python environment -#' -#' @returns python path for new environment -#' @export -#' -#' @examples -#' \dontrun{create_env("3.12", py_env='animl_env')} -create_pyenv <- function(python_version, py_env = "animl_env") { - #Check is Python is greather than 3.9 - check_python(initialize=TRUE) - pyenv_path <- reticulate::virtualenv_create(py_env, python_version = python_version) - pyenv_path -} - - #' Delete the animl_env environment #' -#' @param py_env python environment to remove +#' @param envname python environment to remove #' #' @returns none #' @export #' #' @examples #' \dontrun{delete_pyenv('animl_env')} -delete_pyenv <- function(py_env = "animl_env") { - try(reticulate::virtualenv_remove(py_env), silent = TRUE) +delete_pyenv <- function(envname = "animl_env") { + if (!interactive()) { + stop(paste0("delete_pyenv() must be run interactively.", + "For non-interactive/CI installs, use system installers or CI actions.", + call. = FALSE)) + } + if (!requireNamespace("reticulate", quietly=TRUE)) { + stop("Please install the 'reticulate' package first: install.packages('reticulate')", call. = FALSE) + } + + try(reticulate::virtualenv_remove(envname), silent = TRUE) } diff --git a/R/zzz.R b/R/zzz.R index 27b51af..40c4580 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,5 +1,6 @@ .onLoad <- function(libname, pkgname) { - load_animl_py() + animl_py <- load_animl_py() + assign("animl_py", animl_py, envir = parent.env(environment())) invisible() } @@ -31,14 +32,10 @@ animl_install_instructions <- function() { cat( "animl: instructions to prepare a Python environment for optional features\n\n", - "Recommended (conda):\n", - " conda create -n animl-py python=3.10\n", - " conda activate animl-py\n", - " conda install -c conda-forge numpy pandas # add other required pkgs\n", - " In R: reticulate::use_condaenv('animl-py', required = TRUE)\n\n", - "Virtualenv/pip alternative:\n", - " python -m venv ~/venvs/animl-py\n", - " source ~/venvs/animl-py/bin/activate\n", + "Run animl::animl_install() to set up Python 3.12 environment and install animl-py dependency.\n\n", + "Virtualenv/pip alternative (requires python 3.12 installed):\n", + " python -m venv ~/venvs/animl_env\n", + " source ~/venvs/animl_env/bin/activate\n", " pip install --upgrade pip\n", " pip install numpy pandas # add other required pkgs\n", " In R: reticulate::use_python('~/venvs/animl-py/bin/python', required = TRUE)\n\n", diff --git a/examples/Workflow_animl.R b/examples/Workflow_animl.R index 8f6c46d..633b562 100644 --- a/examples/Workflow_animl.R +++ b/examples/Workflow_animl.R @@ -16,7 +16,7 @@ imagedir <- "examples/Southwest" WorkingDirectory(imagedir, globalenv()) # Build file manifest for all images and videos within base directory -files <- build_file_manifest(imagedir, out_file=filemanifest_file, exif=TRUE) +files <- build_file_manifest(imagedir, exif=TRUE) #=============================================================================== # Add Project-Specific Info diff --git a/man/animl_install.Rd b/man/animl_install.Rd index 27ea579..cbf548d 100644 --- a/man/animl_install.Rd +++ b/man/animl_install.Rd @@ -4,12 +4,12 @@ \alias{animl_install} \title{Load animl-py if available} \usage{ -animl_install( - py_env = "animl_env", - animl_version = ANIML_VERSION, - python_version = "3.12", - confirm = TRUE -) +animl_install(envname = "animl_env", python_version = "3.12") +} +\arguments{ +\item{envname}{name of python environment} + +\item{python_version}{version of python to install} } \value{ animl-py module diff --git a/man/create_pyenv.Rd b/man/create_pyenv.Rd index 69f6ce0..64aa95e 100644 --- a/man/create_pyenv.Rd +++ b/man/create_pyenv.Rd @@ -2,21 +2,18 @@ % Please edit documentation in R/install.R \name{create_pyenv} \alias{create_pyenv} -\title{Create the environment animl_env} +\title{Install python if necessary and create the environment animl_env} \usage{ -create_pyenv(python_version, py_env = "animl_env") +create_pyenv(envname = "animl_env", python_version = "3.12") } \arguments{ -\item{python_version}{python version for new environment} +\item{envname}{name of the conda environment to create / use (default "animl-py")} -\item{py_env}{name of python environment} +\item{python_version}{python version to add to environment} } \value{ -python path for new environment +invisible TRUE on success, otherwise stops or returns FALSE invisibly on failure } \description{ -Create the environment animl_env -} -\examples{ -\dontrun{create_env("3.12", py_env='animl_env')} +Install python if necessary and create the environment animl_env } diff --git a/man/delete_pyenv.Rd b/man/delete_pyenv.Rd index 6bd522d..bb0af3a 100644 --- a/man/delete_pyenv.Rd +++ b/man/delete_pyenv.Rd @@ -4,10 +4,10 @@ \alias{delete_pyenv} \title{Delete the animl_env environment} \usage{ -delete_pyenv(py_env = "animl_env") +delete_pyenv(envname = "animl_env") } \arguments{ -\item{py_env}{python environment to remove} +\item{envname}{python environment to remove} } \value{ none diff --git a/man/load_animl_py.Rd b/man/load_animl_py.Rd index 9568329..c053095 100644 --- a/man/load_animl_py.Rd +++ b/man/load_animl_py.Rd @@ -4,16 +4,10 @@ \alias{load_animl_py} \title{Load animl-py if available} \usage{ -load_animl_py( - py_env = "animl_env", - animl_version = ANIML_VERSION, - python_version = "3.12" -) +load_animl_py(envname = "animl_env", python_version = "3.12") } \arguments{ -\item{py_env}{name of python environment} - -\item{animl_version}{version of animl to install} +\item{envname}{name of python environment} \item{python_version}{version of python to install} } diff --git a/man/update_animl_py.Rd b/man/update_animl_py.Rd index c4a4743..b2a5160 100644 --- a/man/update_animl_py.Rd +++ b/man/update_animl_py.Rd @@ -2,21 +2,19 @@ % Please edit documentation in R/install.R \name{update_animl_py} \alias{update_animl_py} -\title{Update animl-py version} +\title{Update animl-py version for the given environment} \usage{ -update_animl_py(py_env = "animl_env", animl_version = ANIML_VERSION) +update_animl_py(envname = "animl_env") } \arguments{ -\item{py_env}{name of python environment} - -\item{animl_version}{version of animl to install} +\item{envname}{name of python environment} } \value{ None } \description{ -Update animl-py version +Update animl-py version for the given environment } \examples{ -\dontrun{update_animl_py(py_env = "animl_env", animl_version = ANIML_VERSION)} +\dontrun{update_animl_py(py_env = "animl_env")} } From eeb53fae6643cf1ffc9e4d154a644e95bc8a87c7 Mon Sep 17 00:00:00 2001 From: Kyra Swanson Date: Wed, 28 Jan 2026 17:04:09 -0800 Subject: [PATCH 03/10] change to load_animl() and update to 3.2.0 --- DESCRIPTION | 2 +- NAMESPACE | 2 +- R/install.R | 8 ++++---- R/zzz.R | 7 +++++-- man/{load_animl_py.Rd => load_animl.Rd} | 6 +++--- 5 files changed, 14 insertions(+), 11 deletions(-) rename man/{load_animl_py.Rd => load_animl.Rd} (78%) diff --git a/DESCRIPTION b/DESCRIPTION index 6918ff7..3b262ef 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: animl Title: A Collection of ML Tools for Conservation Research -Version: 3.1.1 +Version: 3.2.0 Authors@R: c(person(given="Kyra", family="Swanson",email="tswanson@sdzwa.org", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-1496-3217")), person(given="Mathias",family="Tobler",role = "aut")) Description: Functions required to classify subjects within camera trap field data. The package can handle both images and videos. The authors recommend a two-step approach using Microsoft's 'MegaDector' model and then a second model trained on the classes of interest. diff --git a/NAMESPACE b/NAMESPACE index 9a4ecd7..7686fe6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -25,7 +25,7 @@ export(get_animals) export(get_empty) export(get_frame_as_image) export(list_models) -export(load_animl_py) +export(load_animl) export(load_class_list) export(load_classifier) export(load_data) diff --git a/R/install.R b/R/install.R index 66ed61e..3910fb6 100644 --- a/R/install.R +++ b/R/install.R @@ -1,5 +1,5 @@ # VARIABLE FOR VERSION -ANIML_VERSION <- "3.1.1" +ANIML_VERSION <- "3.2.0" #' Load animl-py if available #' @@ -11,7 +11,7 @@ ANIML_VERSION <- "3.1.1" #' #' @examples #' \dontrun{animl_install("animl_env", ANIML_VERSION, python_version="3.12")} -load_animl_py <- function(envname = "animl_env", +load_animl <- function(envname = "animl_env", python_version = "3.12") { # 1. Load environment if exists packageStartupMessage(sprintf("1. Loading Python Environment (%s)...", envname)) @@ -35,8 +35,8 @@ load_animl_py <- function(envname = "animl_env", # check version match if (!identical(ANIML_VERSION, current_version)){ # tell user to update animl-py - packageStartupMessage(paste0("animl-py version conflicts with current version.\n - To update animl-py, run animl::update_animl_py().")) + packageStartupMessage(paste0("animl-py version conflicts with current version.\n", + "To update animl-py, run animl::update_animl_py().")) return(NULL) } # correct version diff --git a/R/zzz.R b/R/zzz.R index 40c4580..d584289 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,6 +1,9 @@ +.animl_internal <- new.env(parent = emptyenv()) +.animl_internal$animl_py <- NULL + .onLoad <- function(libname, pkgname) { - animl_py <- load_animl_py() - assign("animl_py", animl_py, envir = parent.env(environment())) + animl_py <- load_animl() + assign("animl_py", animl_py, envir = .animl_internal) invisible() } diff --git a/man/load_animl_py.Rd b/man/load_animl.Rd similarity index 78% rename from man/load_animl_py.Rd rename to man/load_animl.Rd index c053095..b12422e 100644 --- a/man/load_animl_py.Rd +++ b/man/load_animl.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/install.R -\name{load_animl_py} -\alias{load_animl_py} +\name{load_animl} +\alias{load_animl} \title{Load animl-py if available} \usage{ -load_animl_py(envname = "animl_env", python_version = "3.12") +load_animl(envname = "animl_env", python_version = "3.12") } \arguments{ \item{envname}{name of python environment} From ab8b2eb6ab78cc81453cbd194166a2296f45e1a1 Mon Sep 17 00:00:00 2001 From: tkswanson Date: Thu, 29 Jan 2026 15:07:10 -0800 Subject: [PATCH 04/10] add best param to single_classification --- R/classification.R | 52 +++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/R/classification.R b/R/classification.R index 415a10a..ca7e93c 100644 --- a/R/classification.R +++ b/R/classification.R @@ -1,3 +1,22 @@ +#' Save model state weights +#' +#' @param model pytorch model +#' @param out_dir directory to save model to +#' @param epoch current training epoch +#' @param stats performance metrics of current epoch +#' @param optimizer pytorch optimizer (optional) +#' @param scheduler pytorch scheduler (optional) +#' +#' @returns None +#' @export +#' +#' @examples +#' \dontrun{save_classifier(model, 'models/', 10, list(acc = 0.85))} +save_classifier <- function(model, out_dir, epoch, stats, optimizer=NULL, scheduler=NULL){ + animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py$save_classifier(model, out_dir, epoch, reticulate::r_to_py(stats), optimizer=optimizer, scheduler=scheduler) +} + #' Load a Classifier Model and Class_list #' #' @param model_path path to model @@ -20,25 +39,6 @@ load_classifier <- function(model_path, classes, device=NULL, architecture="CTL" } -#' Save model state weights -#' -#' @param model pytorch model -#' @param out_dir directory to save model to -#' @param epoch current training epoch -#' @param stats performance metrics of current epoch -#' @param optimizer pytorch optimizer (optional) -#' @param scheduler pytorch scheduler (optional) -#' -#' @returns None -#' @export -#' -#' @examples -#' \dontrun{save_classifier(model, 'models/', 10, list(acc = 0.85))} -save_classifier <- function(model, out_dir, epoch, stats, optimizer=NULL, scheduler=NULL){ - animl_py <- get("animl_py", envir = parent.env(environment())) - animl_py$save_classifier(model, out_dir, epoch, reticulate::r_to_py(stats), optimizer=optimizer, scheduler=scheduler) -} - #' Load class list .csv file #' #' @param classlist_file path to class list @@ -73,11 +73,10 @@ load_class_list <- function(classlist_file){ #' @examples #' \dontrun{animals <- classify(classifier, animals, file_col='filepath')} classify <- function(model, detections, - resize_width=480, resize_height=480, - file_col='filepath', crop=TRUE, normalize=TRUE, - batch_size=1, num_workers=1, - device=NULL, out_file=NULL){ - + resize_width=480, resize_height=480, + file_col='filepath', crop=TRUE, normalize=TRUE, + batch_size=1, num_workers=1, + device=NULL, out_file=NULL){ animl_py <- get("animl_py", envir = parent.env(environment())) animl_py$classify(model, detections, resize_width=as.integer(resize_width), @@ -95,13 +94,14 @@ classify <- function(model, detections, #' @param empty manifest of md human, vehicle and empty images #' @param predictions_raw softmaxed likelihoods from predict_species #' @param class_list list of class labels +#' @param best whether to return one prediction per file #' #' @returns dataframe with prediction and confidence columns #' @export #' #' @examples #' \dontrun{animals <- single_classification(animals, empty, pred_raw, class_list)} -single_classification <- function(animals, empty, predictions_raw, class_list){ +single_classification <- function(animals, empty, predictions_raw, class_list, best=FALSE){ animl_py <- get("animl_py", envir = parent.env(environment())) - animl_py$single_classification(animals, empty, predictions_raw, class_list) + animl_py$single_classification(animals, empty, predictions_raw, class_list, best=best) } From 26fcf17dd34125a8b09c7928938ac6802d256873 Mon Sep 17 00:00:00 2001 From: tkswanson Date: Thu, 29 Jan 2026 15:34:25 -0800 Subject: [PATCH 05/10] update args and docstrings --- R/detection.R | 3 +-- R/export.R | 16 ++++++++-------- R/video_processing.R | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/R/detection.R b/R/detection.R index 02e19fc..cc68c1d 100644 --- a/R/detection.R +++ b/R/detection.R @@ -11,7 +11,7 @@ #' \dontrun{md_py <- megadetector("/mnt/machinelearning/megaDetector/md_v5a.0.0.pt", #' model_type='mdv5', device='cuda:0')} load_detector <- function(model_path, model_type, device=NULL){ - # first check if animl-py is loaded + # TODO first check if animl-py is loaded animl_py$load_detector(model_path, model_type=model_type, device=device) } @@ -71,4 +71,3 @@ parse_detections <- function(results, manifest=NULL, out_file=NULL, threshold=0, animl_py$parse_detections(results, manifest=manifest, out_file=out_file, threshold=threshold, file_col=file_col) } - diff --git a/R/export.R b/R/export.R index c73c2ca..127a362 100644 --- a/R/export.R +++ b/R/export.R @@ -1,9 +1,9 @@ #' Create SymLink Directories and Sort Classified Images #' #' @param manifest DataFrame of classified images -#' @param out_dir Destination directory for symlinks +#' @param out_dir Destination directory for species folders #' @param out_file if provided, save the manifest to this file -#' @param label_col (str): specify 'prediction' for species or 'category' for megadetector class +#' @param label_col specify 'prediction' for species or 'category' for megadetector class #' @param file_col Colun containing file paths #' @param unique_name Unique image name identifier #' @param copy Toggle to determine copy or hard link, defaults to link @@ -49,7 +49,7 @@ remove_link <- function(manifest, link_col='link'){ #' #' @param manifest dataframe containing file data and predictions #' @param export_dir directory to sort files into -#' @param unique_name column name indicating a unique file name for each row +#' @param unique_name column containing unique file names #' #' @return dataframe with new "Species" column that contains the verified species #' @export @@ -123,8 +123,8 @@ export_camtrapR <- function(manifest, out_dir, out_file=NULL, label_col='predict #' Converts the Manifests to a csv file that contains columns needed for TimeLapse conversion in later step #' -#' @param results a DataFrame that has entries of anuimal classification -#' @param image_dir location of root directory where all images are stored (can contain subdirectories) +#' @param manifest a DataFrame that has entries of anuimal classification +#' @param out_dir location of root directory where all images are stored (can contain subdirectories) #' @param only_animal A bool that confirms whether we want only animal detctions or all #' #' @returns animals.csv, non-anim.csv, csv_loc @@ -132,9 +132,9 @@ export_camtrapR <- function(manifest, out_dir, out_file=NULL, label_col='predict #' #' @examples #' \dontrun{export_timelapse(animals, empty, '/path/to/images/')} -export_timelapse <- function(results, image_dir, only_animal=TRUE){ +export_timelapse <- function(manifest, out_dir, only_animal=TRUE){ animl_py <- get("animl_py", envir = parent.env(environment())) - animl_py$export_timelapse(results, image_dir, only_animal=only_animal) + animl_py$export_timelapse(manifest, out_dir, only_animal=only_animal) } @@ -151,7 +151,7 @@ export_timelapse <- function(results, image_dir, only_animal=TRUE){ #' @examples #' \dontrun{export_megadetector(manifest, output_file= 'results.json', detector='MDv6')} export_megadetector <- function(manifest, out_file=NULL, - detector='MegaDetector v5a', prompt=TRUE){ + detector='MegaDetector v5b', prompt=TRUE){ animl_py <- get("animl_py", envir = parent.env(environment())) animl_py$export_megadetector(manifest, out_file=out_file, detector=detector, prompt=prompt) diff --git a/R/video_processing.R b/R/video_processing.R index ca6aa48..444a92d 100644 --- a/R/video_processing.R +++ b/R/video_processing.R @@ -17,7 +17,7 @@ #' frames <- extract_frames(manifest, out_dir = "C:\\Users\\usr\\Videos\\", frames = 5) #' } extract_frames <- function(files, frames=5, fps=NULL, out_file=NULL, out_dir=NULL, - file_col="filepath", parallel=FALSE, num_workers=1){ + file_col="filepath", parallel=TRUE, num_workers=1){ if (!is.null(fps)){ fps <- as.integer(fps) } if (!is.null(frames)){ frames <- as.integer(frames) } animl_py <- get("animl_py", envir = parent.env(environment())) @@ -39,5 +39,5 @@ extract_frames <- function(files, frames=5, fps=NULL, out_file=NULL, out_dir=NUL #' \dontrun{get_frame_as_image('/example/path/to/video.mp4', frame=213)} get_frame_as_image <- function(video_path, frame=0){ animl_py <- get("animl_py", envir = parent.env(environment())) - animl_py$get_frame_as_image(video_path, frame=frame) + animl_py$get_frame_as_image(video_path, frame=as.integer(frame)) } \ No newline at end of file From da57b80f20c0820639374e67fc364d6fa824226d Mon Sep 17 00:00:00 2001 From: Kyra Swanson Date: Mon, 2 Feb 2026 13:13:49 -0800 Subject: [PATCH 06/10] change anml_py variable access --- R/classification.R | 9 ++++----- R/detection.R | 7 ++++--- R/export.R | 10 +++++----- R/file_management.R | 10 +++++----- R/install.R | 7 +++---- R/reid.R | 14 +++++++------- R/split.R | 2 +- R/train.R | 4 ++-- R/video_processing.R | 4 ++-- R/visualization.R | 4 ++-- R/zzz.R | 1 - examples/Workflow_animl.R | 6 +++--- 12 files changed, 38 insertions(+), 40 deletions(-) diff --git a/R/classification.R b/R/classification.R index 415a10a..2d77cff 100644 --- a/R/classification.R +++ b/R/classification.R @@ -13,8 +13,7 @@ #' classes <- load_class_list('sdzwa_andes_v1_classes.csv') #' andes <- load_classifier('andes_v1.pt', nrow(classes))} load_classifier <- function(model_path, classes, device=NULL, architecture="CTL"){ - animl_py <- get("animl_py", envir = parent.env(environment())) - + animl_py <- .animl_internal$animl_py if(is.numeric(classes)){ classes = as.integer(classes)} animl_py$load_classifier(model_path, classes, device=device, architecture=architecture) } @@ -35,7 +34,7 @@ load_classifier <- function(model_path, classes, device=NULL, architecture="CTL" #' @examples #' \dontrun{save_classifier(model, 'models/', 10, list(acc = 0.85))} save_classifier <- function(model, out_dir, epoch, stats, optimizer=NULL, scheduler=NULL){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$save_classifier(model, out_dir, epoch, reticulate::r_to_py(stats), optimizer=optimizer, scheduler=scheduler) } @@ -78,7 +77,7 @@ classify <- function(model, detections, batch_size=1, num_workers=1, device=NULL, out_file=NULL){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$classify(model, detections, resize_width=as.integer(resize_width), resize_height=as.integer(resize_height), @@ -102,6 +101,6 @@ classify <- function(model, detections, #' @examples #' \dontrun{animals <- single_classification(animals, empty, pred_raw, class_list)} single_classification <- function(animals, empty, predictions_raw, class_list){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$single_classification(animals, empty, predictions_raw, class_list) } diff --git a/R/detection.R b/R/detection.R index 02e19fc..0fe0af6 100644 --- a/R/detection.R +++ b/R/detection.R @@ -11,7 +11,7 @@ #' \dontrun{md_py <- megadetector("/mnt/machinelearning/megaDetector/md_v5a.0.0.pt", #' model_type='mdv5', device='cuda:0')} load_detector <- function(model_path, model_type, device=NULL){ - # first check if animl-py is loaded + animl_py <- .animl_internal$animl_py animl_py$load_detector(model_path, model_type=model_type, device=device) } @@ -40,7 +40,8 @@ detect <- function(detector, image_file_names, resize_width, resize_height, letterbox=TRUE, confidence_threshold=0.1, file_col='filepath', batch_size=1, num_workers=1, device=NULL, checkpoint_path=NULL, checkpoint_frequency=-1){ - animl_py <- get("animl_py", envir = parent.env(environment())) + + animl_py <- .animl_internal$animl_py animl_py$detect(detector, image_file_names, as.integer(resize_width), as.integer(resize_height), letterbox=letterbox, confidence_threshold=confidence_threshold, @@ -67,7 +68,7 @@ detect <- function(detector, image_file_names, resize_width, resize_height, #' mdresults <- parseMD(mdres) #' } parse_detections <- function(results, manifest=NULL, out_file=NULL, threshold=0, file_col="filepath") { - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$parse_detections(results, manifest=manifest, out_file=out_file, threshold=threshold, file_col=file_col) } diff --git a/R/export.R b/R/export.R index c73c2ca..3eb041e 100644 --- a/R/export.R +++ b/R/export.R @@ -18,7 +18,7 @@ export_folders <- function(manifest, out_dir, out_file=NULL, label_col="prediction", file_col="filepath", unique_name='uniquename', copy=FALSE) { - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py manifest <- animl_py$export_folders(manifest, out_dir, out_file, label_col=label_col, file_col=file_col, unique_name=unique_name, copy=copy) @@ -87,7 +87,7 @@ update_labels_from_folders <- function(manifest, export_dir, unique_name='unique #' @examples #' \dontrun{export_megadetector(manifest, output_file= 'results.json', detector='MDv6')} export_coco <- function(manifest, class_list, out_file, info=NULL, licenses=NULL){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$export_coco(manifest, class_list, out_file, info=info, licenses=licenses) } @@ -114,7 +114,7 @@ export_coco <- function(manifest, class_list, out_file, info=NULL, licenses=NULL export_camtrapR <- function(manifest, out_dir, out_file=NULL, label_col='prediction', file_col="filepath", station_col='station', unique_name='uniquename', copy=FALSE){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$export_camtrapR(manifest, out_dir, out_file=out_file, label_col=label_col, file_col=file_col, station_col=station_col, unique_name=unique_name, copy=copy) @@ -133,7 +133,7 @@ export_camtrapR <- function(manifest, out_dir, out_file=NULL, label_col='predict #' @examples #' \dontrun{export_timelapse(animals, empty, '/path/to/images/')} export_timelapse <- function(results, image_dir, only_animal=TRUE){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$export_timelapse(results, image_dir, only_animal=only_animal) } @@ -152,7 +152,7 @@ export_timelapse <- function(results, image_dir, only_animal=TRUE){ #' \dontrun{export_megadetector(manifest, output_file= 'results.json', detector='MDv6')} export_megadetector <- function(manifest, out_file=NULL, detector='MegaDetector v5a', prompt=TRUE){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$export_megadetector(manifest, out_file=out_file, detector=detector, prompt=prompt) } diff --git a/R/file_management.R b/R/file_management.R index 311321e..5f26c6c 100644 --- a/R/file_management.R +++ b/R/file_management.R @@ -22,7 +22,7 @@ #' } build_file_manifest <- function(image_dir, exif=TRUE, out_file=NULL, offset=0, recursive=TRUE) { - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py manifest <- animl_py$build_file_manifest(image_dir, exif=exif, out_file=out_file, offset=offset, recursive=recursive) return(manifest) } @@ -145,7 +145,7 @@ check_file <- function(file, output_type) { #' save_json(mdresults, 'mdraw.json') #' } save_json <- function(data, out_file, prompt=TRUE){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$save_json(data, out_file, prompt=prompt) } @@ -162,7 +162,7 @@ save_json <- function(data, out_file, prompt=TRUE){ #' mdraw <- load_json('mdraw.json') #' } load_json <- function(file){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$load_json(file) } @@ -181,7 +181,7 @@ load_json <- function(file){ #' download_model("https://models.com/path/to/model.pt", out_dir='models') #' } download_model <- function(model_url, out_dir='models'){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$download_model(model_url, out_dir = out_dir) } @@ -196,6 +196,6 @@ download_model <- function(model_url, out_dir='models'){ #' download_model("https://models.com/path/to/model.pt", out_dir='models') #' } list_models <- function(){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$list_models() } \ No newline at end of file diff --git a/R/install.R b/R/install.R index 3910fb6..06194de 100644 --- a/R/install.R +++ b/R/install.R @@ -23,7 +23,6 @@ load_animl <- function(envname = "animl_env", msg <- sprintf(paste0("%s python environment not found. Run animl::animl_install().\n", "See `?animl::animl_install_instructions` for more detail."), envname) packageStartupMessage(msg) - return(NULL) } # env exists else{ @@ -37,21 +36,21 @@ load_animl <- function(envname = "animl_env", # tell user to update animl-py packageStartupMessage(paste0("animl-py version conflicts with current version.\n", "To update animl-py, run animl::update_animl_py().")) - return(NULL) } # correct version else{ packageStartupMessage(paste0("animl successfully loaded.")) - return(animl_py) + # assign to internal variable + assign("animl_py", animl_py, envir = .animl_internal) } } # animl_env exists but animl-py not installed else{ packageStartupMessage(paste0("Python environment found but animl-py not installed.\n", "See `?animl::animl_install_instructions`.")) - return(NULL) } } + invisible() } diff --git a/R/reid.R b/R/reid.R index f995b1f..e3ff548 100644 --- a/R/reid.R +++ b/R/reid.R @@ -9,7 +9,7 @@ #' @examples #' \dontrun{miew = load_miewid("miewid_v3.bin")} load_miew <- function(file_path, device=NULL){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$load_miew(file_path, device=device) } @@ -30,7 +30,7 @@ load_miew <- function(file_path, device=NULL){ #' \dontrun{embeddings = extract_embeddings(miew, manifest)} extract_miew_embeddings <- function(miew_model, manifest, file_col="filepath", batch_size=1, num_workers=1, device=NULL){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$extract_miew_embeddings(miew_model, manifest, file_col=file_col, batch_size=as.integer(batch_size), num_workers=as.integer(num_workers), @@ -48,7 +48,7 @@ extract_miew_embeddings <- function(miew_model, manifest, file_col="filepath", #' @examples #' \dontrun{cleaned_dist <- remove_diagonal(dist_matrix)} remove_diagonal <- function(A){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$remove_diagonal(A) } @@ -63,7 +63,7 @@ remove_diagonal <- function(A){ #' @examples #' \dontrun{dist_matrix <- euclidean_squared_distance(embeddings, embeddings)} euclidean_squared_distance <- function(input1, input2){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$euclidean_squared_distance(input1, input2) } @@ -78,7 +78,7 @@ euclidean_squared_distance <- function(input1, input2){ #' @examples #' \dontrun{dist_matrix <- cosine_distance(embeddings, embeddings)} cosine_distance <- function(input1, input2){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$cosine_distance(input1, input2) } @@ -96,7 +96,7 @@ cosine_distance <- function(input1, input2){ #' @examples #' \dontrun{dist_matrix <- compute_distance_matrix(embeddings, embeddings, metric='cosine')} compute_distance_matrix <- function(input1, input2, metric='euclidean'){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$compute_distance_matrix(input1, input2, metric=metric) } @@ -115,6 +115,6 @@ compute_distance_matrix <- function(input1, input2, metric='euclidean'){ #' dist_matrix <- compute_batched_distance_matrix(query_embeddings, database_embeddings, #' metric='cosine', batch_size=12)} compute_batched_distance_matrix <- function(input1, input2, metric='cosine', batch_size=10){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$compute_batched_distance_matrix(input1, input2, metric=metric, batch_size=batch_size) } \ No newline at end of file diff --git a/R/split.R b/R/split.R index 13f86c9..c223e51 100644 --- a/R/split.R +++ b/R/split.R @@ -77,7 +77,7 @@ get_animals <- function(manifest){ train_val_test <- function(manifest, label_col="class", file_col='filepath', conf_col = 'confidence', out_dir=NULL, val_size= 0.1, test_size = 0.1, seed=42){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$train_val_test(manifest, label_col="class", file_col='filepath', conf_col = 'confidence', out_dir=NULL, val_size= 0.1, test_size = 0.1, seed=42) diff --git a/R/train.R b/R/train.R index 1a86f1d..ccc5b32 100644 --- a/R/train.R +++ b/R/train.R @@ -16,7 +16,7 @@ #' @examples #' \dontrun{train_main('training_cfg.yml')} train_main <- function(cfg){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$train_main(cfg) } @@ -31,6 +31,6 @@ train_main <- function(cfg){ #' @examples #' \dontrun{test_main('training_cfg.yml')} test_main <- function(cfg){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$test_main(cfg) } \ No newline at end of file diff --git a/R/video_processing.R b/R/video_processing.R index ca6aa48..9841c3f 100644 --- a/R/video_processing.R +++ b/R/video_processing.R @@ -20,7 +20,7 @@ extract_frames <- function(files, frames=5, fps=NULL, out_file=NULL, out_dir=NUL file_col="filepath", parallel=FALSE, num_workers=1){ if (!is.null(fps)){ fps <- as.integer(fps) } if (!is.null(frames)){ frames <- as.integer(frames) } - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$extract_frames(files, frames=frames, fps=fps, out_file=out_file, out_dir=out_dir, file_col=file_col, parallel=parallel, num_workers=as.integer(num_workers)) } @@ -38,6 +38,6 @@ extract_frames <- function(files, frames=5, fps=NULL, out_file=NULL, out_dir=NUL #' @examples #' \dontrun{get_frame_as_image('/example/path/to/video.mp4', frame=213)} get_frame_as_image <- function(video_path, frame=0){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$get_frame_as_image(video_path, frame=frame) } \ No newline at end of file diff --git a/R/visualization.R b/R/visualization.R index 72290df..768c1a7 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -20,7 +20,7 @@ plot_box <- function(rows, file_col='filepath', min_conf=0, label_col=NULL, show_confidence=FALSE, colors=NULL, detector_labels=NULL, return_img=FALSE) { - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$plot_box(rows, file_col=file_col, min_conf=min_conf, label_col=label_col, show_confidence=show_confidence, colors=colors, detector_labels=detector_labels, return_img=return_img) @@ -50,7 +50,7 @@ plot_box <- function(rows, file_col='filepath', min_conf=0, label_col=NULL, plot_all_bounding_boxes <- function(manifest, out_dir, file_col='filepath', min_conf=0.1, label_col=FALSE, show_confidence=FALSE, colors=NULL, detector_labels=NULL){ - animl_py <- get("animl_py", envir = parent.env(environment())) + animl_py <- .animl_internal$animl_py animl_py$plot_all_bounding_boxes(manifest, out_dir, file_col=file_col, min_conf=min_conf, label_col=label_col, show_confidence=show_confidence, colors=colors, detector_labels=detector_labels) diff --git a/R/zzz.R b/R/zzz.R index d584289..c59b5ba 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -3,7 +3,6 @@ .onLoad <- function(libname, pkgname) { animl_py <- load_animl() - assign("animl_py", animl_py, envir = .animl_internal) invisible() } diff --git a/examples/Workflow_animl.R b/examples/Workflow_animl.R index 633b562..06bcb3f 100644 --- a/examples/Workflow_animl.R +++ b/examples/Workflow_animl.R @@ -8,6 +8,7 @@ # Setup #------------------------------------------------------------------------------- library(animl) +#load_animl() imagedir <- "C:\\Users\\Kyra\\animl\\examples\\Southwest" imagedir <- "examples/Southwest" @@ -16,7 +17,7 @@ imagedir <- "examples/Southwest" WorkingDirectory(imagedir, globalenv()) # Build file manifest for all images and videos within base directory -files <- build_file_manifest(imagedir, exif=TRUE) +files <- build_file_manifest(imagedir, out_file = filemanifest_file, exif=TRUE) #=============================================================================== # Add Project-Specific Info @@ -75,8 +76,7 @@ manifest <- sequence_classification(animals, empty=empty, pred_raw, classes=clas #=============================================================================== #symlink species predictions -alldata <- export_folders(manifest, linkdir) -write.csv(alldata, results) +alldata <- export_folders(manifest, linkdir, out_file = results_file) #=============================================================================== From 77ac49f4f3051a38ea250d9b9d76affe5eca444e Mon Sep 17 00:00:00 2001 From: Kyra Swanson Date: Mon, 2 Feb 2026 14:13:37 -0800 Subject: [PATCH 07/10] update docs --- R/install.R | 21 ++++++++++++++++++++- R/zzz.R | 22 ---------------------- examples/Workflow_animl.R | 2 +- man/animl_install_instructions.Rd | 2 +- man/export_folders.Rd | 4 ++-- man/export_timelapse.Rd | 6 +++--- man/extract_frames.Rd | 2 +- man/save_classifier.Rd | 14 ++++++++++++++ man/single_classification.Rd | 10 +++++++++- man/update_labels_from_folders.Rd | 2 +- 10 files changed, 52 insertions(+), 33 deletions(-) diff --git a/R/install.R b/R/install.R index 06194de..3dcc22f 100644 --- a/R/install.R +++ b/R/install.R @@ -209,7 +209,6 @@ check_python <- function(python_version = "3.12", initialize = TRUE) { } - #' Delete the animl_env environment #' #' @param envname python environment to remove @@ -231,3 +230,23 @@ delete_pyenv <- function(envname = "animl_env") { try(reticulate::virtualenv_remove(envname), silent = TRUE) } + + +#' Installation Instructions for animl-r Python dependencies +#' +#' +#' @export +animl_install_instructions <- function() { + cat( + "animl: instructions to prepare a Python environment for optional features\n\n", + "Run animl::animl_install() to set up Python 3.12 environment and install animl-py dependency.\n\n", + "Virtualenv/pip alternative (requires python 3.12 installed):\n", + " python -m venv ~/venvs/animl_env\n", + " source ~/venvs/animl_env/bin/activate\n", + " pip install --upgrade pip\n", + " pip install animl\n\n", + "Restart R session and reload animl library.", + sep = "" + ) + invisible(NULL) +} diff --git a/R/zzz.R b/R/zzz.R index c59b5ba..a0955e6 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -24,26 +24,4 @@ packageStartupMessage(pkgmsg) } invisible() -} - - -#' Installation Instructions for animl-r Python dependencies -#' -#' -#' @export -animl_install_instructions <- function() { - cat( - "animl: instructions to prepare a Python environment for optional features\n\n", - "Run animl::animl_install() to set up Python 3.12 environment and install animl-py dependency.\n\n", - "Virtualenv/pip alternative (requires python 3.12 installed):\n", - " python -m venv ~/venvs/animl_env\n", - " source ~/venvs/animl_env/bin/activate\n", - " pip install --upgrade pip\n", - " pip install numpy pandas # add other required pkgs\n", - " In R: reticulate::use_python('~/venvs/animl-py/bin/python', required = TRUE)\n\n", - "Note: Do NOT rely on automatic installation from inside the package; ", - "install Python packages manually and configure reticulate.", - sep = "" - ) - invisible(NULL) } \ No newline at end of file diff --git a/examples/Workflow_animl.R b/examples/Workflow_animl.R index 06bcb3f..9d4bda8 100644 --- a/examples/Workflow_animl.R +++ b/examples/Workflow_animl.R @@ -17,7 +17,7 @@ imagedir <- "examples/Southwest" WorkingDirectory(imagedir, globalenv()) # Build file manifest for all images and videos within base directory -files <- build_file_manifest(imagedir, out_file = filemanifest_file, exif=TRUE) +files <- build_file_manifest(imagedir, out_file=filemanifest_file, exif=TRUE) #=============================================================================== # Add Project-Specific Info diff --git a/man/animl_install_instructions.Rd b/man/animl_install_instructions.Rd index fc8f62d..f902f94 100644 --- a/man/animl_install_instructions.Rd +++ b/man/animl_install_instructions.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/zzz.R +% Please edit documentation in R/install.R \name{animl_install_instructions} \alias{animl_install_instructions} \title{Installation Instructions for animl-r Python dependencies} diff --git a/man/export_folders.Rd b/man/export_folders.Rd index 3b2e750..62d05f1 100644 --- a/man/export_folders.Rd +++ b/man/export_folders.Rd @@ -17,11 +17,11 @@ export_folders( \arguments{ \item{manifest}{DataFrame of classified images} -\item{out_dir}{Destination directory for symlinks} +\item{out_dir}{Destination directory for species folders} \item{out_file}{if provided, save the manifest to this file} -\item{label_col}{(str): specify 'prediction' for species or 'category' for megadetector class} +\item{label_col}{specify 'prediction' for species or 'category' for megadetector class} \item{file_col}{Colun containing file paths} diff --git a/man/export_timelapse.Rd b/man/export_timelapse.Rd index afc89d5..69173c7 100644 --- a/man/export_timelapse.Rd +++ b/man/export_timelapse.Rd @@ -4,12 +4,12 @@ \alias{export_timelapse} \title{Converts the Manifests to a csv file that contains columns needed for TimeLapse conversion in later step} \usage{ -export_timelapse(results, image_dir, only_animal = TRUE) +export_timelapse(manifest, out_dir, only_animal = TRUE) } \arguments{ -\item{results}{a DataFrame that has entries of anuimal classification} +\item{manifest}{a DataFrame that has entries of anuimal classification} -\item{image_dir}{location of root directory where all images are stored (can contain subdirectories)} +\item{out_dir}{location of root directory where all images are stored (can contain subdirectories)} \item{only_animal}{A bool that confirms whether we want only animal detctions or all} } diff --git a/man/extract_frames.Rd b/man/extract_frames.Rd index 850511f..ca0bd52 100644 --- a/man/extract_frames.Rd +++ b/man/extract_frames.Rd @@ -11,7 +11,7 @@ extract_frames( out_file = NULL, out_dir = NULL, file_col = "filepath", - parallel = FALSE, + parallel = TRUE, num_workers = 1 ) } diff --git a/man/save_classifier.Rd b/man/save_classifier.Rd index c7d1ecb..3effba3 100644 --- a/man/save_classifier.Rd +++ b/man/save_classifier.Rd @@ -4,6 +4,15 @@ \alias{save_classifier} \title{Save model state weights} \usage{ +save_classifier( + model, + out_dir, + epoch, + stats, + optimizer = NULL, + scheduler = NULL +) + save_classifier( model, out_dir, @@ -27,11 +36,16 @@ save_classifier( \item{scheduler}{pytorch scheduler (optional)} } \value{ +None + None } \description{ +Save model state weights + Save model state weights } \examples{ \dontrun{save_classifier(model, 'models/', 10, list(acc = 0.85))} +\dontrun{save_classifier(model, 'models/', 10, list(acc = 0.85))} } diff --git a/man/single_classification.Rd b/man/single_classification.Rd index 1ddd9a9..3c66b95 100644 --- a/man/single_classification.Rd +++ b/man/single_classification.Rd @@ -4,7 +4,13 @@ \alias{single_classification} \title{Get Maximum likelihood label for each Detection} \usage{ -single_classification(animals, empty, predictions_raw, class_list) +single_classification( + animals, + empty, + predictions_raw, + class_list, + best = FALSE +) } \arguments{ \item{animals}{manifest of animal detections} @@ -14,6 +20,8 @@ single_classification(animals, empty, predictions_raw, class_list) \item{predictions_raw}{softmaxed likelihoods from predict_species} \item{class_list}{list of class labels} + +\item{best}{whether to return one prediction per file} } \value{ dataframe with prediction and confidence columns diff --git a/man/update_labels_from_folders.Rd b/man/update_labels_from_folders.Rd index d47133c..806ddd9 100644 --- a/man/update_labels_from_folders.Rd +++ b/man/update_labels_from_folders.Rd @@ -11,7 +11,7 @@ update_labels_from_folders(manifest, export_dir, unique_name = "uniquename") \item{export_dir}{directory to sort files into} -\item{unique_name}{column name indicating a unique file name for each row} +\item{unique_name}{column containing unique file names} } \value{ dataframe with new "Species" column that contains the verified species From 9647e3ef2c72ca1b7105426fb5b94d8a1b632d62 Mon Sep 17 00:00:00 2001 From: Kyra Swanson Date: Mon, 2 Feb 2026 14:23:31 -0800 Subject: [PATCH 08/10] update onAttach message --- R/zzz.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/zzz.R b/R/zzz.R index a0955e6..498e708 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -12,7 +12,7 @@ if (interactive()) { if (requireNamespace("reticulate", quietly = TRUE)) { pkgmsg <- paste0( - "animl: requires Python 3.12 and animl-py 3.1.1. If animl fails to load, ", + "animl: requires Python 3.12 and animl-py 3.2.0. If animl fails to load, ", "see `?animl::animl_install_instructions`." ) } else { From 1f4a2c383c8b53c6a490d24d7ed6611058b05b0b Mon Sep 17 00:00:00 2001 From: Kyra Swanson Date: Tue, 3 Feb 2026 16:25:44 -0800 Subject: [PATCH 09/10] increase default num_workers for video processing to 4 --- .gitignore | 1 + R/video_processing.R | 4 ++-- examples/Workflow_animl.R | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 45b3942..2dfe3f5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ inst/doc .ipynb animl-py/src/animl/__pycache__/** animl-py/src/animl/.ipynb_checkpoints/** +examples/** \ No newline at end of file diff --git a/R/video_processing.R b/R/video_processing.R index ebfc3cb..a6640c3 100644 --- a/R/video_processing.R +++ b/R/video_processing.R @@ -7,7 +7,7 @@ #' @param out_dir directory to save frames to if not null #' @param file_col string value indexing which column contains file paths #' @param parallel Toggle for parallel processing, defaults to FALSE -#' @param num_workers number of processors to use if parallel, defaults to 1 +#' @param num_workers number of processors to use if parallel, defaults to 4 #' #' @return dataframe of still frames for each video #' @export @@ -17,7 +17,7 @@ #' frames <- extract_frames(manifest, out_dir = "C:\\Users\\usr\\Videos\\", frames = 5) #' } extract_frames <- function(files, frames=5, fps=NULL, out_file=NULL, out_dir=NULL, - file_col="filepath", parallel=TRUE, num_workers=1){ + file_col="filepath", parallel=TRUE, num_workers=4){ if (!is.null(fps)){ fps <- as.integer(fps) } if (!is.null(frames)){ frames <- as.integer(frames) } animl_py <- .animl_internal$animl_py diff --git a/examples/Workflow_animl.R b/examples/Workflow_animl.R index 9d4bda8..6a9cb92 100644 --- a/examples/Workflow_animl.R +++ b/examples/Workflow_animl.R @@ -8,7 +8,6 @@ # Setup #------------------------------------------------------------------------------- library(animl) -#load_animl() imagedir <- "C:\\Users\\Kyra\\animl\\examples\\Southwest" imagedir <- "examples/Southwest" From ac582895ade099d887ac1f30ee10cb5615dc2b75 Mon Sep 17 00:00:00 2001 From: Kyra Swanson Date: Tue, 3 Feb 2026 16:37:42 -0800 Subject: [PATCH 10/10] update default value for num_workers --- man/extract_frames.Rd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/extract_frames.Rd b/man/extract_frames.Rd index ca0bd52..f7af8c1 100644 --- a/man/extract_frames.Rd +++ b/man/extract_frames.Rd @@ -12,7 +12,7 @@ extract_frames( out_dir = NULL, file_col = "filepath", parallel = TRUE, - num_workers = 1 + num_workers = 4 ) } \arguments{ @@ -30,7 +30,7 @@ extract_frames( \item{parallel}{Toggle for parallel processing, defaults to FALSE} -\item{num_workers}{number of processors to use if parallel, defaults to 1} +\item{num_workers}{number of processors to use if parallel, defaults to 4} } \value{ dataframe of still frames for each video