R/getH-M.R

Defines functions getSpatialMethod getMatrices getMatrix getInitiationInput getInitiationInfo getImgAnnSf getImgAnnRange getImgAnnCenterBcsp get_img_ann_helper getImgAnnOutlineDf getImgAnnBarcodes getImgAnnArea getImageSectionsByBarcode getImageRasterInfo getImageRaster getImageRange getImageOrigin getImageObject getImageInfo getImageDirLowres getImageDirHighres getImageDirectories getImageDirDefault getImageDir getImageDims getImageDf getImageCenter getImgAnnTags getImageAnnotations getImgAnnIds getImgAnnSummaryDf getImageAnnotation getImage getImageAnnotationScreeningDf getIasExpansion getIasDf getIasBinAreas

Documented in getIasBinAreas getIasDf getIasExpansion getImage getImageAnnotation getImageAnnotations getImageAnnotationScreeningDf getImageCenter getImageDf getImageDims getImageDir getImageDirDefault getImageDirectories getImageDirHighres getImageDirLowres getImageInfo getImageObject getImageOrigin getImageRange getImageRaster getImageRasterInfo getImageSectionsByBarcode getImgAnnArea getImgAnnBarcodes getImgAnnCenterBcsp get_img_ann_helper getImgAnnIds getImgAnnOutlineDf getImgAnnRange getImgAnnSf getImgAnnSummaryDf getImgAnnTags getInitiationInfo getInitiationInput getMatrices getMatrix getSpatialMethod

# getI --------------------------------------------------------------------

#' @title Calculate IAS bin area
#'
#' @description Computes the area circular bins of the IAS algorithm
#' cover.
#'
#' @param use_outline Logical value. If `TRUE`, uses the outline variable
#' set with `setOutlineVarName()` or if none is set DBSCAN to identify the
#' outline of the tissue section or sections in case of multiple tissue sections
#' on one Visium slide to only compute the area of circle bins that covers the
#' tissue section.
#'
#' @inherit getIasDf params
#'
#' @details Approximates the area each circular bin covers
#' by assigning each pixel to the circular bin it falls into.
#' Afterwards the number of pixels per bin is multiplied
#' with the area scale factor as is obtained by `getPixelScaleFactor(object, unit = unit)`
#' where unit is the squared unit of input for argument `binwidth`. E.g.
#' if `binwidth` = *'0.1mm'* then `unit` = *mm2*.
#'
#' @return Data.frame in which each observation corresponds to a circular bin.
#'
#' @note If multiple tissue sections are located on the Visium slide use `createSpatialSegmentation()`
#' to encircle each section and set the variable name in which you saved the
#' encircling via `setOutlineVarName()`. Else the areas of the circle bins might
#' include space that is not covered by tissue. This might distort computation
#' results.
#'
#' @export
#'
getIasBinAreas <- function(object,
                           id,
                           distance = NA_integer_,
                           n_bins_circle = NA_integer_,
                           binwidth = getCCD(object),
                           angle_span = c(0, 360),
                           n_bins_angle = 1,
                           area_unit = NULL,
                           use_outline = TRUE,
                           remove_circle_bins = "Outside",
                           verbose = NULL){

  hlpr_assign_arguments(object)

  if(base::is.null(area_unit)){

    area_unit <- stringr::str_c(extract_unit(binwidth), "2")

  }

  ias_input <-
    check_ias_input(
      distance = distance,
      binwidth = binwidth,
      n_bins_circle = n_bins_circle,
      object = object,
      verbose = verbose
    )

  area_scale_fct <-
    getPixelScaleFactor(object, unit = area_unit) %>%
    base::as.numeric()

  pxl_df <- getPixelDf(object)

  if(base::isTRUE(use_outline)){

    outline_var <- getOutlineVarName(object)

    if(base::is.character(outline_var)){

      coords_df <- getCoordsDf(object, features = outline_var)

    } else {

      coords_df <- getCoordsDf(object)

    }

    pxl_df <-
      include_tissue_outline(
        coords_df = coords_df,
        input_df = pxl_df,
        outline_var = outline_var,
        img_ann_center = getImgAnnCenter(object, id),
        ccd = getCCD(object, "px")
      )

  }

  out_df <-
    bin_by_expansion(
      coords_df = pxl_df,
      area_df = getImgAnnOutlineDf(object, ids = id),
      binwidth = ias_input$binwidth,
      n_bins_circle = ias_input$n_bins_circle,
      remove = remove_circle_bins
    ) %>%
      bin_by_angle(
        coords_df = .,
        center = getImgAnnCenter(object, id = id),
        n_bins_angle = n_bins_angle,
        angle_span = angle_span,
        var_to_bin = "pixel",
        verbose = FALSE
      ) %>%
      dplyr::group_by(bins_circle, bins_angle) %>%
      dplyr::summarise(n_pixel = dplyr::n()) %>%
      dplyr::ungroup() %>%
      dplyr::mutate(
        id = {{id}},
        area_scale_fct = {{area_scale_fct}},
        area = n_pixel * area_scale_fct,
        unit = area_unit,
        bins_order =
          dplyr::case_when(
            bins_circle == "Core" ~ 0,
            bins_circle == "Outside" ~ (base::max(ias_input$n_bins_circle)+1),
            TRUE ~  base::as.numeric(stringr::str_extract(bins_circle, pattern = "\\d*$"))
          )
      ) %>%
      dplyr::select(id, bins_circle, bins_order, bins_angle, dplyr::everything())

  return(out_df)

}

#' @title Obtain image annotation screening data.frame
#'
#' @description Extracts a data.frame of inferred gradients of numeric
#' variables as a fucntion of distance to image annotations.
#'
#' @inherit bin_by_expansion params
#' @inherit bin_by_angle params
#'
#' @inherit getImgAnnOutlineDf params
#' @inherit imageAnnotationScreening params
#' @inherit joinWith params
#'
#' @return Data.frame.
#'
#' @export
#'
#' @examples
#'
#' library(SPATA2)
#' library(SPATAData)
#'
#' data("image_annotations")
#'
#' necrotic_img_ann <- image_annotations[["313_T"]][["necrotic_center"]]
#'
#' object <- downloadSpataObject(sample_name = "313_T")
#'
#' object <- setImageAnnotation(object = object, img_ann = necrotic_img_ann)
#'
#' plotSurfaceIAS(
#'  object = object,
#'  id = "necrotic_center",
#'  distance = 200
#'  )
#'
#' plotSurfaceIAS(
#'  object = object,
#'  id = "necrotic_center",
#'  distance = 200,
#'  binwidth = getCCD(object)*4, # lower resolution by increasing binwidth for visualization
#'  n_bins_angle = 12,
#'  display_angle = TRUE
#'  )
#'
#' getIasDf(
#'   object = object,
#'   id = "necrotic_center",
#'   distance = 200,
#'   variables = "VEGFA"
#'   )
#'
#' getIasDf(
#'   object = object,
#'    id = "necrotic_center",
#'    distance = 200,
#'    variables = "VEGFA"
#'    )
#'
#' getIasDf(
#'   object = object,
#'    id = "necrotic_center",
#'    distance = 200,
#'    variables = "VEGFA",
#'    n_bins_angle = 12
#'    )
#'

