R/uas_move.R

Defines functions uas_move

Documented in uas_move

#' Move UAS images into sub-directories
#'
#' Move UAS images into sub-directories
#'
#' @param x A list of class 'uas_info'
#' @param flt Flight(s) in x to process (character or numeric vector, default is all)
#' @param tree Directory tree template filename or character vector, see Details
#' @param outdir_base Output directory root
#' @param req_all_fltmdflds Require all flight metadata fields in the directory tree template to be defined
#' @param create_dirs Create the output directory tree
#' @param imgs_action The action to take with images
#' @param imgs_prepend_fn Whether th prepend image file names with datestamp (to ensure they'll be unique)
#' @param write_metadata Write a metadtata.txt file in the output image folder
#' @param preview_only Preview the directory tree only
#' @param tb_action The action to take with thumbnail images saved in the default location
#' @param map_action The action to take with the contents of the map folder
#' @param quiet Suppress messages
#' @param flt_idx `r lifecycle::badge("deprecated")` Use `flt` instead
#'
#' @details
#' req_all_fltmdflds means don't move anything unless all uas_info objects have all tokens in the directory tree
#'
#' @return A vector of directory locations where images were copied / moved
#'
#' @seealso \code{\link{uas_info}}
#'
#' @importFrom stringr str_extract_all str_replace
#' @importFrom sf st_drop_geometry
#' @importFrom dplyr mutate select
#' @importFrom crayon yellow green red bold
#' @importFrom lifecycle deprecated is_present deprecate_warn
#'
#' @export

