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/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 63dd3f3..7686fe6 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) @@ -24,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/classification.R b/R/classification.R index 415a10a..a16d74e 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 @@ -13,8 +32,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 +53,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 +96,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), @@ -95,13 +113,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){ - animl_py <- get("animl_py", envir = parent.env(environment())) - animl_py$single_classification(animals, empty, predictions_raw, class_list) +single_classification <- function(animals, empty, predictions_raw, class_list, best=FALSE){ + animl_py <- .animl_internal$animl_py + animl_py$single_classification(animals, empty, predictions_raw, class_list, best=best) } diff --git a/R/detection.R b/R/detection.R index 02e19fc..fe8d40c 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,8 +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..267b435 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 @@ -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) @@ -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 @@ -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) @@ -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){ - animl_py <- get("animl_py", envir = parent.env(environment())) - animl_py$export_timelapse(results, image_dir, only_animal=only_animal) +export_timelapse <- function(manifest, out_dir, only_animal=TRUE){ + animl_py <- .animl_internal$animl_py + animl_py$export_timelapse(manifest, out_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 e5916d8..3dcc22f 100644 --- a/R/install.R +++ b/R/install.R @@ -1,110 +1,175 @@ # VARIABLE FOR VERSION -ANIML_VERSION <- "3.1.1" +ANIML_VERSION <- "3.2.0" -#' 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 envname name of python environment #' @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)} -animl_install <- function(py_env = "animl_env", - animl_version = ANIML_VERSION, - python_version = "3.12", - confirm=TRUE) { +#' \dontrun{animl_install("animl_env", ANIML_VERSION, python_version="3.12")} +load_animl <- 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_condaenv(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) - #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) + #packageStartupMessage(try_error) + msg <- sprintf(paste0("%s python environment not found. Run animl::animl_install().\n", + "See `?animl::animl_install_instructions` for more detail."), envname) + packageStartupMessage(msg) } - # 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)){update_animl_py()} + 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().")) + } + # correct version + else{ + packageStartupMessage(paste0("animl successfully loaded.")) + # assign to internal variable + assign("animl_py", animl_py, envir = .animl_internal) + } } - # animl-py not yet installed + # animl_env exists but animl-py not installed else{ - packageStartupMessage("\n3. Installing animl-py...") - package <- sprintf("animl==%s", animl_version) - reticulate::use_condaenv(py_env) - reticulate::py_install(package, pip=TRUE) + packageStartupMessage(paste0("Python environment found but animl-py not installed.\n", + "See `?animl::animl_install_instructions`.")) } - return(TRUE) } + invisible() } #' 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()} -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) +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)...", 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)", 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") } + # animl_env exists else{ - packageStartupMessage('Animl load failed') - packageStartupMessage(reticulate::py_config()) + # check animl-py installed + packageStartupMessage("\n2. Checking animl-py version...") + if(reticulate::py_module_available("animl")){ + animl_py <- reticulate::import("animl", delay_load = TRUE) + current_version <- animl_py$'__version__' + # check version match + 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::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("animl-py version mismatch, reinstalling...") - reticulate::use_condaenv(py_env) - reticulate::py_install(sprintf("animl==%s", animl_version), pip=TRUE) + 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 #' +#' @param python_version version of python to install #' @param initialize load reticulate library if true #' #' @returns none @@ -112,50 +177,76 @@ 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) { + 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"]] + if (python_test) { + current_version <- reticulate::py_discover_config()[["version"]] + # check if correct 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.", current_version)) + } } - else { stop("Unable to find a Python installation.", - "Please install Python befor running animl_initiaialzer().", - "For more details run reticulate::py_discover_config()") + # no python installed at all + else { + packageStartupMessage(sprintf("Python %s not found, installing...", python_version)) + reticulate::install_python(version=python_version) } - if (utils::compareVersion(as.character(py_version), "3.12") == -1) { - stop("animl needs Python >=3.12") - } - packageStartupMessage(sprintf("Python version %s compatible with animl.", py_version)) + invisible() } - -#' Create the miniconda environment animl_env +#' Delete the animl_env environment #' -#' @param python_version python version for new environment -#' @param py_env name of python environment +#' @param envname python environment to remove #' -#' @returns python path for new environment +#' @returns none #' @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::conda_create(py_env, python_version = python_version) - print(pyenv_path) - pyenv_path +#' \dontrun{delete_pyenv('animl_env')} +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) } -#' Delete the animl_env environment -#' -#' @param py_env python environment to remove -#' -#' @returns none +#' Installation Instructions for animl-r Python dependencies +#' +#' #' @export -#' -#' @examples -#' \dontrun{delete_pyenv('animl_env')} -delete_pyenv <- function(py_env = "animl_env") { - try(reticulate::conda_remove(py_env), silent = TRUE) +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/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..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,10 +17,10 @@ #' 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=4){ 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$get_frame_as_image(video_path, frame=frame) + animl_py <- .animl_internal$animl_py + animl_py$get_frame_as_image(video_path, frame=as.integer(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 299c8e6..498e708 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,19 +1,27 @@ +.animl_internal <- new.env(parent = emptyenv()) +.animl_internal$animl_py <- NULL + .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() - } + animl_py <- load_animl() + 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.2.0. 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() } \ No newline at end of file diff --git a/examples/Workflow_animl.R b/examples/Workflow_animl.R index 8f6c46d..6a9cb92 100644 --- a/examples/Workflow_animl.R +++ b/examples/Workflow_animl.R @@ -75,8 +75,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) #=============================================================================== diff --git a/man/animl_install.Rd b/man/animl_install.Rd index b8c10eb..cbf548d 100644 --- a/man/animl_install.Rd +++ b/man/animl_install.Rd @@ -2,30 +2,21 @@ % 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", - animl_version = ANIML_VERSION, - python_version = "3.12", - confirm = TRUE -) +animl_install(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} - -\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..f902f94 --- /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/install.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..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 miniconda 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 miniconda 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/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..f7af8c1 100644 --- a/man/extract_frames.Rd +++ b/man/extract_frames.Rd @@ -11,8 +11,8 @@ extract_frames( out_file = NULL, out_dir = NULL, file_col = "filepath", - parallel = FALSE, - num_workers = 1 + parallel = TRUE, + 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 diff --git a/man/load_animl.Rd b/man/load_animl.Rd new file mode 100644 index 0000000..b12422e --- /dev/null +++ b/man/load_animl.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/install.R +\name{load_animl} +\alias{load_animl} +\title{Load animl-py if available} +\usage{ +load_animl(envname = "animl_env", python_version = "3.12") +} +\arguments{ +\item{envname}{name of python environment} + +\item{python_version}{version of python to install} +} +\value{ +none +} +\description{ +Load animl-py if available +} +\examples{ +\dontrun{animl_install("animl_env", ANIML_VERSION, python_version="3.12")} +} diff --git a/man/load_animl_py.Rd b/man/load_animl_py.Rd deleted file mode 100644 index 27664fa..0000000 --- a/man/load_animl_py.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/install.R -\name{load_animl_py} -\alias{load_animl_py} -\title{Load animl-py if available} -\usage{ -load_animl_py() -} -\value{ -animl-py module -} -\description{ -Load animl-py if available -} -\examples{ -\dontrun{animl_py <- load_animl_py()} -} 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_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")} } 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