getIasDf <- function(object,
                     id,
                     distance = NA_integer_,
                     n_bins_circle = NA_integer_,
                     binwidth = getCCD(object),
                     angle_span = c(0,360),
                     n_bins_angle = 1,
                     variables = NULL,
                     method_gs = NULL,
                     summarize_by = c("bins_angle", "bins_circle"),
                     summarize_with = "mean",
                     normalize_by = "sample",
                     normalize = FALSE,
                     remove_circle_bins = FALSE,
                     remove_angle_bins = FALSE,
                     rename_angle_bins = FALSE,
                     bcsp_exclude = NULL,
                     verbose = FALSE,
                     ...){

  if(base::length(id) > 1){

    base::stopifnot(summarize_with %in% c("mean", "median"))

    purrr::map_df(
      .x = id,
      .f = function(idx){

        get_img_ann_helper(
          object = object,
          id = idx,
          distance = distance,
          n_bins_circle = n_bins_circle,
          binwidth = binwidth,
          angle_span = angle_span,
          n_bins_angle = n_bins_angle,
          variables = variables,
          method_gs = method_gs,
          summarize_by = summarize_by,
          summarize_with = summarize_with,
          normalize_by = normalize_by,
          normalize = normalize,
          remove_circle_bins = remove_circle_bins,
          remove_angle_bins = remove_angle_bins,
          bcsp_exclude = bcsp_exclude,
          drop = TRUE,
          verbose = verbose
        ) %>%
          dplyr::mutate(img_ann_id = {{idx}})

      }
    ) %>%
      dplyr::group_by(
        dplyr::pick(
          dplyr::any_of(c("bins_circle", "bins_order", "bins_angle"))
          )
        ) %>%
      dplyr::summarise(
        dplyr::across(
          .cols = dplyr::all_of(variables),
          .fns = summarize_formulas[[summarize_with]]
        )
      ) %>%
      dplyr::ungroup() %>%
      dplyr::mutate(
        dplyr::across(
          .cols = dplyr::all_of(variables),
          .fns = ~ confuns::normalize(.x)
        )
      ) %>%
      dplyr::select(dplyr::everything())

  } else {

    get_img_ann_helper(
      object = object,
      id = id,
      distance = distance,
      n_bins_circle = n_bins_circle,
      binwidth = binwidth,
      angle_span = angle_span,
      n_bins_angle = n_bins_angle,
      variables = variables,
      method_gs = method_gs,
      summarize_by = summarize_by,
      summarize_with = summarize_with,
      normalize_by = normalize_by,
      normalize = normalize,
      remove_circle_bins = remove_circle_bins,
      remove_angle_bins = remove_angle_bins,
      bcsp_exclude = bcsp_exclude,
      drop = TRUE,
      verbose = verbose
    )

  }

}


#' @title Obtain expanded Image Annotation polygons
#'
#' @description Expands polygons of image annotations according
#' to `distance`, `binwidth` and `n_bins_circle` input.
#'
#' @inherit imageAnnotationScreening params
#'
#' @return List of data.frames.
#' @export
#'
getIasExpansion <- function(object,
                            id,
                            distance = NA_integer_,
                            binwidth = getCCD(object),
                            n_bins_circle = NA_integer_,
                            direction = "outwards",
                            inc_outline = TRUE,
                            verbose = NULL){

  hlpr_assign_arguments(object)

  ias_input <-
    check_ias_input(
      distance = distance,
      binwidth = binwidth,
      n_bins_circle = n_bins_circle,
      object = object,
      verbose = verbose
    )

  area_df <- getImgAnnOutlineDf(object, ids = id)

  binwidth <- ias_input$binwidth
  n_bins_circle <- base::max(ias_input$n_bins_circle)

  circle_names <- stringr::str_c("Circle", 1:n_bins_circle, sep = " ")

  circles <-
    purrr::set_names(
      x = c((1:n_bins_circle)*binwidth),
      nm = circle_names
    )

  binwidth_vec <- c("Core" = 0, circles)

  if(direction == "outwards"){

    area_df <- dplyr::filter(area_df, border == "outer")

    expansions <-
      purrr::imap(
        .x = binwidth_vec,
        .f = ~
          buffer_area(df = area_df[c("x", "y")], buffer = .x) %>%
          dplyr::mutate(bins_circle = .y)
      )

    if(base::isTRUE(inc_outline)){

      ccd <- getCCD(object, unit = "px")

      expansions <-
        purrr::map(
          .x = expansions,
          .f = ~ include_tissue_outline(
            coords_df = getCoordsDf(object),
            input_df = .x,
            img_ann_center = getImgAnnCenter(object, id = id),
            remove = FALSE,
            ias_circles = TRUE,
            ccd = ccd,
            buffer = ccd*0.5
          )
        ) %>%
        purrr::discard(.p = base::is.null)

    }

  } else if(direction == "inwards"){

    area_df <- dplyr::filter(area_df, border == "outer")

    expansions <-
      purrr::imap(
        .x = binwidth_vec,
        .f = ~
          buffer_area(df = area_df[c("x", "y")], buffer = -(.x)) %>%
          dplyr::mutate(bins_circle = .y)
      )

  }

  return(expansions)

}

#' @rdname getIasDf
#' @export
getImageAnnotationScreeningDf <- function(...){

  deprecated(fn = TRUE)

  getIasDf(...)

}

#' @title Obtain histology image
#'
#' @description Extracts the image as an object of class \emph{EBImage}.
#'
#' @inherit argument_dummy params
#' @inherit check_sample params
#'
#' @export

getImage <- function(object, xrange = NULL, yrange = NULL, expand = 0, ...){

  deprecated(...)

  check_object(object)

  feedback_range_input(xrange = xrange, yrange = yrange)

  out <- object@images[[1]]@image

  if(base::is.null(out)){ stop("No image found.") }

  if(base::is.null(xrange)){ xrange <- getImageRange(object)$x }

  if(base::is.null(yrange)){ yrange <- getImageRange(object)$y }

  range_list <-
    process_ranges(
      xrange = xrange,
      yrange = yrange,
      expand = expand,
      object = object
    )

  xmin <- range_list$xmin
  xmax <- range_list$xmax
  ymin <- range_list$ymin
  ymax <- range_list$ymax

  if(nImageDims(object) == 3){

    out <- out[xmin:xmax, , ]
    out <- out[, ymin:ymax, ]

  } else if(nImageDims(object) == 2){

    out <- out[xmin:xmax, ]
    out <- out[, ymin:ymax]

  }

  return(out)

}



#' @title Obtain object of class \code{ImageAnnotation}
#'
#' @description Extracts object of class \code{ImageAnnotaion} by
#' its id.
#'
#' @param id Character value. The ID of the image annotation of interest.
#'
#' @inherit getImageAnnotations params
#' @inherit argument_dummy params
#'
#' @inheritSection section_dummy Expansion of cropped image sections
#'
#' @return An object of class \code{ImageAnnotation}.
#' @export
#'

getImageAnnotation <- function(object,
                               id,
                               add_barcodes = TRUE,
                               strictly = FALSE,
                               add_image = TRUE,
                               expand = 0,
                               square = FALSE){

  confuns::check_one_of(
    input = id,
    against = getImgAnnIds(object)
  )

  getImageAnnotations(
    object = object,
    ids = id,
    flatten = TRUE,
    add_barcodes = add_barcodes,
    add_image = add_image,
    square = square,
    expand = expand
  )

}


