Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
^.Rhistory
^.*\.Rproj$
^\.Rproj\.user$
^R/old/*
^R/old/*
^.git
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.RData
.Rhistory
.Rproj.user
inst/doc
Expand Down
3 changes: 1 addition & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: animl
Title: A Collection of ML Tools for Conservation Research
Version: 3.0.0
Version: 3.1.1
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.
Expand All @@ -12,7 +12,6 @@ Imports:
methods,
pbapply,
reticulate,
parallel,
stats,
Depends:
R (>= 4.0.0)
6 changes: 6 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,23 @@ export(delete_pyenv)
export(detect)
export(download_model)
export(euclidean_squared_distance)
export(export_camtrapR)
export(export_coco)
export(export_folders)
export(export_megadetector)
export(export_timelapse)
export(extract_frames)
export(extract_miew_embeddings)
export(get_animals)
export(get_empty)
export(get_frame_as_image)
export(list_models)
export(load_animl_py)
export(load_class_list)
export(load_classifier)
export(load_data)
export(load_detector)
export(load_json)
export(load_miew)
export(parse_detections)
export(plot_all_bounding_boxes)
Expand All @@ -34,10 +38,12 @@ export(plot_from_file)
export(remove_diagonal)
export(remove_link)
export(save_classifier)
export(save_json)
export(sequence_classification)
export(single_classification)
export(test_main)
export(train_main)
export(train_val_test)
export(update_animl_py)
export(update_labels_from_folders)
importFrom(methods,is)
43 changes: 25 additions & 18 deletions R/classification.R
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
#' Load a Classifier Model with animl-py
#' Load a Classifier Model and Class_list
#'
#' @param model_path path to model
#' @param len_classes path to class list
#' @param classes path to class list or loaded class list
#' @param device send model to the specified device
#' @param architecture model architecture
#'
#' @return classifier model
#' @return classifier model, class list
#' @export
#'
#' @examples
#' \dontrun{
#' classes <- load_class_list('sdzwa_andes_v1_classes.csv')
#' andes <- load_classifier('andes_v1.pt', nrow(classes))}
load_classifier <- function(model_path, len_classes, device=NULL, architecture="CTL"){
load_classifier <- function(model_path, classes, device=NULL, architecture="CTL"){
animl_py <- get("animl_py", envir = parent.env(environment()))
animl_py$load_classifier(model_path, as.integer(len_classes), device=device, architecture=architecture)

if(is.numeric(classes)){ classes = as.integer(classes)}
animl_py$load_classifier(model_path, classes, device=device, architecture=architecture)
}


Expand Down Expand Up @@ -47,38 +49,43 @@ save_classifier <- function(model, out_dir, epoch, stats, optimizer=NULL, schedu
#' @examples
#' \dontrun{classes <- load_class_list('andes_classes.csv')}
load_class_list <- function(classlist_file){
read.csv(classlist_file)
utils::read.csv(classlist_file)
}


#' Infer Species for Given Detections
#'
#' @param model loaded classifier model
#' @param detections manifest of animal detections
#' @param device send model to the specified device
#' @param out_file path to csv to save results to
#' @param resize_width image width input size
#' @param resize_height image height input size
#' @param file_col column in manifest containing file paths
#' @param crop use bbox to crop images before feeding into model
#' @param normalize normalize the tensor before inference
#' @param resize_width image width input size
#' @param resize_height image height input size
#' @param batch_size batch size for generator
#' @param num_workers number of processes
#' @param num_workers number of processes
#' @param device send model to the specified device
#' @param out_file path to csv to save results to
#'
#' @return detection manifest with added prediction and confidence columns
#' @export
#'
#' @examples
#' \dontrun{animals <- classify(classifier, animals, file_col='filepath')}
classify <- function(model, detections, device=NULL, out_file=NULL,
file_col='frame', crop=TRUE, normalize=TRUE,
classify <- function(model, detections,
resize_width=480, resize_height=480,
batch_size=1, num_workers=1){
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, device=device, out_file=out_file,
file_col=file_col, crop=crop, normalize=normalize,
resize_width=as.integer(resize_width), resize_height=as.integer(resize_height),
batch_size=as.integer(batch_size), num_workers=as.integer(num_workers))
animl_py$classify(model, detections,
resize_width=as.integer(resize_width),
resize_height=as.integer(resize_height),
file_col=file_col, crop=crop, normalize=normalize,
batch_size=as.integer(batch_size),
num_workers=as.integer(num_workers),
device=device, out_file=out_file)
}


Expand Down
17 changes: 9 additions & 8 deletions R/detection.R
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#' Load an Object Detector
#'
#' @param model_path path to detector model file
#' @param model_type type of model expected ie "MDV5", "MDV6", "YOLO"
#' @param model_type type of model expected ie "MDV5", "MDV6", "YOLO", "ONNX"
#' @param device specify to run on cpu or gpu
#'
#' @return megadetector object
#' @return detector object
#' @export
#'
#' @examples
#' \dontrun{md_py <- megadetector("/mnt/machinelearning/megaDetector/md_v5a.0.0.pt", model_type='mdv5', device = 'cuda:0')}
#' \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$load_detector(model_path, model_type=model_type, device=device)
Expand Down Expand Up @@ -36,7 +37,7 @@ load_detector <- function(model_path, model_type, device=NULL){
#' @examples
#' \dontrun{mdres <- detect(md_py, allframes$Frame, 1280, 960, device='cpu')}
detect <- function(detector, image_file_names, resize_width, resize_height,
letterbox=TRUE, confidence_threshold=0.1, file_col='frame',
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()))
Expand All @@ -50,11 +51,11 @@ detect <- function(detector, image_file_names, resize_width, resize_height,
}


#' parse MD results into a simple dataframe
#' Parse MD results into a simple dataframe
#'
#' @param results json output from megadetector
#' @param manifest dataframe containing all frames
#' @param out_file path to save dataframe
#' @param manifest optional dataframe containing all frames
#' @param out_file optional path to save dataframe
#' @param threshold confidence threshold to include bbox
#' @param file_col column in manifest that refers to file paths
#'
Expand All @@ -65,7 +66,7 @@ detect <- function(detector, image_file_names, resize_width, resize_height,
#' \dontrun{
#' mdresults <- parseMD(mdres)
#' }
parse_detections <- function(results, manifest=NULL, out_file=NULL, threshold=0, file_col="frame") {
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$parse_detections(results, manifest=manifest, out_file=out_file,
threshold=threshold, file_col=file_col)
Expand Down
84 changes: 68 additions & 16 deletions R/export.R
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,21 @@ remove_link <- function(manifest, link_col='link'){
#' Udate Results from File Browser
#'
#' @param manifest dataframe containing file data and predictions
#' @param link_dir directory to sort files into
#' @param export_dir directory to sort files into
#' @param unique_name column name indicating a unique file name for each row
#'
#' @return dataframe with new "Species" column that contains the verified species
#' @export
#'
#' @examples
#' \dontrun{
#' results <- update_labels_from_folders(manifest, link_dir)
#' results <- update_labels_from_folders(manifest, export_dir)
#' }
update_labels_from_folders <- function(manifest, link_dir, unique_name='uniquename'){
if (!dir.exists(link_dir)) {stop("The given directory does not exist.")}
update_labels_from_folders <- function(manifest, export_dir, unique_name='uniquename'){
if (!dir.exists(export_dir)) {stop("The given directory does not exist.")}
if (!unique_name %in% names(manifest)) {stop("Manifest does not have unique names, cannot match to sorted directories.")}

FilePath <- list.files(link_dir, recursive = TRUE, include.dirs = TRUE)
FilePath <- list.files(export_dir, recursive = TRUE, include.dirs = TRUE)
files <- data.frame(FilePath)

files[unique_name] <- sapply(files$FilePath,function(x)strsplit(x,"/")[[1]][2])
Expand All @@ -73,36 +73,88 @@ update_labels_from_folders <- function(manifest, link_dir, unique_name='uniquena
}


#' Converts the .csv file to the MD-formatted .json file.
#' Converts the .csv file to a COCO-formatted .json file.
#'
#' @param manifest dataframe containing images and associated detections
#' @param output_file path to save the MD formatted file
#' @param detector name of the detector model used
#' @param class_list dataframe containing class names and their corresponding IDs
#' @param out_file path to save the formatted file
#' @param info info section of COCO file, named list
#' @param licenses licenses section of COCO file, array
#'
#' @return None
#' @return coco formated json
#' @export
#'
#' @examples
#' \dontrun{export_megadetector(manifest, output_file= 'results.json', detector='MDv6')}
export_megadetector <- function(manifest, output_file=NULL, detector='MegaDetector v5a'){
export_coco <- function(manifest, class_list, out_file, info=NULL, licenses=NULL){
animl_py <- get("animl_py", envir = parent.env(environment()))
animl_py$export_megadetector(manifest, output_file=output_file, detector=detector)
animl_py$export_coco(manifest, class_list, out_file, info=info, licenses=licenses)
}



#' Export data into sorted folders organized by station
#'
#' @param manifest dataframe containing images and associated predictions
#' @param out_dir directory to export sorted images
#' @param out_file if provided, save the manifest to this file
#' @param label_col column containing species labels
#' @param file_col column containing source paths
#' @param station_col column containing station names
#' @param unique_name column containing unique file name
#' @param copy if true, hard copy
#'
#' @returns manifest with link column
#' @export
#'
#' @examples
#' \dontrun{manifest <- export_camtrapR(manifest, out_dir, out_file=NULL, label_col='prediction',
#' file_col="filepath", station_col='station',
#' unique_name='uniquename', copy=FALSE)}
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$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)
}


#' Converts the Manifests to a csv file that contains columns needed for TimeLapse conversion in later step
#'
#' @param animals a DataFrame that has entries of anuimal classification
#' @param empty a DataFrame that has detection of non-animal objects in images
#' @param imagedir location of root directory where all images are stored (can contain subdirectories)
#' @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 only_animal A bool that confirms whether we want only animal detctions or all
#'
#' @returns animals.csv, non-anim.csv, csv_loc
#' @export
#'
#' @examples
#' \dontrun{export_timelapse(animals, empty, '/path/to/images/')}
export_timelapse <- function(animals, empty, imagedir, only_animal=TRUE){
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)
}


#' Converts the .csv file to the MD-formatted .json file.
#'
#' @param manifest dataframe containing images and associated detections
#' @param out_file path to save the MD formatted file
#' @param detector name of the detector model used
#' @param prompt ask user to overwrite existing file
#'
#' @return None
#' @export
#'
#' @examples
#' \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$export_timelapse(animals, empty, imagedir, only_animal=only_animal)
animl_py$export_megadetector(manifest, out_file=out_file,
detector=detector, prompt=prompt)
}


Loading