uas_move <- function(x,
                     flt = NULL,
                     tree,
                     outdir_base,
                     req_all_fltmdflds = TRUE,
                     create_dirs = "ask",
                     imgs_action = c("copy", "move", "none")[1],
                     imgs_prepend_fn = FALSE,
                     write_metadata = TRUE,
                     preview_only = FALSE,
                     tb_action = imgs_action,
                     map_action = c("copy", "move", "none")[3],
                     quiet = FALSE,
                     flt_idx = deprecated()) {

  ## TODO If there are multiple metadata files going to one place, that's an issue
  ## maybe give them different file names

  ## Do we really want to copy the map folder? I suppose so. Risk is that local_dir will be
  ## out of date.

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

  debug <- FALSE

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

  if (!imgs_action %in% c("copy", "move", "none")) stop("Unknown value for imgs_action")

  ## Chop off any trailing slashes from outdir_base
  outdir_base_use <-  gsub("\\\\$|/$", "", outdir_base)
  if (!file.exists(outdir_base)) stop(paste0("Base directory not found: ", outdir_base))

  ## 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`")
    }
  }

  ## Load the tree template
  if (length(tree) == 1) {
    ## We assume tree is a file name
    if (!file.exists(tree)) stop(paste0("File not found: ", tree))
    tree_input <- readLines(tree, warn = FALSE)
  } else {
    # We assume tree is a character vector
    tree_input <- tree
  }

  ## Clean the tree template; make a list of tokens
  tree_use <- character(0)
  tokens <- character(0)
  #dir_fltimg_idx <- NULL
  #dir_nofltimg_idx <- NULL

  for (tree_ln in tree_input) {
    ## First trim any white space
    tree_ln_clean <- trimws(tree_ln)

    ## Next, make sure it isn't a comment
    if (!grepl("^#|^;", tree_ln_clean)) {

      ## Next, make sure it isn't a blank line
      if (tree_ln_clean != "") {

        ## Check if the line ends with **
        # put_fltimgs_here <- grepl("\\*\\*$", tree_ln_clean)
        # if (put_fltimgs_here) {
        #   tree_ln_clean <- trimws(gsub("\\*\\*$", "", tree_ln_clean))
        # }

        # ## Check if the line ends with --
        # put_nofltimgs_here <- grepl("--$", tree_ln_clean)
        # if (put_nofltimgs_here) {
        #     tree_ln_clean <- trimws(gsub("--$", "", tree_ln_clean))
        # }

        ## Extract all the tokens and add them to the master vector
        tokens <- unique(c(tokens,
                         str_extract_all(tree_ln_clean, "(?<=\\{).+?(?=\\})")[[1]]))

        ## Add this line to tree use
        tree_use <- c(tree_use, tree_ln_clean)

        ## Record the index if this is where flight images go
        #if (put_fltimgs_here) dir_fltimg_idx <- c(dir_fltimg_idx, length(tree_use))

        ## Record the index if this is where flight images go
        # if (put_nofltimgs_here) dir_nofltimg_idx <- c(dir_nofltimg_idx, length(tree_use))

      } # if (tree_ln_clean != "") {
    }
  }

  ## Check to see if one and only one directory was designated for flight images
  # if (length(dir_fltimg_idx) != 1) {
  #   stop("Error in tree: One and only one directory must be designated as the destination for flight images. See help.")
  # }

  ## if (length(dir_nofltimg_idx) != 1) stop("Error in tree: One and only one directory must be designated as the destination for non-flight images. See help.")

  ## Next, we need to see if all the flights have all the required metadata fields

  if (req_all_fltmdflds) {
    ## Verify that fligth metadata exists for all the required tokens (excluding those which will be generated
    ## automatically)

    tokens_check <- tokens[!tokens %in% c("flt_start", "flt_end", "flt_date", "flt_agl",
                                          "camera_name", "camera_abbrev",
                                          "filetype", "make", "model")]
    for (flt_name in names(x)) {
      if (FALSE %in% (tokens_check %in% names(ucm_flts_info[[flt_name]]$metadata))) {
          stop(paste0("Required metadata field(s) not found for `", flt_name, "`: ",
                      paste(tokens_check[!tokens_check %in% names(ucm_flts_info[[flt_name]]$metadata)],
                            collapse = ", " )))
      }
    }
  }

  img_tokens <- c("camera_name", "camera_abbrev", "filetype", "make", "model")
  img_token_present <- (TRUE %in% (img_tokens %in% tokens))

  if (debug) {
      cat("Tree Template: \n")
      cat(paste0("  ", tree_use, collapse = "\n"), "\n")
      #cat("Image dir: ", dir_fltimg_idx, "\n\n")
  }

  res <- NULL

  ## We've verified all tokens are available.

  for (flt_idx in flt_idx_use) {

    flt_tokens <- list()

    ## Gather all computed flight-tokens
    ## flt_start", "flt_end", "flt_date", "flt_agl""
    flt_imgs_dt <- as.POSIXct(x[[flt_idx]]$pts$date_time, format="%Y:%m:%d %H:%M:%S")
    if (NA %in% flt_imgs_dt) stop("Unable to parse image timestamps")
    flt_tokens[["flt_date"]] <- format(min(flt_imgs_dt), "%Y-%m-%d")
    flt_tokens[["flt_start"]] <- format(min(flt_imgs_dt), "%H%M")
    flt_tokens[["flt_end"]] <- format(max(flt_imgs_dt), "%H%M")

    ## Add all metadata flight token
    flt_tokens <- c(flt_tokens,
                    x[[flt_idx]]$metadata)

    ## Expand the directories needed for this flight
    flt_tree <- tree_use

    ## For every token in the tree, if we have a flight token
    ## with the same name, make the replacement
    for (my_token in tokens) {
      if (my_token %in% names(flt_tokens)) {
        flt_tree <- gsub(paste0("\\{", my_token, "\\}"), flt_tokens[[my_token]], flt_tree)
      }
    }

    ## Next, if any of the field names in $pts are tokens, make the replacements
    ## This could result in duplicate lines in the tree if the attribute column contains
    ## more than one unique value

    flt_img_tree <- NULL
    for (my_token in tokens) {
      if (my_token %in% names(x[[flt_idx]]$pts)) {
        for (replacement_val in unique(x[[flt_idx]]$pts[[my_token]])) {
          flt_img_tree <- c(flt_img_tree,
                            gsub(paste0("\\{", my_token, "\\}"), replacement_val, flt_tree))
        }
      }
    }

    ## Construct the final flight tree
    if (is.null(flt_img_tree)) {
      ## No substitutions were made
      flt_tree_final <- flt_tree
    } else {
      flt_tree_final <- unique(flt_img_tree)
    }

    # if (debug) {
    #   cat("\n", names(x)[flt_idx], ":\n", sep = "")
    #   cat("Flight tree final: \n")
    #   cat(paste0("  ", flt_tree_final, collapse = "\n"), "\n")
    # }

    ## Now we're ready to create the directories
    dirs_needed <- normalizePath(file.path(outdir_base, flt_tree_final), mustWork = FALSE)
    dir_missing <- !file.exists(dirs_needed)

    # if (debug) {
    #   cat("\n There are the dirs needed\n")
    #   cat(paste0("  ", dirs_needed, collapse = "\n"), "\n")
    # }

    #if (preview_only || identical(create_dirs, "ask")) {
    if (!quiet) {
      dir_asterik <- rep("", length(dirs_needed))
      dir_asterik[dir_missing] <- crayon::yellow(" **")
      message(crayon::yellow("\n", names(x)[flt_idx]), sep = "")
      message(" - output directory tree for this flight:")
      message(paste0("   - ", dirs_needed, dir_asterik, collapse = "\n"))
    }

    if (!preview_only && (TRUE %in% dir_missing)) {

      if (identical(create_dirs, TRUE)) {
        make_dir_yn <- TRUE
      } else if (identical(create_dirs, FALSE)) {
        make_dir_yn <- FALSE
      } else if (identical(create_dirs, "ask")) {
        message(crayon::yellow("    ** = new"))
        ans <- readline(prompt = "Create new directories now? y/n ")
        make_dir_yn <- (tolower(ans) == "y")
      }

      if (make_dir_yn) {
        message(" - creating directory tree")
        sapply(dirs_needed[dir_missing], dir.create, recursive = TRUE, showWarnings = FALSE)
      }

    }

    ## Move / copy images
    if (imgs_action != "none" && !preview_only) {
      ## See if the directories are

      if (FALSE %in% file.exists(dirs_needed)) {
        message(crayon::yellow(paste0(" - Directory not found. Can not ", imgs_action, " images")))

      } else {

        ans <- readline(prompt = paste0(imgs_action, " images now? y/n "))
        copymove_yn <- (tolower(ans) == "y")

        if (copymove_yn) {
          imgs_go_here <- normalizePath(file.path(outdir_base, flt_tree[1]), mustWork = FALSE)

          ## Append the 'images go here' path from the pts attribute table
          img_fromto_tbl <- x[[flt_idx]]$pts %>%
            st_drop_geometry() %>%
            mutate(dest_path = imgs_go_here)

          ## Loop through any image tokens in the attribute table and update the dest_path
          for (my_img_token in tokens[tokens %in% img_tokens]) {
            img_fromto_tbl <- img_fromto_tbl %>%
              mutate(dest_path = str_replace(dest_path,
                                             pattern = paste0("\\{", .env$my_img_token, "\\}"),
                                             replacement = !!as.name(my_img_token)))
          }

          ## Prepend a time stamp to file names (to make sure they're unique)
          if (imgs_prepend_fn) {
            img_fromto_tbl <- img_fromto_tbl %>%
              mutate(date_time_dt = as.POSIXct(date_time, format="%Y:%m:%d %H:%M:%S")) %>%
              mutate(file_name = paste0(format(date_time_dt, "%Y%m%d_%H%M%S_"), file_name))
          } else {
            ## TODO: check for duplicate file names right here
          }

          ## Keep only columns required for the move operation
          img_fromto_tbl <- img_fromto_tbl %>%
            select(img_fn, file_name, dest_path)

          ## Copy / move image files
          if (imgs_action == "copy") {
            file.copy(from = img_fromto_tbl$img_fn,
                      to = file.path(img_fromto_tbl$dest_path,
                                     img_fromto_tbl$file_name),
                      overwrite = FALSE)

            res <- c(res, unique(img_fromto_tbl$dest_path))

          } else if (imgs_action == "move") {
            file.rename(from = img_fromto_tbl$img_fn,
                        to = file.path(img_fromto_tbl$dest_path,
                                       img_fromto_tbl$file_name))
            res <- c(res, unique(img_fromto_tbl$dest_path))
          }

          ## Write a metadata.txt file the destination folder(s)
          if (write_metadata) {
            md_lst <- x[[flt_idx]]$metadata
            fltmd_yaml <- paste(sapply(1:length(md_lst), function(j) paste0(names(md_lst)[j], ": ", md_lst[[j]])), collapse = "\n\n")

            for (out_dir in unique(img_fromto_tbl$dest_path)) {
              md_pathfn <- file.path(out_dir, "metadata.txt")

              if (!file.exists(md_pathfn)) {
                descript_line <- paste0("## FLIGHT METADATA FOR:\n## ", out_dir, "\n##\n",
                                        "## Tips: \n",
                                        "## `name_short` is used to generate default file names. Keep it short and avoid special characters.\n",
                                        "## `name_long` will be used as a title for the HTML summary page\n\n")

                ## Write the file
                cat(c(descript_line, fltmd_yaml), file = md_pathfn, sep="")

                if (!quiet) message(" - metadata.txt written")
              }
            }

          }   ## if write_metadata = TRUE

          if (tb_action != "none") {

            tb_src <- file.path(unique(dirname(x[[flt_idx]]$pts$img_fn)), "map", "tb")

            for (my_tb_src in tb_src) {
              if (file.exists(my_tb_src)) {

                out_dirs <- unique(img_fromto_tbl$dest_path)

                if (length(out_dirs) > 1) {
                  if (!quiet) message(yellow(" - can not copy thumbnails (multiple output directories)"))

                } else {

                  tb_dest <- file.path(out_dirs, "map", "tb")
                  if (!file.exists(tb_dest)) {
                    dir.create(tb_dest, recursive = TRUE)
                  }

                  if (tb_action == "copy") {
                    if (!quiet) message(" - copying thumbnails")
                    file.copy(from = list.files(my_tb_src, full.names = TRUE),
                              to = tb_dest,
                              recursive = TRUE,
                              overwrite = FALSE)

                  } else if (tb_action == "move") {
                    if (!quiet) message(" - moving thumbnails")
                    file.rename(from = my_tb_src,
                                to = tb_dest)
                  }

                }

              }
            }

          }  # if tb_action != "none"

        }


      }  # if imgs_action != "none" && !preview_only


    }

  }

  ## Return a vector of image folders
  invisible(res)
}
UCANR-IGIS/uasimg documentation built on Jan. 16, 2025, 10:29 p.m.