| Title: | Preprocessing Trap Camera Data for Machine Learning Models |
|---|---|
| Description: | Provides tools for preprocessing camera trap image data to prepare it for training machine learning models. It automates tasks such as extracting EXIF metadata, processing and analyzing image attributes, selecting relevant images (e.g., daytime photos), sampling subsets, and managing file organization. These features streamline the workflow for ecological research and wildlife monitoring projects. Additionally, the package includes tools for generating exploratory visualizations and creating annotation datasets. |
| Authors: | Edlin Guerra-Castro [aut, cph] (ORCID: <https://orcid.org/0000-0003-3529-4507>), Arturo Sanchez-Porras [aut, cre] (ORCID: <https://orcid.org/0000-0002-1691-286X>) |
| Maintainer: | Arturo Sanchez-Porras <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 0.3.0 |
| Built: | 2026-10-14 18:58:02 UTC |
| Source: | https://github.com/cran/cam2model |
fs
Copies files listed in a data frame into destination subfolders of fixed size. This is useful when a large image collection must be reorganized into evenly sized groups (for example, 250 files per folder) while preserving a record of the operation.
copy_in_batches( df, output_dir, batch_size = 250L, folder_prefix = "batch_", rename_with_id = TRUE, overwrite = FALSE, dry_run = FALSE, verbose = TRUE )copy_in_batches( df, output_dir, batch_size = 250L, folder_prefix = "batch_", rename_with_id = TRUE, overwrite = FALSE, dry_run = FALSE, verbose = TRUE )
df |
A |
output_dir |
Character scalar. Root directory where batch folders will be created. |
batch_size |
Integer scalar. Number of files per destination folder.
Default is |
folder_prefix |
Character scalar. Prefix used to name batch folders.
Default is |
rename_with_id |
Logical. If |
overwrite |
Logical. If |
dry_run |
Logical. If |
verbose |
Logical. If |
Files are assigned to folders according to their row order in df.
Expected minimum input columns (contract): df must include id and
path.
Produced columns: returns one row per valid input record with id,
path, source_exists, batch_id, batch_folder_name, dest_file_name,
dest_path, copied, and status.
I/O side-effects: creates output_dir and batch subfolders when
dry_run = FALSE; copies files into those subfolders unless dry_run = TRUE.
The function creates one folder per batch using zero-padded sequential numbering, for example:
batch_001
batch_002
batch_003
Destination file names are generated from the source file name. When
rename_with_id = TRUE, the output name becomes:
"{id}_{original_filename}"
If duplicates remain within a batch, they are disambiguated with
base::make.unique().
The function returns a data frame describing the outcome for each row, including whether the source existed, the assigned batch, the destination path, and the copy status.
A data.frame with one row per valid input file and the following columns:
Identifier from the input data frame.
Original source path.
Logical; whether the source file existed.
Integer batch number assigned to the file.
Name of the destination batch folder.
Destination file name.
Full destination path.
Logical indicating whether the copy succeeded. NA in
dry_run mode.
Character status label such as "copied",
"source_not_found", "destination_exists_not_overwritten",
"copy_failed", or "dry_run".
rename_images(), process_metadata(), generate_plot(),
process_training_images()
# Create temporary source and output directories src_dir <- tempfile("cam2model_batch_src_") out_dir <- tempfile("cam2model_batch_out_") dir.create(src_dir, recursive = TRUE, showWarnings = FALSE) dir.create(out_dir, recursive = TRUE, showWarnings = FALSE) file1 <- file.path(src_dir, "img1.jpg") file2 <- file.path(src_dir, "img2.jpg") file3 <- file.path(src_dir, "img3.jpg") writeBin(charToRaw("a"), file1) writeBin(charToRaw("b"), file2) writeBin(charToRaw("c"), file3) df <- data.frame( id = c("A01", "A02", "A03"), path = c(file1, file2, file3), stringsAsFactors = FALSE ) # Dry run: inspect the planned organization without copying report_dry <- copy_in_batches( df = df, output_dir = out_dir, batch_size = 2, dry_run = TRUE, verbose = FALSE ) report_dry[, c("id", "batch_folder_name", "dest_file_name", "status")] # Actual copy report <- copy_in_batches( df = df, output_dir = out_dir, batch_size = 2, dry_run = FALSE, verbose = FALSE ) report[, c("id", "dest_path", "status")]# Create temporary source and output directories src_dir <- tempfile("cam2model_batch_src_") out_dir <- tempfile("cam2model_batch_out_") dir.create(src_dir, recursive = TRUE, showWarnings = FALSE) dir.create(out_dir, recursive = TRUE, showWarnings = FALSE) file1 <- file.path(src_dir, "img1.jpg") file2 <- file.path(src_dir, "img2.jpg") file3 <- file.path(src_dir, "img3.jpg") writeBin(charToRaw("a"), file1) writeBin(charToRaw("b"), file2) writeBin(charToRaw("c"), file3) df <- data.frame( id = c("A01", "A02", "A03"), path = c(file1, file2, file3), stringsAsFactors = FALSE ) # Dry run: inspect the planned organization without copying report_dry <- copy_in_batches( df = df, output_dir = out_dir, batch_size = 2, dry_run = TRUE, verbose = FALSE ) report_dry[, c("id", "batch_folder_name", "dest_file_name", "status")] # Actual copy report <- copy_in_batches( df = df, output_dir = out_dir, batch_size = 2, dry_run = FALSE, verbose = FALSE ) report[, c("id", "dest_path", "status")]
Generates ggplot2 visualizations for processed camera-trap metadata,
with flexible x/y mappings, optional color/faceting, and optional filtering.
generate_plot( processed_metadata, x, y, color = NULL, facets = NULL, filter_expr = NULL, angle = 0 )generate_plot( processed_metadata, x, y, color = NULL, facets = NULL, filter_expr = NULL, angle = 0 )
processed_metadata |
A data frame containing processed metadata. |
x |
Bare column name to use for the x-axis. |
y |
Bare column name to use for the y-axis. |
color |
Bare column name (optional) to map to point/line color. |
facets |
Bare column name (optional) used in |
filter_expr |
Expression (optional) used to filter the metadata before plotting. |
angle |
Numeric. Angle for x-axis labels. Default is |
Expected minimum input columns (contract): the columns referenced in
x and y, and optionally those used in color, facets, and
filter_expr.
Produced columns/artifacts: none (no data is modified on disk). The
function returns a ggplot object combining geom_line() and geom_point().
I/O side-effects: none.
A ggplot object.
rename_images(), process_metadata(), process_training_images(),
copy_in_batches()
demo_metadata <- data.frame( Image_dttm = as.POSIXct("2024-08-01 00:00:00", tz = "UTC") + 0:5 * 3600, bLuma = c(100, 120, 140, 130, 150, 160), Camera = rep(c("C1", "C2"), each = 3), stringsAsFactors = FALSE ) p <- generate_plot( processed_metadata = demo_metadata, x = Image_dttm, y = bLuma, color = Camera, facets = Camera ) pdemo_metadata <- data.frame( Image_dttm = as.POSIXct("2024-08-01 00:00:00", tz = "UTC") + 0:5 * 3600, bLuma = c(100, 120, 140, 130, 150, 160), Camera = rep(c("C1", "C2"), each = 3), stringsAsFactors = FALSE ) p <- generate_plot( processed_metadata = demo_metadata, x = Image_dttm, y = bLuma, color = Camera, facets = Camera ) p
Extracts EXIF metadata from images in a directory, processes the
UserComment column, and saves the resulting metadata to a CSV file.
process_metadata(output_dir, output_file, UserComment = FALSE)process_metadata(output_dir, output_file, UserComment = FALSE)
output_dir |
Character. Path to the main directory containing camera folders with renamed images. |
output_file |
Character. Relative file path (from |
UserComment |
Logical. Default |
Expected minimum input columns (internal contract): none. This function
reads EXIF tags directly from image files under output_dir and appends a
Camera column derived from each first-level folder name.
Produced columns: varies by EXIF availability. Output always includes
available EXIF fields plus Camera; when UserComment = TRUE and parsing is
successful, it may also include Image_dttm, File_date, File_hms,
File_hour, File_minute, and parsed fields such as temp/bLuma.
I/O side-effects: writes one CSV to file.path(output_dir, output_file).
The function does not create missing parent directories for output_file.
A tibble with processed metadata. Typical contract columns used by
downstream functions include Directory and FileName (required by
process_training_images()).
rename_images(), generate_plot(), process_training_images(),
copy_in_batches()
## Not run: # Requires real image files with EXIF metadata. renamed_dir <- tempfile("cam2model_renamed_") dir.create(renamed_dir, recursive = TRUE) dir.create(file.path(renamed_dir, "camera_A"), recursive = TRUE) # Copy real camera-trap images into `renamed_dir/camera_A` before running. meta <- process_metadata( output_dir = renamed_dir, output_file = "metadata.csv", UserComment = FALSE ) file.exists(file.path(renamed_dir, "metadata.csv")) head(meta) ## End(Not run)## Not run: # Requires real image files with EXIF metadata. renamed_dir <- tempfile("cam2model_renamed_") dir.create(renamed_dir, recursive = TRUE) dir.create(file.path(renamed_dir, "camera_A"), recursive = TRUE) # Copy real camera-trap images into `renamed_dir/camera_A` before running. meta <- process_metadata( output_dir = renamed_dir, output_file = "metadata.csv", UserComment = FALSE ) file.exists(file.path(renamed_dir, "metadata.csv")) head(meta) ## End(Not run)
Filters metadata according to one or more rules, samples a per-directory proportion of images, optionally copies sampled images to a destination directory, and generates CSV files for bookkeeping and annotation.
process_training_images( metadata_file, dest_dir, output_sample_file, output_annotation_file, sample_proportion = 0.5, filters = NULL, min_per_dir = 1, max_per_dir = Inf, copy_images = FALSE, parallel = FALSE, workers = NULL, show_progress = TRUE )process_training_images( metadata_file, dest_dir, output_sample_file, output_annotation_file, sample_proportion = 0.5, filters = NULL, min_per_dir = 1, max_per_dir = Inf, copy_images = FALSE, parallel = FALSE, workers = NULL, show_progress = TRUE )
metadata_file |
Character. Path to the CSV file containing metadata. |
dest_dir |
Character. Path to the directory where outputs are written. |
output_sample_file |
Character. Name of the CSV file storing the sampled image list. |
output_annotation_file |
Character. Name of the CSV file storing the annotation template. |
sample_proportion |
Numeric in |
filters |
A list of filtering rules. Each rule must be a list with:
If |
min_per_dir |
Integer. Minimum number of images to sample per directory. |
max_per_dir |
Integer. Maximum number of images to sample per directory. |
copy_images |
Logical. If |
parallel |
Logical. If |
workers |
Integer or NULL. Number of workers for parallel copying. |
show_progress |
Logical. If |
Expected minimum input columns (contract): Directory and FileName
must be present in metadata_file.
Produced columns: returns sampled rows with at least Directory,
FileName, optional File_date/File_hour (if present in input), and
file_path; adds dest_file and copied when copy_images = TRUE.
I/O side-effects:
Creates dest_dir if it does not exist.
Writes sampled CSV to file.path(dest_dir, output_sample_file).
Writes annotation template CSV to
file.path(dest_dir, output_annotation_file).
Optionally copies sampled files into dest_dir when
copy_images = TRUE.
A tibble with sampled images and file paths used for training.
rename_images(), process_metadata(), generate_plot(),
copy_in_batches()
src_dir <- tempfile("cam2model_train_src_") dest_dir <- tempfile("cam2model_train_out_") dir.create(src_dir, recursive = TRUE) # Create minimal files that can be referenced from metadata f1 <- file.path(src_dir, "img1.jpg") f2 <- file.path(src_dir, "img2.jpg") writeBin(charToRaw("a"), f1) writeBin(charToRaw("b"), f2) metadata_tbl <- data.frame( Directory = c(src_dir, src_dir), FileName = c("img1.jpg", "img2.jpg"), File_hour = c(10, 14), stringsAsFactors = FALSE ) metadata_csv <- tempfile("cam2model_metadata_", fileext = ".csv") readr::write_csv(metadata_tbl, metadata_csv) sampled <- process_training_images( metadata_file = metadata_csv, dest_dir = dest_dir, output_sample_file = "sampled_images.csv", output_annotation_file = "annotation_data.csv", sample_proportion = 1, copy_images = FALSE, show_progress = FALSE, parallel = FALSE ) sampled file.exists(file.path(dest_dir, "sampled_images.csv")) file.exists(file.path(dest_dir, "annotation_data.csv"))src_dir <- tempfile("cam2model_train_src_") dest_dir <- tempfile("cam2model_train_out_") dir.create(src_dir, recursive = TRUE) # Create minimal files that can be referenced from metadata f1 <- file.path(src_dir, "img1.jpg") f2 <- file.path(src_dir, "img2.jpg") writeBin(charToRaw("a"), f1) writeBin(charToRaw("b"), f2) metadata_tbl <- data.frame( Directory = c(src_dir, src_dir), FileName = c("img1.jpg", "img2.jpg"), File_hour = c(10, 14), stringsAsFactors = FALSE ) metadata_csv <- tempfile("cam2model_metadata_", fileext = ".csv") readr::write_csv(metadata_tbl, metadata_csv) sampled <- process_training_images( metadata_file = metadata_csv, dest_dir = dest_dir, output_sample_file = "sampled_images.csv", output_annotation_file = "annotation_data.csv", sample_proportion = 1, copy_images = FALSE, show_progress = FALSE, parallel = FALSE ) sampled file.exists(file.path(dest_dir, "sampled_images.csv")) file.exists(file.path(dest_dir, "annotation_data.csv"))
This wrapper function builds a renaming plan and immediately executes it, allowing the user to flatten and rename all images from a sampling-site directory in a single call.
rename_images( input_dir, output_dir, site_name = NULL, recursive = TRUE, extensions = c("jpg", "jpeg", "png", "tif", "tiff"), keep_relative_path = TRUE, date_fields = c("DateTimeOriginal", "FileModifyDate"), parallel = FALSE, workers = NULL, overwrite = FALSE, return_plan = FALSE )rename_images( input_dir, output_dir, site_name = NULL, recursive = TRUE, extensions = c("jpg", "jpeg", "png", "tif", "tiff"), keep_relative_path = TRUE, date_fields = c("DateTimeOriginal", "FileModifyDate"), parallel = FALSE, workers = NULL, overwrite = FALSE, return_plan = FALSE )
input_dir |
Character. Path to the root directory of a sampling site. |
output_dir |
Character. Path to the root output directory. |
site_name |
Character or NULL. Name of the sampling site. If |
recursive |
Logical. Should files be searched recursively? Default |
extensions |
Character vector. Allowed image extensions. |
keep_relative_path |
Logical. If |
date_fields |
Character vector. EXIF date fields to try in order. |
parallel |
Logical. Should the renaming plan be built in parallel?
Default |
workers |
Integer or NULL. Number of workers when |
overwrite |
Logical. Should existing files be overwritten? Default |
return_plan |
Logical. If |
Expected minimum input columns (internal contract): none. The function
reads image files directly from input_dir and builds metadata internally.
Produced columns:
If return_plan = FALSE, returns the execution table with columns from the
renaming plan plus copied.
If return_plan = TRUE, returns a list with:
plan: planning table that includes original_path, new_name, and
new_path.
result: the same rows plus copied.
I/O side-effects: creates <output_dir>/<site_name> when needed and
copies files to that folder using standardized names.
A tibble with copy results, or a list containing both the plan and the execution result.
process_metadata(), generate_plot(), process_training_images(),
copy_in_batches()
## Not run: # Requires real image files on disk. raw_dir <- tempfile("cam2model_raw_") out_dir <- tempfile("cam2model_out_") dir.create(raw_dir, recursive = TRUE) dir.create(out_dir, recursive = TRUE) # Copy real camera-trap images into `raw_dir` before running. result <- rename_images( input_dir = raw_dir, output_dir = out_dir, site_name = "site_demo", overwrite = FALSE ) head(result) ## End(Not run)## Not run: # Requires real image files on disk. raw_dir <- tempfile("cam2model_raw_") out_dir <- tempfile("cam2model_out_") dir.create(raw_dir, recursive = TRUE) dir.create(out_dir, recursive = TRUE) # Copy real camera-trap images into `raw_dir` before running. result <- rename_images( input_dir = raw_dir, output_dir = out_dir, site_name = "site_demo", overwrite = FALSE ) head(result) ## End(Not run)