R/uas_rename.R

Defines functions uas_rename

Documented in uas_rename

#' Rename UAS images
#'
#' Rename UAS images
#'
#' @param x A list of class 'uas_info'
#' @param flt Flight(s) in x to process (character or numeric vector, default is all)
#' @param name_template A template for generating the file names (see Details)
#' @param all_lower Make file names all lowercase, Logical
#' @param preview Preview the new names only
#' @param confirm Confirm continue before changing file names
#' @param flt_idx `r lifecycle::badge("deprecated")` Use `flt` instead
#'
#' @details This function will rename image files on disk based on a naming template which can include
#' placeholders (tokens) from image and/or flight metadata. This can be useful when you want
#' to rename your image files based on some formula of date-time parts, AGL altitude, flight metadata
#' fields, camera type, etc. Renaming image files can be helpful when you're doing analysis of individual images, but
#' is generally not needed when you're doing photogrammetry whereby you're more likely to be using directory
#' names to help you identify groups of images for stitching.
#'
#' \strong{Caution is advised} when using this function, because it will actually \strong{rename your files!} Use \code{preview = TRUE}
#' to test your naming template. When \code{preview = TRUE}, the function will return a tibble with the 'old' and 'new' names, but not actually change
#' any file names.
#'
#' \code{flt} allows you to specify a subset of image folders in \code{x} to process. You can pass a vector of flight names (use names(x)
#' to see what those are) or integers.
#'
#' When you're ready, set \code{preview = FALSE}. Aafter renaming files, you'll need to rerun
#' \code{\link{uas_info}} on the directory(s) to update the info.
#'
#' @section File Name Template:
#'
#' \code{name_template} should be a pattern containing placeholders (or 'tokens') in curly brackets. When you run the function, the tokens will be swapped out
#' for actual values.
#'
#' For example, if a filename is \emph{DJI_0213.JPG} and the name template was
#' \code{"img_{Y}-{m}-{d}_{H}-{M}-{S}"}, the file would be renamed something like
#' \emph{img_2018-06-17_13-04-46.jpg}, where the numbers for date and time are extracted from the
#' image EXIF data. Supported tokens you can use include:
#'
#' \itemize{
#'   \item \code{\{i\}} an integer starting from 1 and going up, padded with enough leading zeros to accommodate all values
#'   \item \code{\{Y\}} year (4-digit)
#'   \item \code{\{y\}} year (2-digit)
#'   \item \code{\{m\}} month (2-digit)
#'   \item \code{\{d\}} day (2-digit)
#'   \item \code{\{j\}} Julian day
#'   \item \code{\{H\}} hour (2-digits)
#'   \item \code{\{M\}} minutes (2-digits)
#'   \item \code{\{S\}} seconds (2-digits)
#'   \item \code{\{camera_abbrev\}} an abbreviated name of the camera
#'   \item \code{\{alt_agl\}} altitude above the launch point (usually in meters). You can indicate rounding by
#' adding a comma and the number of decimal places. For example \code{\{alt_agl,0\}} would round the AGL value to the nearest whole number.
#' This option is only available for images where the relative altitude is saved in the EXIF data (which excludes most multi-spectral images).
#' }
#'
#' In addition, name templates can include any flight metadata field that has been defined. For example if the flight metadata information
#' includes fields for \code{proj} (project abbreviation) and \code{loc} (location), the name template could include  \code{\{proj\}} and  \code{\{loc\}}.
#'
#' When creating your name template, remember:
#'
#' 1) All file names must come out unique. An easy way to ensure this is to include \code{\{i\}} in your name template, which will be replaced with a sequence of integers.
#'
#' 2) File names should not include characters that aren't allowed for file names. These include \code{< > : `"` / \\ | ? *}. If any of these characters are found in name_template, it will be rejected.
#'
#' 3) \code{name_template} should not contain an extension
#'
#' @return A tibble showing the old and new names for each image.
#'
#' @seealso \code{\link{uas_info}}, \code{\link{uas_metadata_make}}
#'
#' @importFrom stringr str_replace_all str_extract
#' @importFrom tools file_ext
#' @importFrom dplyr tibble
#' @importFrom crayon red
#' @importFrom lifecycle deprecated is_present deprecate_warn
#' @export