#' @title Obtain image annotation summary
#'
#' @description Extracts information about image annotations in a
#' data.frame.
#'
#' @param area Logical. If `TRUE`, the area of each image annotation
#' is added in a variable named *area*.
#' @param unit_area The unit of the *area* variable.
#' @param center Logical. If `TRUE`, two variables named *center_x* and
#' *center_y* area added providing the center coordinates of the image
#' annotation.
#' @param unit_center The unit of the center variables.
#' @param genes Character value or `NULL`. If character, the gene expression
#' of the named genes is summarized among all barcode spots that fall in the
#' area of the image annotation and are added as a variable.
#' @param summarize_with Character value. The summarizing function with
#' which the gene expression values are summarized.
#' @param tags_to_lgl Logical. If `TRUE`, tag information is displayed in logical
#' variables where each variable is named like one of the unique tags and
#' every value is either `TRUE` if the annotation cotnains the tag or `FALSE`
#' if not.
#' @param tags_keep Logical. If `TRUE`, variable *tags* is not removed if
#' `tags_to_lgl` is `TRUE`.
#'
#' @inherit getImageAnnotations params
#' @inherit argument_dummy params
#'
#' @inheritSection section_dummy Selection of image annotations with tags
#'
#' @return Data.frame in which each row corresponds to an image annotation identified
#' by the variable *id*.
#'
#' @export
#'
getImgAnnSummaryDf <- function(object,
                               ids = NULL,
                               area = TRUE,
                               unit_area = "mm2",
                               center = TRUE,
                               unit_center = "px",
                               genes = NULL,
                               summarize_with = "mean",
                               tags_to_lgl = TRUE,
                               tags_keep = FALSE,
                               verbose = NULL){

  hlpr_assign_arguments(object)

  if(base::is.character(ids)){

    confuns::check_one_of(
      input = ids,
      against = getImgAnnIds(object)
    )

  } else {

    ids <- getImgAnnIds(object)

  }


  confuns::check_one_of(
    input = unit_area,
    against = validUnitsOfArea()
  )

  confuns::check_one_of(
    input = unit_center,
    against = validUnitsOfLength()
  )

  if(base::is.character(genes)){

    gene_df <-
      joinWith(
        object = object,
        spata_df = getSpataDf(object),
        genes = genes,
        smooth = FALSE,
        verbose = verbose
      )

  }


  prel_df <-
    purrr::map_df(
      .x = ids,
      .f = function(id){

        img_ann <-
          getImageAnnotation(
            object = object,
            id = id,
            add_barcodes = TRUE,
            add_image = FALSE
          )

        df <-
          tibble::tibble(
            id = img_ann@id,
            parent_id = img_ann@info$parent_id,
            parent_origin = img_ann@info$parent_origin
          )

        if(base::isTRUE(center)){

          center_pos <-
            getImgAnnCenter(object, id = id) %>%
            as_unit(input = ., unit = unit_center, object = object)

          df$center_x <- center_pos["x"]
          df$center_y <- center_pos["y"]

        }

        df$tags <- stringr::str_c(img_ann@tags, collapse = "|")

        if(base::is.character(genes)){

          smrd_df <-
            dplyr::filter(gene_df, barcodes %in% img_ann@misc$barcodes) %>%
            dplyr::summarise(
              dplyr::across(
                .cols = dplyr::all_of(genes),
                .fns = summarize_formulas[[summarize_with]]
              )
            )

          df <-
            base::cbind(df, smrd_df) %>%
            tibble::as_tibble()

        }

        return(df)

      }
    )

  # add area measure
  if(base::isTRUE(area)){

    area_df <-
      getImgAnnArea(
        object = object,
        ids = ids,
        unit = unit_area
      ) %>%
      base::as.data.frame() %>%
      tibble::rownames_to_column(var = "id") %>%
      magrittr::set_colnames(value = c("id", "area"))

    prel_df <-
      dplyr::left_join(
        x = prel_df,
        y = area_df,
        by = "id"
      ) %>%
      dplyr::select(
        id, parent_id, parent_origin, dplyr::any_of(c("center_x", "center_y")),
        area,
        dplyr::everything()
      )

  }

  # shift tags
  if(base::isTRUE(tags_to_lgl)){

    all_tags <-
      stringr::str_c(prel_df$tags, collapse = "|") %>%
      stringr::str_split(pattern = "\\|") %>%
      purrr::flatten_chr() %>%
      base::unique()

    for(tag in all_tags){

      prel_df[[tag]] <- FALSE

      prel_df <-
        dplyr::mutate(
          .data = prel_df,
          {{tag}} := stringr::str_detect(string = tags, pattern = tag)
        )

    }

  }

  if(base::isTRUE(tags_keep)){

    out <- prel_df

  } else {

    out <- dplyr::select(prel_df, -tags)

  }

  return(out)

}



#' @title Obtain image annotations ids
#'
#' @description Extracts image annotation IDs as a character vector.
#'
#' @inherit argument_dummy
#'
#' @inheritSection section_dummy Selection of image annotations with tags
#'
#' @return Character vector.
#' @export
#'
getImgAnnIds <- function(object, tags = NULL , test = "any", ...){

  if(nImageAnnotations(object) >= 1){

    out <-
      purrr::map_chr(
        .x = getImageAnnotations(
          object = object,
          ids = list(...)[["ids"]],
          tags = tags,
          test = test,
          add_image = FALSE,
          add_barcodes = FALSE
          ),
        .f = ~ .x@id
      ) %>%
      base::unname()

  } else {

    out <- base::character(0)

  }

  return(out)

}