uas_rename <- function(x, flt = NULL,
                       name_template = "img{i}_{alt_agl}m_{camera_abbrev}_{Y}_{m}_{d}_{H}_{M}_{S}",
                       all_lower = TRUE, preview = TRUE, confirm = TRUE, flt_idx = deprecated()) {

  if (lifecycle::is_present(flt_idx)) {
    lifecycle::deprecate_warn("1.9.0", "uas_rename(flt_idx)", "uas_rename(flt)")
    flt <- flt_idx
  }

  if (!inherits(x, "uas_info")) stop("x should be of class \"uas_info\"")

  if (length(name_template) != 1) stop("name_template should be a character object with length 1")

  ## Make sure name_template doesn't contain an extension
  if (grepl("\\..{3}$", name_template)) stop("The name template should not contain an extension")

  ## Look for invalid characters
  if (grepl("<|>|:|/|\\\\|\\||\\?|\\*", name_template)) stop("name_template has one or more characters that are not allowed in file names")

  ## Verify that that value(s) in flt (if any) are valid
  if (is.null(flt)) {
    flt_idx_use <- 1:length(x)
  } else {
    if (is.numeric(flt)) {
      if (max(flt) > length(x)) stop("flt should not be larger than the number of flights saved in the uas image collection object")
      flt_idx_use <- flt
    } else if (is.character(flt)) {
      if (FALSE %in% (flt %in% names(x))) stop("flight name not found in the uas image collection object")
      flt_idx_use <- which(names(x) %in% flt)
    } else {
      stop("Invalid value for `flt`")
    }
  }

  if (!preview && confirm) {
    cat(crayon::red("This will change the names of files. It can not be undone.\n"))
    ans <- readline(prompt = "Continue? y/n ")
    if (ans != "y") return(invisible(NULL))
  }

  ## Initialize the result
  res <- NULL

  for (flt_idx in flt_idx_use) {

    ## Get the old and new file names
    fn_old <- x[[flt_idx]]$pts$file_name
    fn_new <- rep(name_template, length(fn_old))

    ## Tack on the extensions
    fn_new <- paste0(fn_new, ".", file_ext(fn_old))

    ## Do all the date-time replacements

    ## Convert the character date times to POSIXct
    flt_imgs_dt <- as.POSIXct(x[[flt_idx]]$pts$date_time, format="%Y:%m:%d %H:%M:%S")

    ## Replace the {i} token with an appropriate amount of leading 0s
    if (grepl("\\{i\\}", name_template)) {
      num_imgs <- length(fn_new)
      num_digits <- floor(log10(num_imgs)) + 1
      fn_new <- str_replace_all(fn_new,
                                "\\{i\\}",
                                sprintf(paste0("%0", num_digits, "d"), 1:num_imgs))
    }

    ## Loop through the supported date-time tokens. If found in name_template, apply them
    for (search_token in c("Y", "y", "m", "d", "j", "H", "M", "S")) {
      if (grepl(paste0("\\{", search_token, "\\}"), name_template)) {
        fn_new <- str_replace_all(fn_new,
                                  paste0("\\{", search_token, "\\}"),
                                  format(flt_imgs_dt, paste0("%", search_token)) )
      }
    }

    ## Swap out tokens from CHARACTER columns in the pts attribute table
    for (search_token in c("camera_abbrev")) {
      if (grepl(paste0("\\{", search_token, "\\}"), name_template)) {
        fn_new <- str_replace_all(fn_new,
                                  paste0("\\{", search_token, "\\}"),
                                  x[[flt_idx]]$pts[[search_token]])
      }
    }

    ## Swap out tokens from NUMERIC columns in the pts attribute table
    ## These ones are allowed to have a ",1" at the end to indicate rounding
    for (search_token in c("alt_agl")) {
      if (grepl(paste0("\\{", search_token, "(.*?)\\}"), name_template)) {

        if (grepl(paste0("\\{", search_token, ",(.*?)\\}"), name_template)) {
          ## PUll out the 'roundto' value using a lookbehind and lookahead pattern
          roundto <- as.numeric(str_extract(name_template, paste0("(?<=\\{", search_token, ",).*?(?=\\})")))
          replace_vals <- round(x[[flt_idx]]$pts[[search_token]], roundto)
        } else {
          replace_vals <- x[[flt_idx]]$pts[[search_token]]
        }

        fn_new <- str_replace_all(fn_new,
                                  paste0("\\{", search_token, "(.*?)\\}"),
                                  as.character(replace_vals))
      }
    }

    ## Go through the flight metadata fields. If any of them are found as tokens in name_template,
    ## swap them out
    for (search_token in names(x[[flt_idx]]$metadata)) {
      if (grepl(paste0("\\{", search_token, "\\}"), name_template)) {
        fn_new <- str_replace_all(fn_new,
                                  paste0("\\{", search_token, "\\}"),
                                  x[[flt_idx]]$metadata[[search_token]])
      }
    }

    if (all_lower) fn_new <- tolower(fn_new)

    ## Add the old and new names to the result
    df <- tibble(dir = names(x)[flt_idx], fn_old = fn_old, fn_new = fn_new)
    res <- rbind(res, df)

    if (!preview) {
      if (FALSE %in% file.exists(x[[flt_idx]]$pts$img_fn)) stop("One or more input files not found. Can not proceed.")
      if (anyDuplicated(fn_new) > 0 ) stop("Duplicate output file names detected. Try to modify your name_template to get unique file names.")

      file.rename(from = x[[flt_idx]]$pts$img_fn,
                  to = file.path(dirname(x[[flt_idx]]$pts$img_fn), fn_new))
    }

  } ##  for (i in flt_idx_use)

  if (preview) {
    ## Return the tibble
    res

  } else {
    message("Filenames changed. Remember to re-run uas_info() to reflect the changes")
    ## Return invisible
    invisible(res)
  }

}
UCANR-IGIS/uasimg documentation built on Jan. 16, 2025, 10:29 p.m.