#' @title Obtain list of \code{ImageAnnotation}-objects
#'
#' @description Extracts a list of objects of class \code{ImageAnnotaion}.
#'
#' @param add_barcodes Logical. If `TRUE`, barcodes of spots that fall into the
#' area of an image annotation are identified and added to slot @@misc$barcodes
#' of the output image annotations.
#'
#' @param strictly Logical. If `TRUE`, only barcodes of spots that are strictly interior
#' to the area of an image annotation are added to the output. If `FALSE`,
#' barcodes of spots that are on the relative interior of the area or are
#' vertices of the border are added, too.
#'
#' @param add_image Logical. If TRUE, the area of the histology image that
#' is occupied by the annotated structure is added to the \code{ImageAnnotation}
#' object in slot @@image. Dimensions of the image can be adjusted with `square`
#' and `expand`.
#'
#' @inherit getBarcodesInPolygon params
#' @inherit argument_dummy params
#' @inherit getImage details
#'
#' @note To test how the extracted image section looks like depending
#' on input for argument `square` and `expand` use
#' `plotImageAnnotations(..., encircle = FALSE)`.
#'
#' @inheritSection section_dummy Expansion of cropped image sections
#' @inheritSection section_dummy Selection of image annotations with tags
#'
#' @return A list of objects of class \code{ImageAnnotation}.
#'
#' @export
#'
getImageAnnotations <- function(object,
                                ids = NULL,
                                tags = NULL,
                                test = "any",
                                add_barcodes = TRUE,
                                strictly = FALSE,
                                add_image = TRUE,
                                expand = 0,
                                square = FALSE,
                                flatten = FALSE,
                                check = FALSE){

  img_annotations <- getImageObject(object)@annotations

  if(base::isTRUE(check)){

    check_availability(
      test = base::length(img_annotations) >= 1,
      ref_x = "any image annotations",
      ref_fns = "`createImageAnnotations()`"
    )

  }

  if(base::is.character(ids)){

    check_image_annotation_ids(object, ids)

    img_annotations <- purrr::keep(.x = img_annotations, .p = ~ .x@id %in% ids)

  } else if(base::is.numeric(ids)){

    img_annotations <- img_annotations[ids]

  }

  base::stopifnot(base::length(test) == 1)

  if(base::is.character(tags)){

    check_image_annotation_tags(object, tags)

    img_annotations <-
      purrr::keep(
        .x = img_annotations,
        .p = function(img_ann){

          if(test == "any" | test == 1){

            out <- base::any(tags %in% img_ann@tags)

          } else if(test == "all" | test == 2){

            out <- base::all(tags %in% img_ann@tags)

          } else if(test == "identical" | test == 3){

            tags_input <- base::sort(tags)
            tags_img_ann <- base::sort(img_ann@tags)

            out <- base::identical(tags_input, tags_img_ann)

          } else if(test == "not_identical" | test == 4){

            tags_input <- base::sort(tags)
            tags_img_ann <- base::sort(img_ann@tags)

            out <- !base::identical(tags_input, tags_img_ann)

          } else if(test == "none" | test == 5){

            out <- !base::any(tags %in% img_ann@tags)

          } else {

            stop(invalid_img_ann_tests)

          }

          return(out)

        }
      )

  }

  coords_df <- getCoordsDf(object)

  for(nm in base::names(img_annotations)){

    img_ann <- img_annotations[[nm]]

    if(base::isTRUE(add_image)){

      img_ann_range <- getImgAnnRange(object, id = img_ann@id)

      xrange <- img_ann_range$x
      yrange <- img_ann_range$y

      xmean <- base::mean(xrange)
      ymean <- base::mean(yrange)

      # make image section to square
      if(base::isTRUE(square)){

        xdist <- xrange[2] - xrange[1]

        ydist <- yrange[2] - yrange[1]

        if(xdist > ydist){

          xdisth <- xdist/2

          yrange <- c(ymean - xdisth, ymean + xdisth)

        } else if(ydist > xdist) {

          ydisth <- ydist/2

          xrange <- c(xmean - ydisth, xmean + ydisth)

        } else {

          # both ranges are equally long

        }

      }

      img_ann@image <-
        getImage(
          object = object,
          xrange = xrange,
          yrange = yrange,
          expand = expand
        )

      img_list <- list()

      # getImage already outputs warnings
      base::suppressWarnings({

        range_list <-
          process_ranges(
            xrange = xrange,
            yrange = yrange,
            expand = expand,
            object = object
          )

      })


      for(val in base::names(range_list)){ # sets xmin - ymax

        img_list[[val]] <- range_list[[val]]

      }

      img_list$orig_ranges <- list(x = xrange, y = yrange)

      img_list$expand <- process_expand_input(expand)

      img_list$square <- square

      img_list$xmin_parent <- 0
      img_list$ymin_parent <- 0

      img_list$xmax_parent <- getImageRange(object)$x[2]
      img_list$ymax_parent <- getImageRange(object)$y[2]

      img_list$ymin_coords <-
        img_list$ymax_parent - img_list$ymax

      img_list$ymax_coords <-
        img_list$ymax_parent - img_list$ymin

      # set list
      img_ann@image_info <- img_list

    }

    if(base::isTRUE(add_barcodes)){

      img_ann@misc$barcodes <-
        getBarcodesInPolygonList(
          object = object,
          polygon_list = img_ann@area,
          strictly = strictly
          )

    }

    img_annotations[[nm]] <- img_ann

  }

  if(base::isTRUE(flatten) && base::length(img_annotations) == 1){

    img_annotations <- img_annotations[[1]]

  }

  return(img_annotations)

}


#' @title Obtain image annotations tags
#'
#' @description Extracts all unique tags with which image annotations
#' have been tagged.
#'
#' @inherit argument_dummy
#'
#' @return Character vector.
#' @export
#'
getImgAnnTags <- function(object){

  if(nImageAnnotations(object) >= 1){

    out <-
      purrr::map(
        .x = getImageAnnotations(object, add_image = FALSE, add_barcodes = FALSE),
        .f = ~ .x@tags
      ) %>%
      purrr::flatten_chr() %>%
      base::unique()

  } else {

    out <- base::character(0)

  }

  return(out)

}



#' @title Obtain image center
#'
#' @description Computes and extracts center of the image frame.
#'
#' @inherit argument_dummy params
#'
#' @return Numeric vector of length two.
#' @export
getImageCenter <- function(object){

  getImageRange(object) %>%
    purrr::map_dbl(.f = base::mean)

}


#' @title Obtain melted image
#'
#' @description Melts image array in a data.frame where each
#' row corresponds to a pixel-color value.
#'
#' @inherit argument_dummy params
#'
#' @return Data.frame.
#' @export
#'
getImageDf <- function(object, xrange = NULL, yrange = NULL){

  img <- getImage(object)
  img_dims <- getImageDims(object)

  # red, green, blue
  channels = c("red", "green", "blue")

  out <-
    purrr::map_df(
      .x = 1:img_dims[3],
      .f = function(cdim){ # iterate over color dimensions

        reshape2::melt(img[,,cdim], value.name = "intensity") %>%
          dplyr::select(-dplyr::any_of("Var3")) %>%
          magrittr::set_names(value = c("x", "y", "intensity")) %>%
          dplyr::mutate(channel = channels[cdim]) %>%
          tibble::as_tibble()

      }
    ) %>%
    tidyr::pivot_wider(
      id_cols = c("x", "y"),
      names_from = "channel",
      values_from = "intensity"
    ) %>%
    dplyr::mutate(
      color = grDevices::rgb(green = green, red = red, blue = blue)
    )

  return(out)

}


#' @title Obtain image dimensions/ranges
#'
#' @description Extracts information regarding the image.
#'
#' \itemize{
#'  \item{`getImageDims()`:}{ Extracts dimensions of the image, namely width, height and depth.}
#'  \item{`getImageRange()`:} Extracts range of the image axis.
#'  }
#'
#' @inherit argument_dummy params
#'
#' @return Similar output, different data structure:
#'
#' \itemize{
#'  \item{`getImageDims()`:}{ Vector of length three: image width, image height, image depth}
#'  \item{`getImageRange()`:}{ Named list, names are *x* and *y*. Each slot contains a
#'  vector of length two that describes the range of the x- and y-axis. Used for intersection
#'  between histology image and scatterplots.}
#' }
#'
#' @details In case of confusion due to overlapping naming conventions: X-axis,
#' x and x-range in terms of coordinates, corresponds to image width in terms of
#' image analysis. Y-axis, y  and y-range, in terms of coordinates, refers to
#' image-height in terms of image analysis. `SPATA2` primarily uses coordinates
#' naming convention.
#'
#' @export
getImageDims <- function(object, ...){

  deprecated(...)

  img <- object@images[[1]]@image

  out <- base::dim(img@.Data)

  return(out)

}

#' @rdname getImageDirLowres
#' @export
getImageDir <- function(object, name){

  io <- getImageObject(object)

  if(name %in% c("default", "highres", "lowres")){

    out <- methods::slot(io, name = stringr::str_c("dir_", name))

  } else {

    if(base::length(io@dir_add) == 0){

      stop("No additional image directories found.")

    } else {

      confuns::check_one_of(
        input = name,
        against = base::names(io@dir_add),
        ref.opt.2 = "additional image directories",
        fdb.opt = 2
      )

    }

    out <- io@dir_add[[name]]

  }

  return(out)

}

#' @rdname getImageDirLowres
#' @export
getImageDirDefault <- function(object, fdb_fn = "warning", check = FALSE, ...){

  dir_default <- getImageObject(object)@dir_default

  if(base::length(dir_default) == 0 || base::is.na(dir_default)){

    msg <- "Could not find directory to default image. Set with `setImageDirDefault()`."

    give_feedback(msg = msg, fdb.fb = fdb_fn, with.time = FALSE)

  }

  if(base::isTRUE(check)){

    confuns::check_directories(directories = dir_default, type = "files")

  }

  return(dir_default)

}


#' @rdname getImageDirLowres
#' @export
getImageDirectories <- function(object){

  io <- getImageObject(object)

  c(
    "default" = io@dir_default,
    "lowres" = io@dir_lowres,
    "highres" = io@dir_highres,
    purrr::map_chr(
      .x = io@dir_add,
      .f = ~ .x
    )
  )

}


#' @rdname getImageDirLowres
#' @export
getImageDirHighres <- function(object, fdb_fn = "warning", check = FALSE, ...){

  dir_highres <- getImageObject(object)@dir_highres

  if(base::length(dir_highres) == 0 || base::is.na(dir_highres)){

    msg <- "Could not find directory to high resolution image. Set with `setImageDirHighres()`."

    give_feedback(msg = msg, fdb.fb = fdb_fn, with.time = FALSE)

  }

  if(base::isTRUE(check)){

    confuns::check_directories(directories = dir_highres, type = "files")

  }

  return(dir_highres)

}


#' @title Obtain image directories
#'
#' @description Extracts image directories known to the `SPATA2` object.
#'
#' @param check Logical value. If `TRUE`, it is checked if the file actually exists.
#' @param name Character value. The name of the image of interest. Should be one
#' of Get
#'
#' @inherit argument_dummy params
#'
#' @return Character vector.
#'
#' @details `getImageDirectories()` returns all image directories known to
#' the `SPATA2` object. `getImageDirLowres()`, `getImageDirHighres()` and
#' `getImageDirDefault()` return the directories of the respective slot of
#' the `HistologyImaging` object. `getImageDir()` extracts specific directories
#' that were set with `setImageDir()` by name.
#'
#' @seealso [`setImageDir()`] to set specific image directories. [`loadImage()`],
#' [`loadImageHighres()`], [`loadImageLowres()`], [`loadImageDefault()`] to
#' exchange images.
#'
#' @export
#'
getImageDirLowres <- function(object, fdb_fn = "warning", check = FALSE){

  dir_lowres <- getImageObject(object)@dir_lowres

  if(base::length(dir_lowres) == 0 || base::is.na(dir_lowres)){

    msg <- "Could not find directory to low resolution image. Set with `setImageDirLowres()`."

    confuns::give_feedback(msg = msg, fdb.fn = fdb_fn, with.time = FALSE)

  }

  if(base::isTRUE(check)){

    confuns::check_directories(directories = dir_lowres, type = "files")

  }

  return(dir_lowres)

}


#' @title Obatain image information
#'
#' @description Extracts a list of information about the currently set
#' image.
#'
#' @inherit argument_dummy params
#'
#' @return List that contains information of slots @@image_info and @@justification
#' of the `HistologyImaging` object.
#' @export
#'
getImageInfo <- function(object){

  io <- getImageObject(object)

  c(
    io@image_info,
    io@justification
  )

}



#' @title Obtain object of class \code{HistologyImage}
#'
#' @description Extracts the S4-object. Do not confuse with \code{getImage()}
#'
#' @inherit argument_dummy params
#'
#' @return Object of class \code{HistologyImage}
#' @export
#'
getImageObject <- function(object){

  out <- object@images[[1]]

  if(!base::is.null(out)){

    out@id <- getSampleName(object)

  } else {

    warning("No image object found. Returning `NULL`.")

  }

  return(out)

}


#' @title Obtain image origin
#'
#' @description Extrats the origin of the image that is currently set.
#'
#' @inherit argument_dummy params
#'
#' @return Either a directory or *Global.Env.* if it was read in from
#' the global environment.
#'
getImageOrigin <- function(object){

  io <- getImageObject(object)

  io@image_info$origin

}


#' @rdname getImageDims
#' @export
getImageRange <- function(object, ...){

  deprecated(...)

  out <- list()

  img_dims <- getImageDims(object, ...)

  out$x <- c(0,img_dims[[1]])
  out$y <- c(0,img_dims[[2]])

  return(out)

}


#' @title Obtain image raster-(information)
#'
#' @inherit argument_dummy params
#'
#' @export
getImageRaster <- function(object, xrange = NULL, yrange = NULL, expand = 0){

  img <-
    getImage(object, xrange = xrange, yrange = yrange, expand = expand) %>%
    grDevices::as.raster() %>%
    magick::image_read()

  return(img)

}

#' @rdname getImageRaster
#' @export
getImageRasterInfo <- function(object, xrange = NULL, yrange = NULL){

  getImageRaster(object, xrange = xrange, yrange = yrange) %>%
    magick::image_info()

}


#' @title Obtain image sections by barcode spot
#'
#' @description Cuts out the area of the image that is covered by each barcode.
#'
#' @param barcodes Characte vector or NULL. If character, subsets the barcodes
#' of interest. If NULL, all barcodes are considered.
#' @inherit argument_dummy params
#'
#' @return A named list. Each slot is named after one barcode. The content is
#' another list that contains the barcode specific image section as well
#' as the x- and y-ranges that were used to crop the section.
#'
#' @export
#'
getImageSectionsByBarcode <- function(object, barcodes = NULL, expand = 0, verbose = NULL){

  hlpr_assign_arguments(object)

  dist_val <-
    getBarcodeSpotDistances(object) %>%
    dplyr::filter(bc_origin != bc_destination) %>%
    dplyr::group_by(bc_origin) %>%
    dplyr::filter(distance == base::min(distance)) %>%
    dplyr::ungroup() %>%
    dplyr::summarise(mean_dist = base::mean(distance)) %>%
    dplyr::pull(mean_dist)

  dist_valh <- dist_val/2

  coords_df <- getCoordsDf(object)

  if(base::is.character(barcodes)){

    coords_df <- dplyr::filter(coords_df, barcodes %in% {{barcodes}})

  }

  barcodes <- coords_df$barcodes

  img_list <-
    purrr::set_names(
      x = base::vector(mode = "list", length = base::nrow(coords_df)),
      nm = barcodes
    )

  pb <- confuns::create_progress_bar(total = base::length(barcodes))

  for(bcsp in barcodes){

    if(base::isTRUE(verbose)){ pb$tick() }

    bcsp_df <- dplyr::filter(coords_df, barcodes == bcsp)

    xrange <- c((bcsp_df$x - dist_valh), (bcsp_df$x + dist_valh))
    yrange <- c((bcsp_df$y - dist_valh), (bcsp_df$y + dist_valh))

    img <- getImage(object, xrange = xrange, yrange = yrange, expand = expand)

    img_list[[bcsp]] <- list(image = img, xrange = xrange, yrange = yrange, barcode = bcsp)

  }

  return(img_list)

}




# getImgAnn ---------------------------------------------------------------

#' @title Obtain area of image annotation
#'
#' @description Computes the area of an image annotation in SI units of area.
#'
#' @inherit argument_dummy params
#' @inherit as_unit params
#' @inherit getImageAnnotation params
#'
#' @return Numeric vector of the same length as `ids`. Named accordingly.
#' Contains the area of the image annotations in the unit that is specified in `unit`.
#' The unit is attached to the output as an attribute named *unit*. E.g. if
#' `unit = *mm2*` the output value has the unit *mm^2*.
#'
#' @details First, the side length of each pixel is calculated and based on that the area.
#'
#' Second, the number of pixels that fall in the area given by the outer border
#' of the image annotation is computed with `sp::point.in.polygon()`.
#'
#' Third, if the image annotation contains holes the pixel that fall in these
#' holes are removed.
#'
#' Fourth, the number of remaining pixels s multiplied with
#' the area per pixel.
#'
#' @inheritSection section_dummy Selection of image annotations with tags
#'
#' @seealso [`getImgAnnOutlineDf()`], [`getCCD()`], [`as_unit()`]
#'
#' @export
#'
getImgAnnArea <- function(object,
                          ids = NULL,
                          unit = "mm2",
                          tags = NULL,
                          test = "any",
                          as_numeric = TRUE,
                          verbose = NULL,
                          ...){

  deprecated(...)

  hlpr_assign_arguments(object)

  confuns::check_one_of(
    input = unit,
    against = validUnitsOfArea()
  )

  if(base::is.character(ids)){

    confuns::check_one_of(
      input = ids,
      against = getImgAnnIds(object)
    )

  } else {

    ids <-
      getImgAnnIds(
        object = object,
        ...
      )

  }

  unit_length <- stringr::str_extract(string = unit, pattern = "[a-z]*")

  # determine pixel area
  scale_fct <- getPixelScaleFactor(object, unit = unit)

  # determine how many pixels lay inside the image annotation

  pixel_df <- getPixelDf(object = object)

  n_ids <- base::length(ids)

  ref_ia <- confuns::adapt_reference(ids, sg = "image annotation")

  pb <- confuns::create_progress_bar(total = n_ids)

  confuns::give_feedback(
    msg = glue::glue("Computing area for {n_ids} {ref_ia}."),
    verbose = verbose
  )

  out <-
    purrr::map_dbl(
      .x = ids,
      .f = function(id){

        if(base::isTRUE(verbose)){

          pb$tick()

        }

        border_df <- getImgAnnOutlineDf(object, ids = id)

        pixel_loc <-
          sp::point.in.polygon(
            point.x = pixel_df[["x"]],
            point.y = pixel_df[["y"]],
            pol.x = border_df[["x"]],
            pol.y = border_df[["y"]]
          )

        pixel_inside <- pixel_df[pixel_loc != 0, ]

        # remove pixel that fall into inner holes
        inner_holes <- dplyr::filter(border_df, border != "outer")

        if(base::nrow(inner_holes) != 0){

          # consecutively reduce the number of rows in the pixel_inside data.frame
          for(hole in base::unique(inner_holes$border)){

            hole_df <- dplyr::filter(border_df, border == {{hole}})

            pixel_loc <-
              sp::point.in.polygon(
                point.x = pixel_inside[["x"]],
                point.y = pixel_inside[["y"]],
                pol.x = hole_df[["x"]],
                pol.y = hole_df[["y"]]
              )

            # keep those that are NOT inside the holes
            pixel_inside <- pixel_inside[pixel_loc == 0, ]

          }

        }

        n_pixel_inside <- base::nrow(pixel_inside)

        # multiply number of pixels with area per pixel
        area_img_ann <- n_pixel_inside * scale_fct

        base::as.numeric(area_img_ann)

      }
    ) %>%
    purrr::set_names(nm = ids) %>%
    units::set_units(value = unit, mode = "standard")

  return(out)

}

#' @title Obtain barcodes by image annotation tag
#'
#' @description Extracts the barcodes that are covered by the extent of the
#' annotated structures of interest.
#'
#' @inherit argument_dummy params
#'
#' @inheritSection section_dummy Selection of image annotations with tags
#'
#' @return Character vector.
#'
#' @export
#'
getImgAnnBarcodes <- function(object, ids = NULL, tags = NULL, test = "any"){

  getImageAnnotations(
    object = object,
    ids = ids,
    tags = tags,
    test = test
  ) %>%
    purrr::map(.f = ~ .x@misc[["barcodes"]]) %>%
    purrr::flatten_chr() %>%
    base::unique()

}

#' @title Obtain image annotation border data.frame
#'
#' @description Extracts the coordinates of the vertices polygons that represent
#' the borders of the image annotation.
#'
#' @inherit argument_dummy params
#'
#' @return A data.frame that contains variables \emph{id}, *border*,
#' and the numeric variables *x*, *y* and *tags*.
#'
#' @inherit getImageAnnotations details
#'
#' @details The variables \emph{x} and \emph{y} give the position of the vertices of the polygon
#' that was drawn to encircle the structure `createImageAnnotations()`. These vertices correspond
#' to the border of the annotation.
#'
#' @inheritSection section_dummy Selection of image annotations with tags
#'
#' @export
#'
getImgAnnOutlineDf <- function(object,
                               ids = NULL,
                               tags = NULL,
                               test = "any",
                               outer = TRUE,
                               inner = TRUE,
                               add_tags = FALSE,
                               sep = " & ",
                               last = " & "){

  img_anns <-
    getImageAnnotations(
      object = object,
      ids = ids,
      tags = tags,
      test = test,
      add_barcodes = FALSE,
      add_image = FALSE
    )

  out <-
    purrr::map_df(
      .x = img_anns,
      .f = function(img_ann){

        tag <-
          scollapse(string = img_ann@tags, sep = sep, last = last) %>%
          base::as.character()

        out <-
          purrr::imap_dfr(
            .x = img_ann@area,
            .f = function(area, name){

              dplyr::mutate(
                .data = area,
                border = {{name}}
              )

            }
          ) %>%
          dplyr::mutate(
            ids = img_ann@id %>% base::factor()
          ) %>%
          tibble::as_tibble()

        if(base::isTRUE(add_tags)){

          out$tags <- tag

          out$tags <- base::as.factor(out$tags)

        }

        return(out)

      }
    ) %>%
      dplyr::select(ids, border, x, y, dplyr::everything())

  if(!base::isTRUE(outer)){

    out <- dplyr::filter(out, border != "outer")

  }

  if(!base::isTRUE(inner)){

    out <- dplyr::filter(out, !stringr::str_detect(border, pattern = "inner"))

  }

  return(out)

}

#' @title Obtain image annotation screening data.frame
#'
#' @description Extracts a data.frame that contains information about barcode-spots
#' needed for analysis related to \code{imageAnnotationScreening()}.
#'
#' @inherit bin_by_expansion params
#' @inherit bin_by_angle params
#'
#' @param normalize_by Character value or FALSE. If character, there are two options:
#' \itemize{
#'  \item{\code{normalize_by} = \emph{'sample'}:}{ Values are normalized across the whole sample.}
#'  \item{\code{normalize_by} = \emph{'bins_angle'}:}{
#'  Values are normalized within each angle bin. This only has an effect if \code{n_bins_angle}
#'  is bigger than 1.
#'  }
#'  }
#'
#' @inherit getImgAnnOutlineDf params
#' @inherit imageAnnotationScreening params
#' @inherit joinWith params
#'
#' @return The final output depends on the input for \code{variables} and
#'  \code{summarize_by}.
#'
#'  By default (both arguments are NULL) the returned data.frame contains
#'  barcode-spots as observations/rows and variables that describe their position
#'  to the image annotation denoted with \code{id}. This includes the variables
#'  \emph{bins_circle}, \emph{bins_order}, \emph{angle}, \emph{bins_angle}. Their
#'  content depends on the set up via the arguments \code{distance}, \code{binwidth}
#'  and \code{n_bins_circle}.
#'
#' \bold{Coordinates data.frame vs. Inferred expression changes}:
#'
#' If argument \code{variables} is a character the denoted variables are
#' joined to the data.frame via \code{joinWith()}. If the set of variables
#' contains only numeric ones (genes, gene-sets and numeric features) the
#' function argument \code{summarize_by} can be set up in three different ways:
#'
#' \itemize{
#'  \item{\code{summarize_by} = \code{FALSE}:}{ Values are not summarized. The output
#'  is a coordinates data.frame with each observation/row corresponding to
#'  a barcode spots with additional information of its relation to the image
#'  annotation denoted in \code{id}.}
#'  \item{\code{summarize_by} = \emph{'bins_circle'}}{ Values of each variable
#'  area summarized by each circular expansion of the polygon. This results
#'  in data.frame with a column named \emph{bins_circle} containing the names of the bin
#'  (\emph{Core, Circle 1, Circle 2, Circle 3, ..., Circle n, Outside}) and 1 column
#'  per variable that contain the summarized expression value by circle bin. Visualization
#'  of the concept can be obtained using \code{plotIasLineplot(..., facet_by = 'variables')}
#'  }
#'  \item{\code{summarize_by} = \emph{c('bins_circle', 'bins_angle'))}}{ Values of
#'  each area are summarized by each circular expansion as well as by angle-bin.
#'  Output data.frame is similar to \code{summarize_by} = \emph{'bins_circle'} apart
#'  from having an extra column identifying the angle-bins. Adding \emph{'bins_circle'}
#'  is only useful if \code{n_bins_circle} is bigger than 1. Visualization
#'  of the concept can be obtained by using \code{plotIasLineplot(..., facet_by = 'bins_angle')}.
#'  }}
#'
#' Normalization in case of \code{normalize_by} != \code{FALSE} happens after the
#' summary step.
#' @keywords internal
get_img_ann_helper <- function(object,
                        id,
                        distance = NA_integer_,
                        n_bins_circle = NA_integer_,
                        binwidth = getCCD(object),
                        angle_span = c(0,360),
                        n_bins_angle = 1,
                        variables = NULL,
                        method_gs = NULL,
                        summarize_by = FALSE,
                        summarize_with = "mean",
                        normalize_by = "sample",
                        normalize = FALSE,
                        remove_circle_bins = FALSE,
                        remove_angle_bins = FALSE,
                        rename_angle_bins = FALSE,
                        bcsp_exclude = NULL,
                        drop = TRUE,
                        verbose = NULL,
                        ...){

  deprecated(...)

  hlpr_assign_arguments(object)

  add_sd <- FALSE

  input_list <-
    check_ias_input(
      distance = distance,
      binwidth = binwidth,
      n_bins_circle = n_bins_circle,
      object = object,
      verbose = verbose
    )

  distance <- input_list$distance
  n_bins_circle <- input_list$n_bins_circle
  binwidth  <- input_list$binwidth

  max_circles <- base::max(n_bins_circle)
  min_circles <- base::min(n_bins_circle)

  img_ann <- getImageAnnotation(object = object, id = id, add_image = FALSE)

  border_df <- getImgAnnOutlineDf(object, ids = id, outer = TRUE, inner = TRUE)

  img_ann_center <- getImgAnnCenter(object, id = id)

  coords_df <-
    getCoordsDf(object) %>%
    dplyr::select(barcodes, x, y)

  if(base::length(drop) == 1){ drop <- base::rep(drop, 2)}

  ias_df <-
    bin_by_expansion(
      coords_df = coords_df,
      area_df = border_df,
      binwidth = binwidth,
      n_bins_circle = max_circles,
      remove = remove_circle_bins,
      bcsp_exclude = bcsp_exclude,
      drop = drop[1]
    ) %>%
    bin_by_angle(
      center = getImgAnnCenters(object, id = id, outer = TRUE, inner = TRUE),
      angle_span = angle_span,
      n_bins_angle = n_bins_angle,
      min_bins_circle = min_circles,
      rename = rename_angle_bins,
      remove = remove_angle_bins,
      drop = drop[2],
      verbose = verbose
    )

  # join with variables if desired
  if(base::is.character(variables)){

    var_df <-
      joinWithVariables(
        object = object,
        spata_df = getSpataDf(object),
        variables = variables,
        smooth = FALSE,
        normalize = normalize,
        method_gs = method_gs,
        verbose = verbose
      )

    ias_df_joined <-
      dplyr::left_join(
        x = ias_df,
        y = var_df,
        by = "barcodes"
      )

    # summarize if desired
    if(base::is.character(summarize_by)){

      groups <- base::character()

      if(base::any(stringr::str_detect(summarize_by, "circle"))){

        groups <- c(groups, "bins_circle")

      }

      if(base::any(stringr::str_detect(summarize_by, "angle"))){

        groups <- c(groups, "bins_angle")

      }

      ref <- confuns::scollapse(string = groups)

      if(base::length(groups) == 0){

        stop("Invalid input for argument `summarize_by`. Must contains 'circle' and/or 'angle'.")

      }

      # keep var bins_order
      groups <- c(groups, "bins_order")

      ias_df1 <-
        dplyr::group_by(
          .data = ias_df_joined,
          dplyr::across(.cols = dplyr::all_of(groups))
        ) %>%
        dplyr::summarise(
          dplyr::across(
            .cols = dplyr::any_of(variables),
            .fns = summarize_formulas[[summarize_with]]
          )
        )

      if(base::isTRUE(add_sd)){

        ias_df2 <-
          dplyr::group_by(
            .data = ias_df_joined,
            dplyr::across(.cols = dplyr::all_of(groups))
          ) %>%
          dplyr::summarise(
            dplyr::across(
              .cols = dplyr::any_of(variables),
              .fns = list(sd = ~ stats::sd(.x, na.rm = TRUE))
            )
          ) %>% select(-bins_order)


        # store ranges for normalization if required
        if(base::is.character(normalize_by)){

          original_ranges <-
            purrr::map(
              .x = variables,
              .f = ~ base::range(ias_df_joined[[.x]])
            ) %>%
            purrr::set_names(
              nm = variables
            )

        }

        ias_df_out <-
          dplyr::left_join(
            x = ias_df1,
            y = ias_df2,
            by = "bins_circle"
          )

      } else {

        ias_df_out <- ias_df1

        ias_df_out

      }

    } else {

      ias_df_out <- ias_df_joined

    }

    # normalize if desired
    if(base::is.character(normalize_by)){

      confuns::check_one_of(
        input = normalize_by,
        against = c("sample", "bins_angle"),
        suggest = FALSE
      )

      if(normalize_by == "sample"){

        # no grouping needed
        groups <- base::character()

        ref = ""

      } else if(normalize_by == "bins_angle"){

        groups <- "bins_angle"

        ref <- " by 'bins_angle'"

      }

      confuns::give_feedback(
        msg = glue::glue("Normalizing{ref}."),
        verbose = verbose
      )

      ias_df_norm <-
        dplyr::group_by(
          .data = ias_df_out,
          dplyr::across(.cols = dplyr::all_of(groups))
        ) %>%
        dplyr::mutate(
          dplyr::across(
            .cols = dplyr::any_of(variables),
            .fns = ~ scales::rescale(x = .x, to = c(0,1))
          )
        )

      if(base::isTRUE(add_sd)){

        for(v in variables){

          vcol <- stringr::str_c(v, "_sd")

          ias_df_norm[[vcol]] <-
            scales::rescale(
              x = ias_df_norm[[vcol]],
              from = original_ranges[[v]],
              to = c(0, 1)
            )

        }

      }

      ias_df_out <- ias_df_norm

    }

  } else {

    confuns::give_feedback(
      msg = "No variables joined.",
      verbose = verbose
    )

    ias_df_out <- ias_df

  }

  out <- dplyr::ungroup(ias_df_out)

  return(out)

}


#' @title Obtain center of an image annotation
#'
#' @description \code{getImgAnnCenter()} computes the
#' x- and y- coordinates of the center of the outer border, returns
#' a numeric vector of length two. `getImgAnnCenters()` computes the center of the outer
#' and every inner border and returns a list of numeric vectors of length two.
#'
#' @inherit getImageAnnotation params
#' @inherit argument_dummy params
#'
#' @return Numeric vector of length two or a list of these. Values are named *x* and *y*.
#'
#' @export

setGeneric(name = "getImgAnnCenter", def = function(object, ...){

  standardGeneric(f = "getImgAnnCenter")

})

#' @rdname getImgAnnCenter
#' @export
setMethod(
  f = "getImgAnnCenter",
  signature = "spata2",
  definition = function(object, id){

    border_df <- getImgAnnOutlineDf(object, ids = id, inner = FALSE)

    x <- base::mean(base::range(border_df$x))
    y <- base::mean(base::range(border_df$y))

    out <- c(x = x, y = y)

    return(out)

  }
)

#' @rdname getImgAnnCenter
#' @export

setMethod(
  f = "getImgAnnCenter",
  signature = "ImageAnnotation",
  definition = function(object){

    border_df <- object@area[["outer"]]

    x <- base::mean(base::range(border_df$x))
    y <- base::mean(base::range(border_df$y))

    out <- c(x = x, y = y)

    return(out)

  }
)

#' @rdname getImgAnnCenter
#' @export
setGeneric(name = "getImgAnnCenters", def = function(object, ...){

  standardGeneric(f = "getImgAnnCenters")

})

#' @rdname getImgAnnCenter
#' @export
setMethod(
  f = "getImgAnnCenters",
  signature = "spata2",
  definition = function(object, id, outer = TRUE, inner = TRUE){

    img_ann <- getImageAnnotation(object, id = id, add_barcodes = FALSE, add_image = FALSE)

    area <- img_ann@area

    if(base::isFALSE(outer)){

      area$outer <- NULL

    }

    if(base::isFALSE(inner)){

      area <- area[c("outer")]

    }

    purrr::map(
      .x = area,
      .f = function(border_df){

        x <- base::mean(base::range(border_df$x))
        y <- base::mean(base::range(border_df$y))

        out <- c(x = x, y = y)

        return(out)

      }
    )

  }
)

#' @rdname getImgAnnCenter
#' @export
setMethod(
  f = "getImgAnnCenters",
  signature = "ImageAnnotation",
  definition = function(object, outer = TRUE, inner = TRUE){

    area <- object@area

    if(base::isFALSE(outer)){

      area$outer <- NULL

    }

    if(base::isFALSE(inner)){

      area <- area[c("outer")]

    }

    purrr::map(
      .x = area,
      .f = function(border_df){

        x <- base::mean(base::range(border_df$x))
        y <- base::mean(base::range(border_df$y))

        out <- c(x = x, y = y)

        return(out)

      }
    )

  }
)


#' @title Obtain center barcode-spot
#'
#' @description Extracts the barcode spot that lies closest
#' to the center of the image annotation.
#'
#' @inherit getImageAnnotation params
#'
#' @return Data.frame as returned by \code{getCoordsDf()} with one row.
#'
#' @export

getImgAnnCenterBcsp <- function(object, id){

  coords_df <- getCoordsDf(object)

  center <- getImgAnnCenter(object, id = id)

  out_df <-
    dplyr::mutate(.data = coords_df, dist = base::sqrt((x - center[["x"]])^2 + (y - center[["y"]])^2) ) %>%
    dplyr::filter(dist == base::min(dist))

  return(out_df)

}

#' @title Obtain image annotations range
#'
#' @description Extracts the minimum and maximum x- and y-coordinates
#' of the image annotation border.
#'
#' @inherit getImageAnnotation params
#'
#' @return List of length two. Named with *x* and *y*. Each slot
#' contains a vector of length two with the minima and maxima in pixel.
#' @export
#'
getImgAnnRange <- function(object, id){

  getImgAnnOutlineDf(object, ids = id) %>%
    dplyr::filter(border == "outer") %>%
    dplyr::select(x, y) %>%
    purrr::map(.f = base::range)

}

#' @title Obtain simple feature
#'
#' @description Exracts an object as created by `sf::st_polygon()` that
#' corresponds to the image annotation.
#'
#' @inherit getImageAnnotation params
#'
#' @return An object of class `POLYGON` from the `sf` package.
#' @export
#'
getImgAnnSf <- function(object, id){

  img_ann <-
    getImageAnnotation(
      object = object,
      id = id,
      add_barcodes = FALSE,
      add_image = FALSE
    )

  sf::st_polygon(
    x = purrr::map(.x = img_ann@area, .f = ~ close_area_df(.x) %>% base::as.matrix())
  )

}



#' @title Obtain information about object initiation
#'
#' @description Information about the object's initiation is stored in
#' a list of three slots:
#'
#' \itemize{
#'  \item{\emph{init_fn}: Contains the name of the initation function as a character value.}
#'  \item{\emph{input}: Contains a list of which every slot refers to the input of one argument with which the
#'  initiation function has been called.}
#'  \item{\emph{time}: Contains the time at which the object was initiated.}
#'  }
#'
#'  \code{getInitiationInput()} returns only slot \emph{input}.
#'
#' @inherit check_object params
#' @inherit argument_dummy params
#'
#' @details \code{initiateSpataObject_CountMtr()} and \code{initiateSpataObject_ExprMtr()} each require
#' a matrix and a coordinate data.frame as input. These are not included in the output
#' of this function but can be obtained via \code{getCoordsDf()} and \code{getCountMtr()} or \code{getExpressionMtr()}.
#'
#' @return A list. See description.
#' @export

getInitiationInfo <- function(object){

  check_object(object)

  info <- object@information$initiation

  return(info)

}

#' @rdname getInitiationInfo
#' @export
getInitiationInput <- function(object, verbose = NULL){

  hlpr_assign_arguments(object)

  info <- getInitiationInfo(object)

  init_fn <- info$init_fn

  confuns::give_feedback(
    msg = glue::glue("Initiation function used: '{init_fn}()'."),
    verbose = verbose,
    with.time = FALSE
  )

  return(info$input)

}

# getM --------------------------------------------------------------------

#' @title Obtain count and expression matrix
#'
#' @inherit check_sample params
#' @param mtr_name Character value. The name of the matrix of interest.
#'
#' @return The matrix of the specified object. A list of all matrices
#' in case of `getMatrices()`.
#' @export

getMatrix <- function(object, mtr_name = NULL, verbose = NULL, ...){

  deprecated(...)

  hlpr_assign_arguments(object)

  if(base::is.null(mtr_name)){

    mtr_name <- getActiveMatrixName(object, verbose = verbose)

  }

  object@data[[1]][[mtr_name]]

}

#' @rdname getMatrix
#' @export
getMatrices <- function(object){

  object@data[[1]]

}


#' @title Obtain spatial method
#'
#' @description Extracts an S4 object of class `SpatialMethod` that contains
#' meta data about the set up of the protocol that was followed to create
#' the data used for the `SPATA2` object.
#'
#' @inherit argument_dummy
#'
#' @return Character value.
#'
#' @seealso `?SpatialMethod`
#'
#' @export


getSpatialMethod <- function(object){

  object@information$method

}



#' @title Obtain model evaluation
#'
#' @description Extracts the data.frame that contains the variable-model-fit
#' evaluation containing.
#'
#' @inherit object_dummy
#'
#' @return Data.frame.
#'
#' @keywords internal

setGeneric(name = "getModelEvaluationDf", def = function(object, ...){

  standardGeneric(f = "getModelEvaluationDf")

})

#' @rdname getModelEvaluationDf
#' @export
setMethod(
  f = "getModelEvaluationDf",
  signature = "ImageAnnotationScreening",
  definition = function(object, smrd = TRUE){

    if(base::isTRUE(smrd)){

      out <- object@results_smrd

    } else {

      out <- object@results

    }

    return(out)

  }
)

#' @rdname getModelEvaluationDf
#' @export
setMethod(
  f = "getModelEvaluationDf",
  signature = "SpatialTrajectoryScreening",
  definition = function(object, ...){

    out <- object@results

    return(out)

  }
)
kueckelj/SPATA2 documentation built on March 16, 2024, 10:25 a.m.