R/server.R

Defines functions AzimuthServer

Documented in AzimuthServer

#' @include zzz.R
#' @include helpers.R
NULL

#' Server function for the mapping app
#'
#' @param input,output,session Required Shiny app server parameters
#'
#' @return The shiny server logic
#'
#' @importFrom BiocGenerics width
#' @importFrom BSgenome.Hsapiens.UCSC.hg38 BSgenome.Hsapiens.UCSC.hg38
#' @importFrom data.table as.data.table
#' @importFrom DT dataTableProxy renderDT selectRows
#' @importFrom EnsDb.Hsapiens.v86 EnsDb.Hsapiens.v86
#' @importFrom future future plan resolved value
#' @importFrom GenomeInfoDb seqlevelsStyle seqnames standardChromosomes
#' @importFrom GenomicRanges granges makeGRangesFromDataFrame
#' @importFrom ggplot2 annotate geom_hline ggtitle scale_colour_hue
#' theme_void xlab layer_scales xlim ylim ggplot aes geom_point theme
#' element_blank element_rect labs
#' @importFrom googlesheets4 gs4_auth gs4_get sheet_append
#' @importFrom IRanges findOverlaps
#' @importFrom Matrix sparse.model.matrix
#' @importFrom JASPAR2020 JASPAR2020
#' @importFrom methods slot slot<- new
#' @importFrom presto wilcoxauc
#' @importFrom SeuratObject AddMetaData Assays Cells DefaultAssay Embeddings
#' GetAssayData Idents Idents<- Key RenameCells Reductions Tool SetAssayData
#' VariableFeatures
#' @importFrom Seurat CreateAssayObject GetAssayData DimPlot FeaturePlot FindNeighbors FindTransferAnchors
#' IntegrateEmbeddings MappingScore NoLegend PercentageFeatureSet
#' RunUMAP TransferData SCTransform VlnPlot LabelClusters
#' FindBridgeTransferAnchors MapQuery NormalizeData
#' @importFrom Signac AddMotifs Annotation CreateChromatinAssay Extend FindMotifs FindTopFeatures GRangesToString 
#' GetGRangesFromEnsDb RunChromVAR RunSVD RunTFIDF AddMotifs
#' @importFrom shiny downloadHandler observeEvent isolate Progress
#' reactiveValues renderPlot renderTable renderText removeUI setProgress
#' safeError updateNumericInput updateSelectizeInput updateCheckboxInput updateTextAreaInput
#' withProgress renderUI onStop showNotification wellPanel nearPoints insertUI
#' modalDialog showModal getDefaultReactiveDomain
#' @importFrom shinydashboard menuItem renderMenu renderValueBox
#' sidebarMenu valueBox
#' @importFrom shinyjs addClass enable disable hide removeClass show onclick
#' disable
#' @importFrom stringr str_interp str_trim str_split
#' @importFrom patchwork wrap_plots
#' @importFrom stats na.omit quantile setNames median
#' @importFrom TFBSTools getMatrixSet
#' @importFrom S4Vectors queryHits subjectHits
#' @importFrom utils write.table packageVersion
#' @importFrom plotly plotlyOutput renderPlotly toWebGL ggplotly plot_ly
#' 
#' @keywords internal
#'
AzimuthServer <- function(input, output, session) {
  hide(id = "legend")
  disable(id = 'metacolor.ref')
  # hide demo dataset button if required
  if (is.null(x = getOption(x = 'Azimuth.app.demodataset'))) {
    hide(id = "demobuttons")
  }
  do.adt <- isTRUE(x = as.logical(getOption(x = 'Azimuth.app.do_adt'), default = TRUE))
  adt.key <- 'impADT'
  # Do Bridge Integration Workflow for ATAC query
  do.bridge <- isTRUE(x = as.logical(getOption(x = 'Azimuth.app.do_bridge'), default = FALSE))
  mt.key <- 'percent.mt'
  mito.pattern <- getOption(x = 'Azimuth.app.mito', default = '^MT-')
  
  n.trees <- getOption(x = "Azimuth.map.ntrees")
  app.env <- reactiveValues(
    adt.features = character(length = 0L),
    anchors = NULL,
    annotations = NULL,
    bridge = FALSE,
    bridge_anchors = FALSE,
    chromatin_assay_1 = NULL,
    chromatin_assay_2 = NULL,
    motif.diff.expr = list(),
    motif.feature = "", 
    motif.features = character(length = 0L),
    clusterpreservationqc = NULL,
    counts = FALSE,
    demo = FALSE,
    demo.inputs = NULL,
    demo.tracker = NULL,
    demo.files = NULL,
    default.assay = NULL,
    default.motif.feature = NULL,
    default.feature = NULL,
    default.metadata = NULL,
    diff.exp = list(),
    feature = '',
    features = character(length = 0L),
    mapping.score = NULL,
    messages = 'Upload a file',
    nanchors = 0L,
    ncellsupload = 0L,
    ncellspreproc = 0L,
    object = NULL,
    metadata.cont = character(length = 0L),
    scorefeatures = character(length = 0L),
    plot.ranges = list(),
    plots.refdim_df = NULL,
    plots.refdim_intro_df = NULL,
    plots.objdim_df = NULL,
    plots.querydim_df = NULL,
    fresh.plot = TRUE,
    singlepred = NULL,
    emptyref = NULL,
    merged = NULL,
    metadata.discrete = NULL,
    metadata.notransfer = NULL,
    requantified_multiome = NULL, 
    requantified_genes = NULL,
    disable = FALSE,
    query.names = character()
  )
  react.env <- reactiveValues(
    no = FALSE,
    anchors = FALSE,
    annotations = FALSE,
    biomarkers = FALSE,
    bridge = FALSE,
    bridge.query = FALSE,
    bridge_anchors = FALSE,
    motif = FALSE, 
    motif.features = FALSE,
    chromatin_assay_1 = FALSE,
    cluster.score = FALSE,
    features = FALSE,
    get.motif.feature = FALSE,
    get.feature = FALSE,
    map = FALSE,
    markers = FALSE,
    metadata = FALSE,
    mt = NULL,
    xferopts = FALSE,
    path = NULL,
    progress = NULL,
    plot.qc = FALSE,
    qc = FALSE,
    requantify_multiome = FALSE, 
    requantify_genes = FALSE,
    score = FALSE,
    sctransform = FALSE,
    start = numeric(length = 0L),
    transform = FALSE
  )
  if (isTRUE(x = do.adt)) {
    output$imputedlabel <- renderUI(expr = h3('Imputed protein biomarkers'))
  } else {
    for (id in c('imputedinput', 'imputedtable', 'imputeddl')) {
      removeUI(selector = paste0('#', id), immediate = TRUE)
    }
    for (id in c('featureinput', 'scoreinput')) {
      removeClass(id = id, class = 'thirds')
      addClass(id = id, class = 'halves')
    }
    removeClass(id = 'biotable', class = 'halves')
    addClass(id = 'biotable', class = 'fulls')
  }
  if (!isTRUE(x = do.bridge)) {
    for (id in c('dist.qc', 'q4', 'valuebox_overlap', 'valuebox_jaccard', 'motifinput', 'continput.motif', 'metagroup.motif', 'motifvln', 'markerclustersgroupinput.motif', 'motiftable', 'overlap_box')) {
      removeUI(selector = paste0('#', id), immediate = TRUE)
    }
  }
  ResetEnv <- function() {
    print('resetting...')
    app.env$disable <- TRUE
    output$menu2 <- NULL
    react.env$plot.qc <- FALSE
    app.env$messages <- NULL
    output$valubox.jaccard <- NULL
    output$valubox.upload <- NULL
    output$valuebox.preproc <- NULL
    output$valuebox.mapped <- NULL
    output$valuebox_overlap <- NULL
    output$valuebox_jaccard <- NULL
    output$valuebox_panchors <- NULL
    output$valuebox_mappingqcstat <- NULL
    app.env$emptyref <- NULL
    app.env$merged <- NULL
    app.env$metadata.discrete <- NULL
    disable(id = 'map')
    hide(selector = '.rowhide')
  }
  motif.proxy <- dataTableProxy(outputId = "motifs")
  rna.proxy <- dataTableProxy(outputId = 'biomarkers')
  adt.proxy <- dataTableProxy(outputId = 'adtbio')
  logging <- all(vapply(
    X = paste0('Azimuth.app.google', c('sheet', 'token', 'tokenemail')),
    FUN = function(x) {
      return(!is.null(x = getOption(x = x)))
    },
    FUN.VALUE = logical(length = 1L)
  ))
  googlesheet <- NULL
  if (logging) {
    try(
      expr = {
        token <- readRDS(getOption(x = "Azimuth.app.googletoken"))
        gs4_auth(email = getOption(x = "Azimuth.app.googletokenemail"), 
                 token = token)
        googlesheet <- gs4_get(ss = getOption(x = "Azimuth.app.googlesheet"))
        app_start_time <- Sys.time()
        app_session_id <- paste0(Sys.info()[["nodename"]], as.numeric(Sys.time()))
        onStop(
          fun = function() {
            try(expr = sheet_append(
              ss = googlesheet,
              data = data.frame(
                "SESSIONLENGTH",
                app_session_id,
                as.numeric(x = Sys.time() - app_start_time, units = "mins")
              )
            ))
          }
        )
      }
    )
  }
  if (!is.null(x = googlesheet)) {
    try(expr = sheet_append(
      ss = googlesheet,
      data = data.frame(
        "STARTUPTIME",
        app_session_id,
        Sys.time()
      )
    ))
    output$menu3 <- renderMenu(expr = {
      sidebarMenu(menuItem(
        text = 'Feedback',
        tabName = 'tab_feedback',
        icon = icon(name = 'comments'),
        selected = FALSE
      ))
    })
  }
  demos <- getOption("Azimuth.app.demodataset")
  if (!is.null(x = demos)) {
    if (!inherits(x = demos, what = "data.frame")) {
      if (is.null(x = names(x = demos))) {
        if (length(x = demos) > 1) {
          demo.names <- paste0("Demo", 1:length(x = demos))
        } else {
          demo.names <- "Load demo dataset"
        }
      } else {
        demo.names <- names(x = demos)
      }
      demos <- data.frame(name = demo.names, file = demos)
    }
    app.env$demo.files <- demos$file
    app.env$demo.inputs <- paste0("triggerdemo", 1:nrow(x = demos))
    app.env$demo.tracker <- rep(x = 0, times = nrow(x = demos))
    for (i in 1:nrow(x = demos)) {
      insertUI(
        selector = '#demobuttons',
        where = 'beforeEnd',
        immediate = TRUE,
        ui = actionButton(
          inputId = paste0('triggerdemo', i),
          label = demos$name[i],
          width = '85%'
        )
      )
    }
  }
  if (getOption(x = "Azimuth.app.metatableheatmap")) {
    insertUI(
      selector = '#tablemetadata',
      where = 'beforeEnd',
      immediate = TRUE,
      ui = plotlyOutput(outputId = 'metadata.heatmap')
    )
  } else {
    insertUI(
      selector = '#tablemetadata',
      where = 'beforeEnd',
      immediate = TRUE,
      ui = tableOutput(outputId = 'metadata.table')
    )
  }
  if (getOption(x = "Azimuth.app.overlayedreference")) {
    insertUI(
      selector = "#topdim",
      where = "beforeEnd",
      immediate = TRUE,
      ui = box(
        title = 'Mapped Query',
        checkboxInput(inputId = 'legend', label = 'Show legend'),
        checkboxGroupInput(
          inputId = "label.opts", label = NULL,
          choiceNames = c("Show labels", "Filter cluster labels (size <2%)"),
          choiceValues = c("labels", "filterlabels"),
          selected = c("labels", "filterlabels"), inline = TRUE),
        checkboxInput(inputId = 'showrefonly', label = 'View reference only'),
        selectizeInput(
          inputId = 'metacolor.ref',
          label = 'Reference metadata to color by',
          choices = '',
          multiple = TRUE,
        ),
        selectizeInput(
          inputId = 'metacolor.query',
          label = 'Query metadata to color by',
          choices = '',
          multiple = TRUE,
        ),
        div(
          style = "position:relative",
          plotOutput(
            outputId = 'objdim',
            hover = hoverOpts(
              id = "objdim_hover_location",
              delay = 5,
              delayType = "debounce",
              nullOutside = TRUE
            ),
            height='750px'
          ),
          uiOutput("objdim_hover_box")
        ),
        width = 12,
        height = 'auto'
      )
    )
  } else {
    insertUI(
      selector = "#topdim",
      where = "beforeEnd",
      immediate = TRUE,
      ui = box(
        title = 'Reference',
        checkboxGroupInput(inputId = "dimplot.opts", label = NULL, choiceNames = c("Show labels", "Show legend"), choiceValues = c("labels", "legend"), selected = "legend", inline = TRUE),
        selectizeInput(
          inputId = 'metacolor.ref',
          label = 'Metadata to color by',
          choices = '',
          multiple = TRUE,
        ),
        div(
          style = "position:relative",
          plotOutput(
            outputId = 'refdim',
            hover = hoverOpts(
              id = "refdim_hover_location",
              delay = 5,
              delayType = "debounce",
              nullOutside = TRUE
            )
          ),
          uiOutput("refdim_hover_box")
        ),
        width = 12
      )
    )
    insertUI(
      selector = "#bottomdim",
      where = "beforeEnd",
      immediate = TRUE,
      ui = box(
        title = 'Query',
        selectizeInput(
          inputId = 'metacolor.query',
          label = 'Metadata to color by',
          choices = '',
          multiple = TRUE,
        ),
        div(
          style = "position:relative",
          plotOutput(
            outputId = 'querydim',
            hover = hoverOpts(
              id = "querydim_hover_location",
              delay = 5,
              delayType = "debounce",
              nullOutside = TRUE
            )
          ),
          uiOutput("querydim_hover_box")
        ),
        width = 12
      )
    )
  }
  if (isTRUE(x = do.bridge)) {
    withProgress(
      message = "Loading bridge and reference",
      expr = {
        disable(id = 'file')
        ToggleDemos(action = "disable", demos = demos)
        setProgress(value = 0.2)
        refs <- LoadBridgeReference(
          path = getOption(
            x = 'Azimuth.app.reference',
            default = stop(safeError(error = "No reference provided"))
          )
        )
        setProgress(value = 1)
        enable(id = 'file')
        ToggleDemos(action = "enable", demos = demos)
        react.env$bridge <- TRUE
      }
    )
  } else {
    withProgress(
      message = "Loading reference",
      expr = {
        disable(id = 'file')
        ToggleDemos(action = "disable", demos = demos)
        setProgress(value = 0)
        refs <- LoadReference(
          path = getOption(
            x = 'Azimuth.app.reference',
            default = stop(safeError(error = "No reference provided"))
          )
        )
        setProgress(value = 1)
        enable(id = 'file')
        ToggleDemos(action = "enable", demos = demos)
        react.env$standard = TRUE
      }
    )
  }
  if (!is.null(x = googlesheet)) {
    try(
      expr = sheet_append(
        ss = googlesheet,
        data = data.frame(
          c('REFERENCE_NAME', "REFERENCE_VERSION"),
          c(app_session_id, app_session_id),
          c(basename(getOption(x = 'Azimuth.app.reference')),  ReferenceVersion(object = refs$map))
        )
      ),
      silent = TRUE
    )
  }
  plotseed <- getOption(x = "Azimuth.app.plotseed")
  if (!is.null(x = plotseed)) {
    set.seed(seed = plotseed)
    colormap <- GetColorMap(object = refs$map)
    for (i in names(x = colormap)) {
      names(x = colormap[[i]]) <- sample(x = names(x = colormap[[i]]))
    }
    refs$map <- SetColorMap(object = refs$map, value = colormap)
  }
  metadata.annotate <- names(x = GetColorMap(object = refs$map))
  if (!is.null(x = getOption(x = "Azimuth.app.metadata_notransfer", default = NULL))) {
    metadata.notransfer <- str_trim(
      str_split(
        getOption(x = "Azimuth.app.metadata_notransfer", default = NULL),
        ','
      )[[1]]
    )
    possible.metadata.transfer <- setdiff(x = metadata.annotate, y = metadata.notransfer)
  } else {
    possible.metadata.transfer <- metadata.annotate
  }
  if (length(x = possible.metadata.transfer) > 1) {
    react.env$xferopts <- TRUE
  }
  default_xfer <- getOption(x = "Azimuth.app.default_metadata", default = possible.metadata.transfer[1])
  if (!default_xfer %in% possible.metadata.transfer) {
    default_xfer <- possible.metadata.transfer[1]
  }
  # React to events
  # Load the data and prepare for QC
  observeEvent(
    eventExpr = input$file,
    handlerExpr = {
      ResetEnv()
      if (nchar(x = input$file$datapath)) {
        react.env$path <- input$file$datapath
      }
    }
  )
  observeEvent(
    eventExpr = sapply(X = app.env$demo.inputs, FUN = function(x) input[[x]]),
    handlerExpr = {
      if (isTRUE(x = !all(sapply(X = app.env$demo.inputs, FUN = is.null)))) {
        ResetEnv()
        for (i in 1:length(x = app.env$demo.inputs)) {
          if (isTRUE(x = input[[app.env$demo.inputs[i]]] != app.env$demo.tracker[i])) {
            app.env$demo.tracker[i] <- app.env$demo.tracker[i] + 1
            react.env$path <- app.env$demo.files[i]
          }
        }
      }
    },
    ignoreInit = TRUE
  )
  observeEvent(
    eventExpr = list(react.env$path, react.env$standard),
    handlerExpr = {
      if (!is.null(x = react.env$path) && nchar(x = react.env$path)) {
        if (isTRUE(react.env$standard)) {
          withProgress(
            message = 'Reading Input',
            expr = {
              setProgress(value = 0)
              tryCatch(
                expr = {
                  app.env$object <- LoadFileInput(path = react.env$path)
                  app.env$object <- DietSeurat(
                    app.env$object,
                    assays = "RNA"
                  )
                  app.env$object <- ConvertGeneNames(
                    object = app.env$object,
                    reference.names = rownames(x = refs$map),
                    homolog.table = getOption(x = 'Azimuth.app.homologs')
                  )
                  if (react.env$path %in% app.env$demo.files) {
                    app.env$demo <- TRUE
                  } else {
                    app.env$demo <- FALSE
                  }
                  app.env$object$query <- 'query'
                  Idents(object = app.env$object) <- 'query'
                  # check that no names overlap with reference
                  query.cell.names <- paste0("query", 1:ncol(x = app.env$object))
                  while (any(query.cell.names %in% Cells(x = refs$map))) {
                    query.cell.names <- paste0(query.cell.names, "x")
                  }
                  app.env$query.names <- Cells(x = app.env$object)
                  app.env$object <- RenameCells(object = app.env$object, new.names = query.cell.names)
                  
                  app.env$default.assay <- DefaultAssay(object = app.env$object)
                  new.mt <- any(grepl(
                    pattern = mito.pattern,
                    x = rownames(x = app.env$object)
                  ))
                  if (isFALSE(x = new.mt) & !isFALSE(x = react.env$mt)) {
                    removeUI(selector = '#pctmt', immediate = TRUE)
                  } else if (!isFALSE(x = new.mt) & isFALSE(x = react.env$mt)) {
                    insertUI(
                      selector = '#nfeature',
                      where = 'afterEnd',
                      immediate = TRUE,
                      ui = div(
                        id = 'pctmt',
                        numericInput(
                          inputId = 'minmt',
                          label = NULL,
                          value = 0,
                          width = '90%'
                        ),
                        numericInput(
                          'maxmt',
                          label = NULL,
                          value = 0,
                          width = '90%'
                        )
                      )
                    )
                  }
                  react.env$mt <- new.mt
                  common.features <- intersect(
                    x = rownames(x = app.env$object),
                    y = rownames(x = refs$map)
                  )
                  reject <- c(
                    length(x = common.features) < getOption(x = 'Azimuth.map.ngenes'),
                    length(x = Cells(x = app.env$object)) > getOption(x = 'Azimuth.app.max_cells')
                  )
                  if (any(reject)) {
                    app.env$object <- NULL
                    gc(verbose = FALSE)
                    reject <- min(which(x = reject))
                    app.env$messages <- paste(
                      c(
                        'Not enough genes in common with reference.',
                        'Too many cells.'
                      ),
                      'Try another dataset.'
                    )[reject]
                  }
                  if (isFALSE(x = react.env$xferopts)) {
                    removeUI(selector = '#xferopts', immediate = TRUE)
                  }
                  react.env$qc <- !any(reject)
                  react.env$path <- NULL
                },
                error = function(e) {
                  app.env$messages <- e$message
                  showNotification(
                    e$message,
                    duration = 10,
                    type = 'error',
                    closeButton = TRUE,
                    id = 'no-progress-notification'
                  )
                  app.env$object <- NULL
                  gc(verbose = FALSE)
                  react.env$path <- NULL
                }
              )
              setProgress(value = 1)
            }
          )
        }   
      }
    }
  )
  observeEvent(
    eventExpr = list(react.env$path, react.env$bridge),
    handlerExpr = {
      if (!is.null(x = react.env$path) && nchar(x = react.env$path)) {
        if (isTRUE(react.env$bridge)) {
          withProgress(
            message = 'Reading ATAC Peaks',
            expr = {
              setProgress(value = 0)
              tryCatch(
                expr = {
                  app.env$counts <- LoadFileInput(path = react.env$path, 
                                                  bridge = TRUE)
                  
                  app.env$counts <- DietSeurat(
                    app.env$counts,
                    assays = "RNA"
                  )
                  # app.env$object <- ConvertGeneNames(
                  #   object = app.env$object,
                  #   reference.names = rownames(x = refs$map),
                  #   homolog.table = getOption(x = 'Azimuth.app.homologs')
                  # )
                  if (react.env$path %in% app.env$demo.files) {
                    app.env$demo <- TRUE
                  } else {
                    app.env$demo <- FALSE
                  }
                  app.env$counts$query <- 'query'
                  react.env$chromatin_assay_1 <- TRUE
                  react.env$path <- NULL
                },
                error = function(e) {
                  app.env$messages <- e$message
                  showNotification(
                    e$message,
                    duration = 10,
                    type = 'error',
                    closeButton = TRUE,
                    id = 'no-progress-notification'
                  )
                  app.env$object <- NULL
                  gc(verbose = FALSE)
                  react.env$path <- NULL
                }
              )
              setProgress(value = 0.3)
            }
          )
        }
      }
    }
  )
  observeEvent(
    eventExpr = react.env$chromatin_assay_1, 
    handlerExpr = {
      if (isTRUE(x = react.env$chromatin_assay_1)) {
        withProgress(message = "Making Chromatin Assay", expr = {
          setProgress(value = 0.3)
          tryCatch(expr = {
            app.env$annotations <- refs$map[["ATAC"]]@annotation
            app.env$chromatin_assay_1 <- CreateChromatinAssay(
              counts = app.env$counts[["RNA"]]$counts, 
              sep = c(":", "-"),
              annotation = app.env$annotations
            )
            perc_overlap <- round(x = OverlapTotal(app.env$chromatin_assay_1, refs$map[["ATAC"]]), digits = 4)
            if (perc_overlap >= 70) {
              output$valuebox_overlap <- renderValueBox(expr = {
                valueBox(value = perc_overlap, subtitle = "Overlap Percentage",
                         icon = icon(name = "check"), color = "green")
              })
            }
            else if (perc_overlap < 70 & perc_overlap > 50) {
              output$valuebox_overlap<- renderValueBox(expr = {
                valueBox(value = perc_overlap, subtitle = "Overlap Percentage",
                         icon = icon(name = "exclamation-circle"), color = "yellow")
              })
            }
            else {
              output$valuebox_overlap <- renderValueBox(expr = {
                valueBox(value = perc_overlap, subtitle = "Overlap Percentage Too Low",
                         icon = icon(name = "exclamation-circle"), color = "red")
              })
            }
            jaccard <- round(x = PeakJaccard(app.env$chromatin_assay_1, refs$map[["ATAC"]]), digits = 4)
            if (jaccard >= 30) {
              output$valuebox_jaccard <- renderValueBox(expr = {
                valueBox(value = jaccard, subtitle = "Jaccard Similarity",
                         icon = icon(name = "check"), color = "green")
              })
            }
            else if (jaccard < 30 & jaccard > 20) {
              output$valuebox_jaccard<- renderValueBox(expr = {
                valueBox(value = jaccard, subtitle = "Jaccard Similarity",
                         icon = icon(name = "exclamation-circle"), color = "yellow")
              })
              
            }
            else {
              output$valuebox_jaccard <- renderValueBox(expr = {
                valueBox(value = jaccard, subtitle = "Jaccard Similarity is Low",
                         icon = icon(name = "exclamation-circle"), color = "red")
              })
            }
            query.cell.names <- paste0("query", 1:ncol(x = app.env$chromatin_assay_1))
            while (any(query.cell.names %in% Cells(x = refs$map))) {
              query.cell.names <- paste0(query.cell.names, 
                                         "x")
            }
            app.env$query.names <- Cells(x = app.env$chromatin_assay_1)
            app.env$chromatin_assay_1 <- RenameCells(object = app.env$chromatin_assay_1, 
                                                     new.names = query.cell.names)
            
            # remove this because we don't have mitochondrial genes, just peaks 
            removeUI(selector = '#pctmt', immediate = TRUE)
            react.env$mt <- FALSE
            
            react.env$requantify_multiome <- TRUE
            react.env$chromatin_assay_1 <- FALSE
          }, error = function(e) {
            app.env$messages <- e$message
            showNotification(e$message, duration = 10, 
                             type = "error", closeButton = TRUE, id = "no-progress-notification")
            app.env$chromatin_assay_1 <- NULL
            gc(verbose = FALSE)
            react.env$chromatin_assay_1 <- NULL
          })
          setProgress(value = 0.4)
        }
        )
      }
    })
  observeEvent(
    eventExpr = react.env$requantify_multiome, 
    handlerExpr = {
      if (isTRUE(x = react.env$requantify_multiome)) {
        withProgress(message = "Requantifying Peaks to Match Bridge", expr = {
          setProgress(value = 0.5)
          tryCatch(expr = {
            app.env$requantified_multiome <- RequantifyPeaks(app.env$chromatin_assay_1, refs$map)
            app.env$chromatin_assay_2 <- CreateChromatinAssay(
              counts = app.env$requantified_multiome,
              sep = c(":", "-"),
              annotation = app.env$annotations
            )
            app.env$object <- CreateSeuratObject(counts = app.env$chromatin_assay_2, assay = 'ATAC')
            app.env$object[['peak.orig']] <- app.env$chromatin_assay_1
            app.env$object$query <- "query"
            app.env$default.assay <- DefaultAssay(app.env$object)
            
            common.features <- intersect(
              x = rownames(x = app.env$object),
              y = rownames(x = refs$map[["ATAC"]])
            )
            reject_peaks <- c(
              length(x = common.features) < getOption(x = 'Azimuth.map.ngenes'),
              length(x = Cells(x = app.env$object)) > getOption(x = 'Azimuth.app.max_cells')
            )
            if (any(reject_peaks)) {
              app.env$object <- NULL
              gc(verbose = FALSE)
              reject_peaks <- min(which(x = reject_peaks))
              app.env$messages <- paste(
                c(
                  'Not enough peaks in common with reference.',
                  'Too many cells.'
                ),
                'Try another dataset.'
              )[reject_peaks]
            }
            if (isFALSE(x = react.env$xferopts)) {
              removeUI(selector = '#xferopts', immediate = TRUE)
            }
            
            react.env$qc <- !any(reject_peaks)
            react.env$requantify_multiome <- FALSE
          }, error = function(e) {
            app.env$messages <- e$message
            showNotification(e$message, duration = 10, 
                             type = "error", closeButton = TRUE, id = "no-progress-notification")
            app.env$chromatin_assay_2 <- NULL
            gc(verbose = FALSE)
            react.env$requantify_multiome <- NULL
          })
          setProgress(value = 1)
        }
        )
      }
    })
  observeEvent(
    eventExpr = react.env$qc,
    handlerExpr = {
      if (isTRUE(x = react.env$qc)) {
        for (id in qc.ids) {
          try(expr = enable(id = id), silent = TRUE)
        }
        ncount <- paste0('nCount_', app.env$default.assay)
        nfeature <- paste0('nFeature_', app.env$default.assay)
        if (!all(c(ncount, nfeature) %in% colnames(x = app.env$object[[]]))) {
          withProgress(
            message = 'Calculating nCount and nFeature',
            expr = {
              setProgress(value = 0)
              calcn <- as.data.frame(x = Seurat:::CalcN(object = GetAssayData(app.env$object, slot = "counts")))
              colnames(x = calcn) <- paste(
                colnames(x = calcn),
                app.env$default.assay,
                sep = '_'
              )
              app.env$object <- AddMetaData(
                object = app.env$object,
                metadata = calcn
              )
              rm(calcn)
              gc(verbose = FALSE)
              setProgress(value = 1)
            }
          )
        }
        ncount.val <- range(app.env$object[[ncount, drop = TRUE]])
        ncount.val <- c(
          floor(x = min(ncount.val)),
          ceiling(x = max(ncount.val))
        )
        ncount.min <- if (is.null(getOption(x = "Azimuth.app.ncount_min"))) {
          ncount.val[1]
        } else {
          max(ncount.val[1], getOption(x = "Azimuth.app.ncount_min"))
        }
        ncount.max <- if (is.null(getOption(x = "Azimuth.app.ncount_max"))) {
          ncount.val[2]
        } else {
          min(ncount.val[2], getOption(x = "Azimuth.app.ncount_max"))
        }
        updateNumericInput(
          session = session,
          inputId = 'num.ncountmin',
          label = paste('min', ncount),
          value = ncount.min,
          min = ncount.val[1],
          max = ncount.val[2]
        )
        updateNumericInput(
          session = session,
          inputId = 'num.ncountmax',
          label = paste('max', ncount),
          value = ncount.max,
          min = ncount.val[1],
          max = ncount.val[2]
        )
        nfeature.val <- range(app.env$object[[nfeature, drop = TRUE]])
        nfeature.val <- c(
          floor(x = min(nfeature.val)),
          ceiling(x = max(nfeature.val))
        )
        nfeature.min <- if (is.null(getOption(x = "Azimuth.app.nfeature_min"))) {
          nfeature.val[1]
        } else {
          max(nfeature.val[1], getOption(x = "Azimuth.app.nfeature_min"))
        }
        nfeature.max <- if (is.null(getOption(x = "Azimuth.app.nfeature_max"))) {
          nfeature.val[2]
        } else {
          min(nfeature.val[2], getOption(x = "Azimuth.app.nfeature_max"))
        }
        updateNumericInput(
          session = session,
          inputId = 'num.nfeaturemin',
          label = paste('min', nfeature),
          value = nfeature.min,
          min = nfeature.val[1],
          max = nfeature.val[2]
        )
        updateNumericInput(
          session = session,
          inputId = 'num.nfeaturemax',
          label = paste('max', nfeature),
          value = nfeature.max,
          min = nfeature.val[1],
          max = nfeature.val[2]
        )
        if (isTRUE(x = react.env$mt)) {
          app.env$object <- PercentageFeatureSet(
            object = app.env$object,
            pattern = mito.pattern,
            col.name = mt.key,
            assay = app.env$default.assay
          )
          mito.val <- range(app.env$object[[mt.key, drop = TRUE]])
          mito.val <- c(
            floor(x = min(mito.val)),
            ceiling(x = max(mito.val))
          )
          mito.min <- if (is.null(getOption(x = "Azimuth.app.pctmt_min"))) {
            mito.val[1]
          } else {
            max(mito.val[1], getOption(x = "Azimuth.app.pctmt_min"))
          }
          mito.max <- if (is.null(getOption(x = "Azimuth.app.pctmt_max"))) {
            mito.val[2]
          } else {
            min(mito.val[2], getOption(x = "Azimuth.app.pctmt_max"))
          }
          updateNumericInput(
            session = session,
            inputId = 'minmt',
            label = paste('min', mt.key),
            value = mito.min,
            min = mito.val[1],
            max = mito.val[2]
          )
          updateNumericInput(
            session = session,
            inputId = 'maxmt',
            label = paste('max', mt.key),
            value = mito.max,
            min = mito.val[1],
            max = mito.val[2]
          )
        }
        output$menu1 <- renderMenu(expr = {
          sidebarMenu(menuItem(
            text = 'Preprocessing',
            tabName = 'tab_preproc',
            icon = icon(name = 'filter'),
            selected = TRUE
          ))
        })
        ncellsupload <- length(x = colnames(x = app.env$object))
        app.env$ncellsupload <- ncellsupload
        app.env$messages <- paste(ncellsupload, 'cells uploaded')
        if (ncellsupload < getOption(x = 'Azimuth.map.ncells')) {
          output$valuebox.upload <- renderValueBox(expr = {
            valueBox(
              value = ncellsupload,
              subtitle = paste0(
                'cells uploaded - ',
                getOption(x = 'Azimuth.map.ncells'), ' required'
              ),
              icon = icon(name = 'times'),
              color = 'red'
            )
          })
        } else {
          output$valuebox.upload <- renderValueBox(expr = {
            valueBox(
              value = ncellsupload,
              subtitle = 'cells uploaded',
              icon = icon(name = 'check'),
              color = 'green'
            )
          })
          if (!is.null(x = googlesheet)) {
            try(
              expr = sheet_append(
                ss = googlesheet,
                data = data.frame(
                  'CELLSUPLOAD',
                  app_session_id,
                  ncellsupload
                )
              ),
              silent = TRUE
            )
          }
        }
        if (!is.null(x = react.env$progress)) {
          react.env$progress$close()
          enable(id = 'file')
          ToggleDemos(action = "enable", demos = demos)
          react.env$progress <- NULL
        }
        updateSelectizeInput(
          session = session,
          inputId = 'metadataxfer',
          choices = possible.metadata.transfer,
          selected = default_xfer,
          server = TRUE,
          options = selectize.opts[-which(x = names(x = selectize.opts) == 'maxItems')]
        )
        react.env$qc <- FALSE
        react.env$plot.qc <- TRUE
        if (isTRUE(x = do.bridge)) {
          react.env$dist.qc <- TRUE
        }
      }
    }
  )
  observeEvent(
    eventExpr = input$metadataxfer,
    handlerExpr = {
      if (length(x = input$metadataxfer) == 0) {
        disable(id = 'map')
      } else {
        enable(id = 'map')
      }
    },
    ignoreNULL = FALSE
  )
  # Filter and process the data
  observeEvent(
    eventExpr = input$map,
    handlerExpr = {
      react.env$start <- Sys.time()
      disable(id = 'file')
      ToggleDemos(action = "disable", demos = demos)
      for (id in qc.ids) {
        try(expr = disable(id = id), silent = TRUE)
      }
      react.env$progress <- Progress$new(style = 'notification')
      react.env$progress$set(
        value = 0,
        message = 'Filtering based on nCount and nFeature'
      )
      ncount <- paste0('nCount_', DefaultAssay(object = app.env$object))
      nfeature <- paste0('nFeature_', DefaultAssay(object = app.env$object))
      cells.use <- app.env$object[[ncount, drop = TRUE]] >= input$num.ncountmin &
        app.env$object[[ncount, drop = TRUE]] <= input$num.ncountmax &
        app.env$object[[nfeature, drop = TRUE]] >= input$num.nfeaturemin &
        app.env$object[[nfeature, drop = TRUE]] <= input$num.nfeaturemax
      if (isTRUE(x = react.env$mt)) {
        cells.use <- cells.use &
          app.env$object[[mt.key, drop = TRUE]] >= input$minmt &
          app.env$object[[mt.key, drop = TRUE]] <= input$maxmt
      }
      ncellspreproc <- sum(cells.use)
      app.env$ncellspreproc <- ncellspreproc
      # not enough cells available after filtering: reset filter elements
      if (ncellspreproc < getOption(x = "Azimuth.map.ncells")) {
        output$valuebox.preproc <- renderValueBox(expr = valueBox(
          value = ncellspreproc,
          subtitle = paste0(
            'cells after filtering - ',
            getOption(x = 'Azimuth.map.ncells'), ' required'
          ),
          icon = icon("times"),
          color = "red"
        ))
        react.env$qc <- TRUE
      } else {
        output$valuebox.preproc <- renderValueBox(expr = valueBox(
          value = ncellspreproc,
          subtitle = "cells after filtering",
          icon = icon("check"),
          color = "green"
        ))
        if (!is.null(googlesheet)) {
          try(sheet_append(
            ss = googlesheet,
            data = data.frame(
              "CELLSPREPROC",
              app_session_id,
              ncellspreproc
            )
          ))
        }
        app.env$object <- app.env$object[, cells.use]
        app.env$query.names <- app.env$query.names[cells.use]
        if (isTRUE(x = do.bridge)) {
          react.env$tfidf <- TRUE
        } else {
          react.env$sctransform <- TRUE
        }
      }
    }
  )
  observeEvent(
    eventExpr = react.env$sctransform,
    handlerExpr = {
      if (isTRUE(x = react.env$sctransform)) {
        react.env$progress$set(
          value = 0.2,
          message = 'Normalizing with SCTransform'
        )
        tryCatch(
          expr = {
            app.env$object <- suppressWarnings(expr = SCTransform(
              object = app.env$object,
              residual.features = rownames(x = refs$map),
              reference.SCT.model = slot(object = refs$map[["refAssay"]], name = "SCTModel.list")[["refmodel"]],
              method = "glmGamPoi",
              do.correct.umi = FALSE,
              do.scale = FALSE,
              do.center = TRUE,
              new.assay.name = "refAssay"
            ))
          },
          error = function(e) {
            app.env$object <- suppressWarnings(expr = SCTransform(
              object = app.env$object,
              residual.features = rownames(x = refs$map),
              reference.SCT.model = slot(object = refs$map[["refAssay"]], name = "SCTModel.list")[["refmodel"]],
              method = "poisson",
              do.correct.umi = FALSE,
              do.scale = FALSE,
              do.center = TRUE,
              new.assay.name = "refAssay"
            ))
          }
        )
        app.env$object[[paste0(c("nCount_", "nFeature_"), "refAssay")]] <- app.env$object[[paste0(c("nCount_", 
                                                                                                    "nFeature_"), 
                                                                                                  "RNA")]]
        app.env$messages <- c(
          app.env$messages,
          paste(ncol(x = app.env$object), "cells preprocessed")
        )
        react.env$anchors <- TRUE
        react.env$sctransform <- FALSE
      }
    }
  )
  observeEvent(
    eventExpr = react.env$tfidf, 
    handlerExpr = {
      if (isTRUE(x = react.env$tfidf)) {
        react.env$progress$set(
          value = 0.2, 
          message = "Normalizing with TFIDF"
        )
        tryCatch(
          expr = {
            app.env$object <- suppressWarnings(expr = RunTFIDF(object = app.env$object,
                                                               method = 1))
          }, error = function(e) {
            app.env$object <- suppressWarnings(expr = RunTFIDF(object = app.env$object,
                                                               method = 1))
          })
        app.env$messages <- c(
          app.env$messages, 
          paste(ncol(x = app.env$object), "cells preprocessed")
        )
        react.env$bridge_anchors <- TRUE
        react.env$tfidf <- FALSE
      }
    }
  )
  observeEvent(
    eventExpr = react.env$anchors,
    handlerExpr = {
      if (isTRUE(x = react.env$anchors)) {
        react.env$progress$set(value = 0.3, message = 'Finding anchors')
        app.env$anchors <- FindTransferAnchors(
          reference = refs$map,
          query = app.env$object,
          k.filter = NA,
          reference.neighbors = "refdr.annoy.neighbors",
          reference.assay = "refAssay",
          query.assay = 'refAssay',
          reference.reduction = 'refDR',
          normalization.method = 'SCT',
          recompute.residuals = FALSE,
          features = rownames(x = Loadings(refs$map[["refDR"]])), 
          dims = 1:getOption(x = "Azimuth.map.ndims"),
          n.trees = n.trees,
          verbose = TRUE,
          mapping.score.k = 100
        )
        nanchors <- nrow(x = slot(object = app.env$anchors, name = "anchors"))
        app.env$nanchors <- nanchors
        if (!is.null(googlesheet)) {
          try(sheet_append(
            ss = googlesheet,
            data = data.frame(
              "NANCHORS",
              app_session_id,
              nanchors
            )
          ))
        }
        if (nanchors < getOption(x = 'Azimuth.map.nanchors') |
            length(x = unique(x = slot(
              object = app.env$anchors, name = "anchors")[, 2]
            )) < 50
        ) {
          output$valuebox.mapped <- renderValueBox(expr = {
            valueBox(
              value = 'Failure',
              subtitle = paste0('Too few anchors identified (', nanchors, ')'),
              icon = icon(name = 'times'),
              color = 'red',
              width = 6
            )
          })
          app.env$object <- NULL
          app.env$anchors <- NULL
          react.env$progress$close()
          enable(id = 'file')
          ToggleDemos(action = "enable", demos = demos)
          gc(verbose = FALSE)
        } else {
          query.unique <- length(x = unique(x = slot(object = app.env$anchors, name = "anchors")[, "cell2"]))
          percent.anchors <- round(x = query.unique / ncol(x = app.env$object) * 100, digits = 2)
          if (percent.anchors <  getOption(x = "Azimuth.map.panchorscolors")[1]) {
            output$valuebox_panchors <- renderValueBox(expr = {
              valueBox(
                value = paste0(percent.anchors, "%"),
                subtitle = "% of query cells with anchors",
                color = 'red',
                icon = icon(name = 'times')
              )
            })
          } else if (percent.anchors <  getOption(x = "Azimuth.map.panchorscolors")[2]) {
            output$valuebox_panchors <- renderValueBox(expr = {
              valueBox(
                value = paste0(percent.anchors, "%"),
                subtitle = "% of query cells with anchors",
                color = 'yellow',
                icon = icon(name = 'exclamation-circle')
              )
            })
          } else {
            output$valuebox_panchors <- renderValueBox(expr = {
              valueBox(
                value = paste0(percent.anchors, "%"),
                subtitle = "% of query cells with anchors",
                color = 'green',
                icon = icon(name = 'check')
              )
            })
          }
          react.env$map <- TRUE
        }
        react.env$anchors <- FALSE
      }
    }
  )
  observeEvent(
    eventExpr = react.env$bridge_anchors, 
    handlerExpr = {
      if (isTRUE(x = react.env$bridge_anchors)) {
        react.env$progress$set(value = 0.3, message = "Finding anchors")
        app.env$anchors <- FindBridgeTransferAnchors(extended.reference = refs$map,
                                                     query = app.env$object,
                                                     reduction = "lsiproject",
                                                     scale = FALSE,
                                                     dims = 2:50) # making this a default
        nanchors <- nrow(x = slot(object = app.env$anchors, 
                                  name = "anchors"))
        app.env$nanchors <- nanchors
        if (!is.null(googlesheet)) {
          try(sheet_append(ss = googlesheet, data = data.frame("NANCHORS", 
                                                               app_session_id, nanchors)))
        }
        if (nanchors < getOption(x = "Azimuth.map.nanchors") | 
            length(x = unique(x = slot(object = app.env$anchors, 
                                       name = "anchors")[, 2])) < 50) {
          output$valuebox.mapped <- renderValueBox(expr = {
            valueBox(value = "Failure", subtitle = paste0("Too few anchors identified (", 
                                                          nanchors, ")"), icon = icon(name = "times"), 
                     color = "red", width = 6)
          })
          app.env$object <- NULL
          app.env$anchors <- NULL
          react.env$progress$close()
          enable(id = "file")
          ToggleDemos(action = "enable", demos = demos)
          
          gc(verbose = FALSE)
        }
        else {
          query.unique <- length(x = unique(x = slot(object = app.env$anchors, 
                                                     name = "anchors")[, "cell2"]))
          percent.anchors <- round(x = query.unique/ncol(x = app.env$object) * 
                                     100, digits = 2)
          if (percent.anchors < getOption(x = "Azimuth.map.panchorscolors")[1]) {
            output$valuebox_panchors <- renderValueBox(expr = {
              valueBox(value = paste0(percent.anchors, 
                                      "%"), subtitle = "% of query cells with anchors", 
                       color = "red", icon = icon(name = "times"))
            })
          }
          else if (percent.anchors < getOption(x = "Azimuth.map.panchorscolors")[2]) {
            output$valuebox_panchors <- renderValueBox(expr = {
              valueBox(value = paste0(percent.anchors, 
                                      "%"), subtitle = "% of query cells with anchors", 
                       color = "yellow", icon = icon(name = "exclamation-circle"))
            })
          }
          else {
            output$valuebox_panchors <- renderValueBox(expr = {
              valueBox(value = paste0(percent.anchors, 
                                      "%"), subtitle = "% of query cells with anchors", 
                       color = "green", icon = icon(name = "check"))
            })
          }
          react.env$mapquery <- TRUE
        }
        react.env$bridge_anchors <- FALSE
      }
    }
  )
  observeEvent(
    eventExpr = list(react.env$map, input$metadataxfer),
    handlerExpr = {
      if (isTRUE(x = react.env$map)) {
        if (is.null(x = input$metadataxfer)) {
          app.env$metadataxfer <- names(x = GetColorMap(object = refs$map))
        } else {
          app.env$metadataxfer <- input$metadataxfer
        }
        react.env$progress$set(value = 0.5, message = 'Mapping cells')
        refdata <- lapply(X = app.env$metadataxfer, function(x) {
          refs$map[[x, drop = TRUE]]
        })
        names(x = refdata) <- app.env$metadataxfer
        if (do.adt) {
          refdata[["impADT"]] <- GetAssayData(
            object = refs$map[['ADT']],
            slot = 'data'
          )
        }
        app.env$object <- TransferData(
          reference = refs$map,
          query = app.env$object,
          dims = 1:getOption(x = "Azimuth.map.ndims"),
          anchorset = app.env$anchors,
          refdata = refdata,
          n.trees = n.trees,
          store.weights = TRUE
        )
        app.env$singlepred <- NULL
        for(i in app.env$metadataxfer) {
          app.env$singlepred <- c(app.env$singlepred, length(x = unique(x = as.vector(x = app.env$object[[paste0("predicted.", i), drop = TRUE]]))) == 1)
          app.env$object[[paste0("predicted.", i), drop = TRUE]] <- factor(
            x = app.env$object[[paste0("predicted.", i), drop = TRUE]],
            levels = levels(x = refs$map[[i, drop = TRUE]])
          )
        }
        singlepred <- all(app.env$singlepred)
        if (singlepred & (length(x = setdiff(possible.metadata.transfer, app.env$metadataxfer)) > 0)) {
          showNotification(
            paste0("Only one predicted class. Re-running with all metadata."),
            duration = 5,
            type = 'warning',
            closeButton = TRUE,
            id = 'no-progress-notification'
          )
          updateSelectizeInput(
            session = getDefaultReactiveDomain(),
            inputId = 'metadataxfer',
            choices = possible.metadata.transfer,
            selected = possible.metadata.transfer,
          )
          app.env$metadataxfer <- input$metadataxfer
        } else if (singlepred) {
          showNotification(
            paste0(
              "Only one predicted class: ",
              app.env$object[[paste0("predicted.", app.env$metadataxfer[1]), drop = TRUE]][1]
            ),
            duration = 5,
            type = 'warning',
            closeButton = TRUE,
            id = 'no-progress-notification'
          )
          app.env$object <- NULL
          app.env$anchors <- NULL
          react.env$path <- NULL
          react.env$map <- FALSE
          react.env$progress$close()
          enable(id = 'file')
          ToggleDemos(action = "enable", demos = demos)
          gc(verbose = FALSE)
        } else {
          app.env$object <- IntegrateEmbeddings(
            anchorset = app.env$anchors,
            reference = refs$map,
            query = app.env$object,
            reductions = "pcaproject",
            reuse.weights.matrix = TRUE
          )
          if (is.null(x = getOption(x = "Azimuth.app.default_metadata"))) {
            app.env$default.metadata <- names(x = refdata)[1]
          } else {
            if (getOption(x = "Azimuth.app.default_metadata") %in% names(x = refdata)) {
              app.env$default.metadata <- getOption(x = "Azimuth.app.default_metadata")
            } else {
              app.env$default.metadata <- names(x = refdata)[1]
            }
          }
          react.env$score <- TRUE
          # react.env$cluster.score <- TRUE
          react.env$map <- FALSE
        }
      }
    }
  )
  observeEvent(
    eventExpr = list(react.env$mapquery, input$metadataxfer), 
    handlerExpr = {
      if (isTRUE(x = react.env$mapquery)) {
        if (is.null(x = input$metadataxfer)) {
          app.env$metadataxfer <- names(x = GetColorMap(object = refs$map))
        }
        else {
          app.env$metadataxfer <- input$metadataxfer
        }
        react.env$progress$set(value = 0.5, message = "Mapping cells")
        refdata <- as.list(app.env$metadataxfer)
        names(refdata) <- app.env$metadataxfer
        if (do.adt) {
          refdata[["impADT"]] <- GetAssayData(object = refs$map[["ADT"]], 
                                              slot = "data")
        }
        app.env$object <-  MapQuery(anchorset = app.env$anchors,  # deleted transfer data 
                                    reference = refs$map, 
                                    query = app.env$object, 
                                    refdata = refdata,
                                    reduction.model = "refUMAP")
        app.env$singlepred <- NULL
        for (i in app.env$metadataxfer) { 
          app.env$singlepred <- c(app.env$singlepred, 
                                  length(x = unique(x = as.vector(x = app.env$object[[paste0("predicted.", 
                                                                                             i), drop = TRUE]]))) == 1)
          app.env$object[[paste0("predicted.", i), drop = TRUE]] <- factor(x = app.env$object[[paste0("predicted.", 
                                                                                                      i), drop = TRUE]], levels = levels(x = refs$map[[i, 
                                                                                                                                                       drop = TRUE]]))
        }
        singlepred <- all(app.env$singlepred)
        if (singlepred & (length(x = setdiff(possible.metadata.transfer, 
                                             app.env$metadataxfer)) > 0)) {
          showNotification(paste0("Only one predicted class. Re-running with all metadata."), 
                           duration = 5, type = "warning", closeButton = TRUE, 
                           id = "no-progress-notification")
          updateSelectizeInput(session = getDefaultReactiveDomain(), 
                               inputId = "metadataxfer", choices = possible.metadata.transfer, 
                               selected = possible.metadata.transfer, )
          app.env$metadataxfer <- input$metadataxfer
        }
        else if (singlepred) {
          showNotification(paste0("Only one predicted class: ", 
                                  app.env$object[[paste0("predicted.", app.env$metadataxfer[1]), 
                                                  drop = TRUE]][1]), duration = 5, type = "warning", 
                           closeButton = TRUE, id = "no-progress-notification")
          app.env$object <- NULL
          app.env$bridge_anchors <- NULL
          react.env$path <- NULL
          react.env$mapquery <- FALSE
          react.env$progress$close()
          enable(id = "file")
          ToggleDemos(action = "enable", demos = demos)
          gc(verbose = FALSE)
        }
        else {
          if (is.null(x = getOption(x = "Azimuth.app.default_metadata"))) {
            app.env$default.metadata <- names(x = refdata)[1]
          }
          else {
            if (getOption(x = "Azimuth.app.default_metadata") %in% 
                names(x = refdata)) {
              app.env$default.metadata <- getOption(x = "Azimuth.app.default_metadata")
            }
            else {
              app.env$default.metadata <- names(x = refdata)[1]
            }
          }
          #react.env$score <- TRUE - ill do this after getting gene activity scores 
          react.env$gene_activity <- TRUE
          react.env$mapquery <- FALSE
        }
      }
    }
  )
  observeEvent(
    eventExpr = react.env$gene_activity, 
    handlerExpr = {
      if (isTRUE(react.env$gene_activity)) {
        # Use original peaks 
        DefaultAssay(app.env$object) <- "peak.orig"
        app.env$transcripts <- GetTranscripts(app.env$object)
        temp <- RequantifyPeaks(app.env$object, app.env$transcripts)
        #add feature matrix to Chromatin Assay 
        app.env$object[['RNA']] <- CreateAssayObject(counts = temp)
        
        #Normalize the feature data
        app.env$object <- NormalizeData(
          object = app.env$object,
          assay = 'RNA',
          normalization.method = 'LogNormalize',
          scale.factor = median(unlist(app.env$object[[grep("nCount", 
                                                            colnames(app.env$object@meta.data))]]))
        )
        react.env$gene_activity <- FALSE
        react.env$score <- TRUE
      }
    }
  )
  observeEvent(
    eventExpr = react.env$cluster.score,
    handlerExpr = {
      if (isTRUE(react.env$cluster.score)) {
        # post mapping QC
        if (isTRUE(x = do.bridge)){
          qc.stat <- round(
            x = ClusterPreservationScore(
              query = app.env$object,
              ds.amount = getOption(x = "Azimuth.map.postmapqcds"),
              type = "bridge"
            ),
            digits = 2
          )
        } else {
          qc.stat <- round(
            x = ClusterPreservationScore(
              query = app.env$object,
              ds.amount = getOption(x = "Azimuth.map.postmapqcds"),
              type = "standard"
            ),
            digits = 2
          )
        }
        if (!is.null(googlesheet)) {
          try(sheet_append(
            ss = googlesheet,
            data = data.frame(
              "CLUSTERPRESERVATIONQC",
              app_session_id,
              qc.stat
            )
          ))
        }
        app.env$clusterpreservationqc <- qc.stat
        if (qc.stat <  getOption(x = "Azimuth.map.postmapqccolors")[1]) {
          output$valuebox_mappingqcstat <- renderValueBox(expr = {
            valueBox(
              value = paste0(qc.stat, "/5"),
              subtitle = "cluster preservation score",
              color = 'red',
              icon = icon(name = 'times')
            )
          })
        } else if (qc.stat <  getOption(x = "Azimuth.map.postmapqccolors")[2]) {
          output$valuebox_mappingqcstat <- renderValueBox(expr = {
            valueBox(
              value = paste0(qc.stat, "/5"),
              subtitle = "cluster preservation score",
              color = 'yellow',
              icon = icon(name = 'exclamation-circle')
            )
          })
        } else {
          output$valuebox_mappingqcstat <- renderValueBox(expr = {
            valueBox(
              value = paste0(qc.stat, "/5"),
              subtitle = "cluster preservation score",
              color = 'green',
              icon = icon(name = 'check')
            )
          })
        }
        react.env$cluster.score <- FALSE
        react.env$transform <- TRUE
      }
    }
  )
  observeEvent(
    eventExpr = react.env$score,
    handlerExpr = {
      if (isTRUE(x = react.env$score)) {
        react.env$progress$set(
          value = 0.7,
          message = 'Calculating mapping score'
        )
        # post mapping QC
        if (isTRUE(x = do.bridge)){
          app.env$object[['refAssay']] <- app.env$object[['ATAC']]
          DefaultAssay(app.env$object) <- 'refAssay'
          DefaultAssay(app.env$object[["ref.Bridge.reduc"]]) <- 'refAssay'
          app.env$object <- FindTopFeatures(app.env$object,
                                            min.cutoff = "q0")
          qc.stat <- round(
            x = ClusterPreservationScore(
              query = app.env$object,
              ds.amount = getOption(x = "Azimuth.map.postmapqcds"),
              type = "bridge"
            ),
            digits = 2
          )
        } else {
          qc.stat <- round(
            x = ClusterPreservationScore(
              query = app.env$object,
              ds.amount = getOption(x = "Azimuth.map.postmapqcds"),
              type = "standard"
            ),
            digits = 2
          )
        }
        if (!is.null(googlesheet)) {
          try(sheet_append(
            ss = googlesheet,
            data = data.frame(
              "CLUSTERPRESERVATIONQC",
              app_session_id,
              qc.stat
            )
          ))
        }
        app.env$clusterpreservationqc <- qc.stat
        if (qc.stat <  getOption(x = "Azimuth.map.postmapqccolors")[1]) {
          output$valuebox_mappingqcstat <- renderValueBox(expr = {
            valueBox(
              value = paste0(qc.stat, "/5"),
              subtitle = "cluster preservation score",
              color = 'red',
              icon = icon(name = 'times')
            )
          })
        } else if (qc.stat <  getOption(x = "Azimuth.map.postmapqccolors")[2]) {
          output$valuebox_mappingqcstat <- renderValueBox(expr = {
            valueBox(
              value = paste0(qc.stat, "/5"),
              subtitle = "cluster preservation score",
              color = 'yellow',
              icon = icon(name = 'exclamation-circle')
            )
          })
        } else {
          output$valuebox_mappingqcstat <- renderValueBox(expr = {
            valueBox(
              value = paste0(qc.stat, "/5"),
              subtitle = "cluster preservation score",
              color = 'green',
              icon = icon(name = 'check')
            )
          })
        }
        if (isTRUE(x = do.bridge)){
          refdr <- subset(
            x = app.env$anchors@object.list[[1]][["Bridge.reduc"]], # im gonna try calling this Bridge.Reduc
            cells = paste0(Cells(x = app.env$object), "_query")
          )
          refdr <- RenameCells(
            object = refdr, 
            new.names = Cells(x = app.env$object)
          )
          refdr.ref <- subset(
            x = app.env$anchors@object.list[[1]][["Bridge.reduc"]], 
            cells = paste0(Cells(x = refs$map), "_reference")
          )
          refdr.ref <- RenameCells(
            object = refdr.ref, 
            new.names = Cells(x = refs$map[["Bridge"]])
          )
        } else {
          refdr <- subset(
            x = app.env$anchors@object.list[[1]][["pcaproject.l2"]],
            cells = paste0(Cells(x = app.env$object), "_query")
          )
          refdr <- RenameCells(
            object = refdr,
            new.names = Cells(x = app.env$object)
          )
          refdr.ref <- subset(
            x = app.env$anchors@object.list[[1]][["pcaproject.l2"]],
            cells = paste0(Cells(x = refs$map), "_reference")
          )
          refdr.ref <- RenameCells(
            object = refdr.ref,
            new.names = Cells(x = refs$map)
          )
        }
        if (Sys.getenv("RSTUDIO") == "1") {
          plan("sequential")
        }
        # reduce size of object in anchorset
        app.env$anchors@object.list[[1]] <- DietSeurat(
          object = app.env$anchors@object.list[[1]]
        )
        app.env$anchors@object.list[[1]] <- subset(
          x = app.env$anchors@object.list[[1]],
          features = c(rownames(x = app.env$anchors@object.list[[1]])[1])
        )
        app.env$anchors@object.list[[1]] <- RenameCells(
          object = app.env$anchors@object.list[[1]],
          new.names = unname(obj = sapply(
            X = Cells(x = app.env$anchors@object.list[[1]]),
            FUN = function(x) {
              return(gsub(pattern = "_reference", replacement = "", x = x))
            }
          )))
        app.env$anchors@object.list[[1]] <- RenameCells(
          object = app.env$anchors@object.list[[1]],
          new.names = sapply(
            X = Cells(x = app.env$anchors@object.list[[1]]),
            FUN = function(x) {
              return(gsub(pattern = "_query", replacement = "", x = x))
            },
            USE.NAMES = FALSE
          )
        )
        app.env$anchors@object.list[[1]]@meta.data <- data.frame()
        app.env$anchors@object.list[[1]]@active.ident <- factor()
        mapping.score.k <- min(c(
          50,
          length(x = unique(x = app.env$anchors@anchors[, 1])),
          length(x = unique(x = app.env$anchors@anchors[, 2])))
        )
        app.env$mapping.score <- future(
          expr = {
            MappingScore(
              anchors = app.env$anchors@anchors,
              combined.object = app.env$anchors@object.list[[1]],
              query.neighbors =  slot(object = app.env$anchors, name = "neighbors")[["query.neighbors"]],
              query.weights = Tool(object = app.env$object, slot = "TransferData")$weights.matrix,
              query.embeddings = Embeddings(object = refdr),
              ref.embeddings = Embeddings(object = refdr.ref),
              nn.method = "annoy",
              n.trees = n.trees,
              ndim = getOption(x = "Azimuth.map.ndims"),
              kanchors = mapping.score.k
            )
          }
        )
        app.env$object <- AddMetaData(
          object = app.env$object,
          metadata = rep(x = 0, times = ncol(x = app.env$object)),
          col.name = "mapping.score"
        )
        app.env$anchors <- NULL
        rm(refdr)
        gc(verbose = FALSE)
        # react.env$transform <- TRUE
        react.env$cluster.score <- TRUE
        react.env$score <- FALSE
      }
    }
  )
  observeEvent(
    eventExpr = react.env$transform,
    handlerExpr = {
      if (isTRUE(x = react.env$transform)) {
        if (isTRUE(x = do.bridge)) {
          react.env$progress$set(value = 0.8)
          suppressWarnings(expr = app.env$object[["umap.proj"]] <- app.env$object[["ref.umap"]])
        }
        else {
          react.env$progress$set(value = 0.8, message = 'Running UMAP transform')
          app.env$object[["query_ref.nn"]] <- FindNeighbors(
            object = Embeddings(refs$map[["refDR"]])[, 1:getOption("Azimuth.map.ndims")],
            query = Embeddings(app.env$object[["integrated_dr"]]),
            return.neighbor = TRUE,
            l2.norm = TRUE,
            n.trees = n.trees
          )
          app.env$object <- NNTransform(
            object = app.env$object,
            meta.data = refs$map[[]]
          )
          app.env$object[['umap.proj']] <- RunUMAP(
            object = app.env$object[['query_ref.nn']],
            reduction.model = refs$map[['refUMAP']],
            reduction.key = 'UMAP_'
          )
          app.env$object <- SetAssayData(
            object = app.env$object,
            assay = 'refAssay',
            slot = 'scale.data',
            new.data = new(Class = 'matrix')
          )
        }
        gc(verbose = FALSE)
        app.env$messages <- c(
          app.env$messages,
          paste(ncol(x = app.env$object), "cells mapped")
        )
        react.env$biomarkers <- TRUE
        if (isTRUE(x = do.bridge)) {
          react.env$motif <- TRUE
        }
        react.env$transform <- FALSE
      }
    }
  )
  observeEvent(
    eventExpr = react.env$biomarkers,
    handlerExpr = {
      if (isTRUE(x = react.env$biomarkers)) {
        react.env$progress$set(
          value = 0.95,
          message = 'Running differential expression'
        )
        app.env$gene.assay <- "RNA"
        for (i in app.env$metadataxfer[!app.env$singlepred]) {
          app.env$diff.expr[[paste(app.env$gene.assay, i, sep = "_")]] <- wilcoxauc(
            X = app.env$object,
            group_by = paste0("predicted.", i),
            assay = 'data',
            seurat_assay = app.env$gene.assay
          )
          if (isTRUE(x = do.adt)) {
            app.env$diff.expr[[paste(adt.key, i, sep = "_")]] <- wilcoxauc(
              X = app.env$object,
              group_by = paste0("predicted.", i),
              assay = 'data',
              seurat_assay = adt.key
            )
          }
        }
        if (isTRUE(x = do.bridge)) {
          output$menu2 <- renderMenu(expr = {
            sidebarMenu(
              menuItem(
                text = "Cell Plots",
                tabName = "tab_cell",
                icon = icon("chart-area")
              ),
              menuItem(
                text = "Feature Plots",
                tabName = "tab_feature",
                icon = icon("chart-area")
              ),
              menuItem(
                text = "Motifs",
                tabName = "tab_motif",
                icon = icon("chart-area")
              ),
              menuItem(
                text = "Download Results",
                tabName = "tab_download",
                icon = icon("file-download")
              )
            )
          })
        } else {
          output$menu2 <- renderMenu(expr = {
            sidebarMenu(
              menuItem(
                text = "Cell Plots",
                tabName = "tab_cell",
                icon = icon("chart-area")
              ),
              menuItem(
                text = "Feature Plots",
                tabName = "tab_feature",
                icon = icon("chart-area")
              ),
              menuItem(
                text = "Download Results",
                tabName = "tab_download",
                icon = icon("file-download")
              )
            )
          })
          # Finalize the log
          mapping.time <- difftime(
            time1 = Sys.time(),
            time2 = react.env$start,
            units = 'secs'
          )
          time.fmt <- FormatDiffTime(dt = mapping.time)
          app.env$messages <- c(
            app.env$messages,
            time.fmt
          )
          if (!is.null(x = googlesheet)) {
            try(expr = sheet_append(
              ss = googlesheet,
              data = data.frame(
                "MAPPINGTIME",
                app_session_id,
                as.numeric(x = mapping.time)
              )
            ))
          }
          if (!is.null(x = googlesheet)) {
            try(
              expr = sheet_append(
                ss = googlesheet,
                data = data.frame(
                  "SUMMARY",
                  app_session_id,
                  basename(getOption(x = 'Azimuth.app.reference')),
                  ReferenceVersion(object = refs$map),
                  app.env$demo,
                  app.env$ncellsupload,
                  app.env$ncellspreproc,
                  as.numeric(x = mapping.time),
                  Sys.Date(),
                  app.env$nanchors,
                  app.env$clusterpreservationqc
                )
              ),
              silent = TRUE
            )
          }
        }
        app.env$object <- RenameCells(object = app.env$object, new.names = app.env$query.names)
        if (!isTRUE(x = do.bridge)) {
          react.env$progress$close()
        }
        enable(id = 'file')
        ToggleDemos(action = "enable", demos = demos)
        react.env$metadata <- TRUE
        react.env$biomarkers <- FALSE
      }
    }
  )
  observeEvent(eventExpr = react.env$motif, handlerExpr = {
    if (isTRUE(x = react.env$motif)) {
      react.env$progress$set(value = 0.98, message = "Running Motif Analysis")
      DefaultAssay(app.env$object) <- "ATAC"
      # Remove peaks on scaffolds 
      main.chroms <- standardChromosomes(BSgenome.Hsapiens.UCSC.hg38)
      keep.peaks <- which(as.character(seqnames(granges(app.env$object))) %in% main.chroms)
      app.env$object[["ATAC"]] <- subset(app.env$object[["ATAC"]], features = rownames(app.env$object[["ATAC"]])[keep.peaks])
      
      pfm <- getMatrixSet(
        x = JASPAR2020,
        opts = list(species = 9606, all_versions = FALSE)
      )
      
      # Find Motifs
      for (i in app.env$metadataxfer[!app.env$singlepred]) {
        app.env$peaks.diff.expr[[paste(app.env$default.assay, i, sep = "_")]] <- wilcoxauc(X = app.env$object,
                                                                                           group_by = paste0("predicted.", i),
                                                                                           assay = "data", 
                                                                                           seurat_assay = app.env$default.assay)
        peaks.list <- split(app.env$peaks.diff.expr[[paste(app.env$default.assay, i, sep = "_")]], 
                            f = app.env$peaks.diff.expr[[paste(app.env$default.assay, i, sep = "_")]]$group)
        motif.list <- list()
        for (num in 1:length(peaks.list)){
          if (nrow(peaks.list[[num]]) > 0){
            peaks.list[[num]] <- peaks.list[[num]][order(peaks.list[[num]]$logFC, decreasing = TRUE), ]
            if (nrow(peaks.list[[num]]) > 1000) {
              top.da.peak <- peaks.list[[num]][1:1000,]$feature   #[peaks.list[[num]]$logFC > 0.5, ]$feature
            } else {
              top.da.peak <- peaks.list[[num]][peaks.list[[num]]$pval < 0.05, ]$feature
            }
            enriched.motifs <- FindMotifs( 
              object = refs$map[["ATAC"]],
              features = top.da.peak)
            enriched.motifs$group <- names(peaks.list[num])
            motif.list[[num]] <- enriched.motifs
          }  
        }
        app.env$motif.diff.expr[[paste(app.env$default.assay, i, sep = "_")]] <- dplyr::bind_rows(motif.list)
        
      }
      # Finalize the log
      mapping.time <- difftime(
        time1 = Sys.time(),
        time2 = react.env$start,
        units = 'secs'
      )
      time.fmt <- FormatDiffTime(dt = mapping.time)
      app.env$messages <- c(
        app.env$messages,
        time.fmt
      )
      if (!is.null(x = googlesheet)) {
        try(expr = sheet_append(
          ss = googlesheet,
          data = data.frame(
            "MAPPINGTIME",
            app_session_id,
            as.numeric(x = mapping.time)
          )
        ))
      }
      if (!is.null(x = googlesheet)) {
        try(
          expr = sheet_append(
            ss = googlesheet,
            data = data.frame(
              "SUMMARY",
              app_session_id,
              basename(getOption(x = 'Azimuth.app.reference')),
              ReferenceVersion(object = refs$map),
              app.env$demo,
              app.env$ncellsupload,
              app.env$ncellspreproc,
              as.numeric(x = mapping.time),
              Sys.Date(),
              app.env$nanchors,
              app.env$clusterpreservationqc
            )
          ),
          silent = TRUE
        )
      }
      react.env$progress$close()
      react.env$motif <- FALSE
    }
  })
  # Update input controls
  observeEvent(
    eventExpr = react.env$metadata,
    handlerExpr = {
      if (isTRUE(x = react.env$metadata)) {
        #  Add the discrete metadata dropdowns
        metadata.discrete <- sort(
          x = PlottableMetadataNames(
            object = app.env$object,
            exceptions = app.env$metadataxfer,
            min.levels = 1,
            max.levels = 50
          )
        )
        app.env$metadata.discrete <- metadata.discrete
        for (id in c('metarow', 'metacol', 'metagroup', 'metagroup.motif')) {
          if (id == 'metarow') {
            show.metadata <- 'query'
          } else {
            show.metadata <- paste0("predicted.", app.env$default.metadata)
          }
          updateSelectizeInput(
            session = session,
            inputId = id,
            choices = metadata.discrete,
            selected = show.metadata,
            server = TRUE,
            options = selectize.opts
          )
        }
        updateSelectizeInput(
          session = session,
          inputId = 'metacolor.query',
          choices = c(grep(pattern = '^predicted.', x = metadata.discrete, value = TRUE),
                      grep(pattern = '^predicted.', x = metadata.discrete, value = TRUE, invert = TRUE)),
          selected = paste0("predicted.", app.env$default.metadata),
          server = TRUE,
          options = selectize.opts[-which(x = names(x = selectize.opts) == 'maxItems')]
        )
        # Add the continuous metadata dropdown
        metadata.cont <- sort(x = setdiff(
          x = colnames(x = app.env$object[[]]),
          y = metadata.discrete
        ))
        metadata.cont <- Filter(
          f = function(x) {
            return(is.numeric(x = app.env$object[[x, drop = TRUE]]))
          },
          x = metadata.cont
        )
        # Add prediction scores for all classes to continuous metadata
        metadata.cont <- sort(x = metadata.cont)
        if (any(grepl(pattern = "predicted.*.score", x = metadata.cont))) {
          metadata.cont <- metadata.cont[-grep(pattern = "predicted.*.score", x = metadata.cont)]
        }
        if (any(grepl(pattern = "*_refAssay", x = metadata.cont))) {
          metadata.cont <- metadata.cont[-grep(pattern = "*_refAssay", x = metadata.cont)]
        }
        max.predictions <- paste0("predicted.", app.env$metadataxfer, ".score")
        names(x = max.predictions) <- app.env$metadataxfer
        max.predictions <- as.list(x = max.predictions)
        prediction.score.names <-
          lapply(X = app.env$metadataxfer, FUN = function(x) {
            key <- Key(object = app.env$object[[paste0("prediction.score.", x)]])
            ids <- paste0(rownames(x = app.env$object[[paste0("prediction.score.", x)]]))
            values <- paste0(key, ids)
            names(x = values) <- ids
            return(values)
          })
        names(x = prediction.score.names) <- paste0("Prediction scores - ", app.env$metadataxfer)
        metadata.cont <- c(
          list("Max prediction scores" = max.predictions),
          prediction.score.names,
          list("Other Metadata" = metadata.cont)
        )
        app.env$metadata.cont <- metadata.cont
        updateSelectizeInput(
          session = session,
          inputId = 'metadata.cont',
          choices = app.env$metadata.cont,
          selected = '',
          server = TRUE,
          options = selectize.opts
        )
        updateSelectizeInput(
          session = session,
          inputId = 'metadata.cont.motif',
          choices = app.env$metadata.cont,
          selected = '',
          server = TRUE,
          options = selectize.opts
        )
        updateSelectizeInput(
          session = session,
          inputId = 'metacolor.ref',
          choices = c(grep(pattern = '^predicted.', x = app.env$metadataxfer, value = TRUE), # re-ordering not working...
                      grep(pattern = '^predicted.', x = app.env$metadataxfer, value = TRUE, invert = TRUE)),
          selected = app.env$default.metadata,
          server = TRUE,
          options = selectize.opts[-which(x = names(x = selectize.opts) == 'maxItems')]
        )
        react.env$features <- TRUE
        if (isTRUE(x = do.bridge)) {
          react.env$motif.features <- TRUE
        } 
        react.env$metadata <- FALSE
      }
    }
  )
  observeEvent(
    eventExpr = react.env$features,
    handlerExpr = {
      if (isTRUE(x = react.env$features)) {
        DefaultAssay(app.env$object) <- "RNA"
        app.env$default.feature <- ifelse(
          test = getOption(x = 'Azimuth.app.default_gene') %in% rownames(x = app.env$object),
          yes = getOption(x = 'Azimuth.app.default_gene'),
          no = VariableFeatures(object = app.env$object)[1]
        )
        app.env$features <- unique(x = c(
          FilterFeatures(
            features = VariableFeatures(object = app.env$object)[1:selectize.opts$maxOptions]
          ),
          FilterFeatures(features = rownames(x = app.env$object))
        ))
        updateSelectizeInput(
          session = session,
          inputId = 'feature',
          label = 'Feature',
          choices = app.env$features,
          selected = app.env$default.feature,
          server = TRUE,
          options = selectize.opts
        )
        if (isTRUE(x = do.adt)) {
          # app.env$adt.features <- sort(x = FilterFeatures(features = rownames(
          #   x = app.env$object[[adt.key]]
          # )))
          app.env$adt.features <- sort(x = rownames(
            x = app.env$object[[adt.key]]
          ))
          updateSelectizeInput(
            session = session,
            inputId = 'adtfeature',
            choices = app.env$adt.features,
            selected = '',
            server = TRUE,
            options = selectize.opts
          )
        }
        react.env$features <- FALSE
        if (!isTRUE(x = do.bridge)){
          react.env$markers <- TRUE
        }
      }
    }
  )
  observeEvent(
    eventExpr = react.env$motif.features, 
    handlerExpr = {
      if (isTRUE(x = react.env$motif.features)) {
        DefaultAssay(app.env$object) <- app.env$default.assay
        app.env$default.motif.feature <- ifelse(test = getOption(x = 'Azimuth.app.default_motif') %in% 
                                                  row.names(x = app.env$object[[app.env$default.assay]]@data), 
                                                yes = getOption(x = 'Azimuth.app.default_motif'), 
                                                no = row.names(x = app.env$object[[app.env$default.assay]]@data)[1])
        app.env$motif.features <- unique(x = row.names(x = app.env$object[[app.env$default.assay]]@data)) # c(FilterFeatures(features =
        updateSelectizeInput(session = session, inputId = "motif.feature", 
                             label = "Motif", choices = app.env$motif.features, 
                             selected = app.env$default.motif.feature, server = TRUE, 
                             options = selectize.opts)
        
        if (isTRUE(x = do.adt)) {
          app.env$adt.features <- sort(x = rownames(x = app.env$object[[adt.key]]))
          updateSelectizeInput(session = session, inputId = "adtfeature", 
                               choices = app.env$adt.features, selected = "", 
                               server = TRUE, options = selectize.opts)
        }
        react.env$motif.features <- FALSE
        react.env$markers <- TRUE
      }
    }
  )
  observeEvent(
    eventExpr = react.env$markers,
    handlerExpr = {
      if (isTRUE(x = react.env$markers)) {
        allowed.clusters <- names(x = which(
          x = table(app.env$object[[paste0("predicted.", app.env$default.metadata)]]) > getOption(x = 'Azimuth.de.mincells')
        ))
        allowed.clusters <- factor(
          x = allowed.clusters,
          levels = unique(x = app.env$object[[paste0("predicted.", app.env$default.metadata), drop = TRUE]])
        )
        allowed.clusters <- sort(x = levels(x = droplevels(x = na.omit(
          object = allowed.clusters
        ))))
        # updateSelectizeInput(
        #   session = session,
        #   inputId = 'select.prediction',
        #   choices = allowed.clusters,
        #   selected = allowed.clusters[1],
        #   server = TRUE,
        #   options = selectize.opts
        # )
        
        updateSelectizeInput(
          session = session,
          inputId = 'markerclusters',
          choices = allowed.clusters,
          selected = allowed.clusters[1],
          server = TRUE,
          options = selectize.opts
        )
        
        updateSelectizeInput(
          session = session,
          inputId = 'markerclustersgroup',
          choices = app.env$metadataxfer[!app.env$singlepred],
          selected = app.env$default.metadata,
          server = TRUE,
          options = selectize.opts
        )
        
        updateSelectizeInput(
          session = session,
          inputId = 'markerclustersgroup.motif',
          choices = app.env$metadataxfer[!app.env$singlepred],
          selected = app.env$default.metadata,
          server = TRUE,
          options = selectize.opts
        )
        
        react.env$markers <- FALSE
        app.env$disable <- FALSE
        react.env$get.feature <- TRUE
        if (isTRUE(x = do.bridge)){
          react.env$get.motif.feature <- TRUE
        }
      }
    }
  )
  observeEvent(
    eventExpr = react.env$no,
    handlerExpr = {
      if (FALSE) {
        # Enable the feature explorer
        
        # Add the predicted ID and score to the plots
        
        
        
        # Enable downloads
        
        
        react.env$no <- FALSE
      }
    }
  )
  # Handle input changes
  observeEvent( # RNA feature
    eventExpr = input$feature,
    handlerExpr = {
      if (nchar(x = input$feature)) {
        if (nchar(x = input$markerclustersgroup)) {
          if (isTRUE(x = do.bridge)) {
            app.env$feature <- ifelse(
              test = input$feature %in% rownames(x = app.env$object[[app.env$gene.assay]]),
              yes = paste0(
                Key(object = app.env$object[[app.env$gene.assay]]),
                input$feature
              ),
              no = input$feature
            )
          } else {
            app.env$feature <- ifelse(
              test = input$feature %in% rownames(x = app.env$object[["refAssay"]]),
              yes = paste0(
                Key(object = app.env$object[["refAssay"]]),
                input$feature
              ),
              no = input$feature
            )
          }
          for (f in c('adtfeature', 'metadata.cont')) {
            updateSelectizeInput(
              session = session,
              inputId = f,
              choices = list(
                'adtfeature' = app.env$adt.features,
                'metadata.cont' = app.env$metadata.cont
              )[[f]],
              selected = '',
              server = TRUE,
              options = selectize.opts
            )
          }
          table.check <- input$feature %in% rownames(x = RenderDiffExp(
            diff.exp = app.env$diff.expr[[paste(app.env$gene.assay, input$markerclustersgroup, sep = "_")]],
            groups.use = input$markerclusters,
            n = Inf
          ))
          tables.clear <- list(adt.proxy, rna.proxy)[c(TRUE, !table.check)]
          for (tab in tables.clear) {
            selectRows(proxy = tab, selected = NULL)
          }
        }
      }
    }
  )
  
  observeEvent( # motif feature
    eventExpr = input$motif.feature,
    handlerExpr = {
      if (nchar(x = input$motif.feature)) {
        if (nchar(x = input$markerclustersgroup.motif)) {
          app.env$motif.feature <- ifelse(
            test = input$motif.feature %in% rownames(x = app.env$object),
            yes = paste0(
              Key(object = app.env$object[[app.env$default.assay]]),
              input$motif.feature
            ),
            no = input$motif.feature
          )
          table.check <- input$motif.feature %in% rownames(x = RenderDiffMotifExp(
            diff.exp = app.env$motif.diff.expr[[paste(app.env$default.assay, input$markerclustersgroup.motif, sep = "_")]],
            groups.use = input$markerclusters.motif,
            n = Inf
          ))
          tables.clear <- list(adt.proxy, motif.proxy)[c(TRUE, !table.check)]
          for (tab in tables.clear) {
            selectRows(proxy = tab, selected = NULL)
          }
        }
      }
    }
  )
  
  observeEvent( # Protein feature
    eventExpr = input$adtfeature,
    handlerExpr = {
      if (nchar(x = input$adtfeature)) {
        app.env$feature <- paste0(
          Key(object = app.env$object[[adt.key]]),
          input$adtfeature
        )
        for (f in c('feature', 'metadata.cont')) {
          updateSelectizeInput(
            session = session,
            inputId = f,
            choices = list(
              'feature' = app.env$features,
              'metadata.cont' = app.env$metadata.cont
            )[[f]],
            selected = '',
            server = TRUE,
            options = selectize.opts
          )
        }
        table.check <- input$adtfeature %in% rownames(x = RenderDiffExp(
          diff.exp = app.env$diff.expr[[paste(adt.key, input$markerclustersgroup, sep = "_")]],
          groups.use = input$markerclusters,
          n = Inf
        ))
        tables.clear <- list(rna.proxy, adt.proxy)[c(TRUE, !table.check)]
        for (tab in tables.clear) {
          selectRows(proxy = tab, selected = NULL)
        }
      }
    }
  )
  observeEvent( # Continuous Metadata
    eventExpr = input$metadata.cont,
    handlerExpr = {
      if (nchar(x = input$metadata.cont)) {
        if (input$metadata.cont == "mapping.score") {
          if (resolved(x = app.env$mapping.score)) {
            app.env$object$mapping.score <- value(app.env$mapping.score)
          }
        }
        app.env$feature <- input$metadata.cont
        for (f in c('feature', 'adtfeature')) {
          updateSelectizeInput(
            session = session,
            inputId = f,
            choices = list(
              'feature' = app.env$features,
              'adtfeature' = app.env$adt.features
            )[[f]],
            selected = '',
            server = TRUE,
            options = selectize.opts
          )
        }
        for (tab in list(rna.proxy, adt.proxy)) {
          selectRows(proxy = tab, selected = NULL)
        }
      }
    }
  )
  observeEvent( # Continuous Metadata
    eventExpr = input$metadata.cont.motif,
    handlerExpr = {
      if (nchar(x = input$metadata.cont.motif)) {
        if (input$metadata.cont.motif == "mapping.score") {
          if (resolved(x = app.env$mapping.score)) {
            app.env$object$mapping.score <- value(app.env$mapping.score)
          }
        }
        app.env$feature <- input$metadata.cont.motif
        updateSelectizeInput(
          session = session,
          inputId = "motif.feature",
          choices = app.env$motiffeatures,
          selected = '',
          server = TRUE,
          options = selectize.opts
        )
        for (tab in list(rna.proxy, adt.proxy)) {
          selectRows(proxy = tab, selected = NULL)
        }
      }
    }
  )
  observeEvent( # Marker clusters group
    eventExpr = input$markerclustersgroup,
    handlerExpr = {
      if (nchar(x = input$markerclustersgroup)) {
        allowed.clusters <- names(x = which(
          x = table(app.env$object[[paste0("predicted.", input$markerclustersgroup)]]) > getOption(x = 'Azimuth.de.mincells')
        ))
        allowed.clusters <- factor(
          x = allowed.clusters,
          levels = unique(x = app.env$object[[paste0("predicted.", input$markerclustersgroup), drop = TRUE]])
        )
        allowed.clusters <- sort(x = levels(x = droplevels(x = na.omit(
          object = allowed.clusters
        ))))
        app.env$allowedclusters <- allowed.clusters
        updateSelectizeInput(
          session = session,
          inputId = "markerclusters",
          choices = app.env$allowedclusters,
          selected = app.env$allowedclusters[1],
          server = TRUE,
          options = selectize.opts
        )
      }
    }
  )
  observeEvent( # Marker clusters group motif
    eventExpr = input$markerclustersgroup.motif,
    handlerExpr = {
      if (nchar(x = input$markerclustersgroup.motif)) {
        allowed.clusters <- names(x = which(
          x = table(app.env$object[[paste0("predicted.", input$markerclustersgroup.motif)]]) > getOption(x = 'Azimuth.de.mincells')
        ))
        allowed.clusters <- factor(
          x = allowed.clusters,
          levels = unique(x = app.env$object[[paste0("predicted.", input$markerclustersgroup.motif), drop = TRUE]])
        )
        allowed.clusters <- sort(x = levels(x = droplevels(x = na.omit(
          object = allowed.clusters
        ))))
        app.env$allowedclusters <- allowed.clusters
        updateSelectizeInput(
          session = session,
          inputId = "markerclusters.motif",
          choices = app.env$allowedclusters,
          selected = app.env$allowedclusters[1],
          server = TRUE,
          options = selectize.opts
        )
      }
    }
  )
  observeEvent( # Select from biomarkers table
    eventExpr = input$biomarkers_rows_selected,
    handlerExpr = {
      if (length(x = input$biomarkers_rows_selected)) {
        updateSelectizeInput(
          session = session,
          inputId = 'feature',
          choices = app.env$features,
          selected = rownames(x = RenderDiffExp(
            diff.exp = app.env$diff.expr[[paste(app.env$gene.assay, input$markerclustersgroup, sep = "_")]],
            groups.use = input$markerclusters,
            n = Inf
          ))[input$biomarkers_rows_selected],
          server = TRUE,
          options = selectize.opts
        )
      }
    }
  )
  observeEvent( # Select from adtbio table
    eventExpr = input$adtbio_rows_selected,
    handlerExpr = {
      if (length(x = input$adtbio_rows_selected)) {
        updateSelectizeInput(
          session = session,
          inputId = 'adtfeature',
          choices = app.env$adt.features,
          selected = rownames(x = RenderDiffExp(
            diff.exp = app.env$diff.expr[[paste(adt.key, input$markerclustersgroup, sep = "_")]],
            groups.use = input$markerclusters,
            n = Inf
          ))[input$adtbio_rows_selected],
          server = TRUE,
          options = selectize.opts
        )
      }
    }
  )
  observeEvent( # Select from motif table
    eventExpr = input$motif_rows_selected,
    handlerExpr = {
      if (length(x = input$motif_rows_selected)) {
        updateSelectizeInput(
          session = session,
          inputId = 'motif',
          choices = app.env$motif.features,
          selected = rownames(x = RenderDiffMotifExp(
            diff.exp = app.env$motif.diff.expr[[paste(app.env$default.assay, input$markerclustersgroup.motif, sep = "_")]],
            groups.use = input$markerclusters.motif,
            n = Inf
          ))[input$motif_rows_selected],
          server = TRUE,
          options = selectize.opts
        )
      }
    }
  )
  observeEvent( # Once reference metadata is initialized, set the default legend/labels based on ref only
    eventExpr = input$metacolor.ref,
    handlerExpr = {
      if (length(x = unique(x = as.vector(x = refs$plot[[input$metacolor.ref[1], drop = TRUE]]))) >= 30) {
        if (isTRUE(app.env$fresh.plot)) {
          updateCheckboxInput(
            session = session,
            inputId = 'labels',
            value = TRUE
          )
          updateCheckboxInput(
            session = session,
            inputId = 'legend',
            value = FALSE
          )
          app.env$fresh.plot <- FALSE
        }
      }
    }
  )
  observeEvent( # Record feedback and update UI if feedback submitted
    eventExpr = input$submit_feedback,
    handlerExpr = {
      if (!is.null(x = googlesheet)) {
        try(expr = sheet_append(
          ss = googlesheet,
          data = data.frame(
            "FEEDBACK",
            app_session_id,
            paste0('feedback: \"', input$feedback, '\"')
          )
        ))
      }
      updateTextAreaInput(
        session = session,
        inputId = 'feedback',
        label = NULL,
        value = 'Thank you for your feedback!')
    }
  )
  observeEvent( # Change metadata appropriately
    eventExpr = input$showrefonly,
    handlerExpr = {
      if (!is.null(app.env$metadata.discrete)) {
        if (input$showrefonly) {
          # change to appropriate input$metacolor.ref if its an option
          disable(id = 'metacolor.query')
          enable(id = 'metacolor.ref')
        } else {
          # change to appropriate input$metacolor.query
          disable(id = 'metacolor.ref')
          enable(id = 'metacolor.query')
        }
      }
    }
  )
  # Plots
  output$plot.qc <- renderPlot(expr = {
    if (!is.null(x = isolate(expr = app.env$object)) & isTRUE(x = react.env$plot.qc)) {
      # all(paste0(c('nCount_', 'nFeature_'), app.env$default.assay) %in% colnames(app.env$object@meta.data)) ) {
      qc <- paste0(c('nCount_', 'nFeature_'), app.env$default.assay)
      if (isTRUE(x = react.env$mt)) {
        qc <- c(qc, mt.key)
      }
      check.qcpoints <- "qcpoints" %in% input$check.qc
      check.qcscale <- "qcscale" %in% input$check.qc
      vlnlist <- VlnPlot(
        object = isolate(app.env$object),
        features = qc,
        group.by = 'query',
        combine = FALSE,
        pt.size = ifelse(
          test = check.qcpoints,
          yes = 0,
          no = Seurat:::AutoPointSize(data = isolate(app.env$object))
        ),
        log = check.qcscale
      )
      # nCount
      vlnlist[[1]] <- vlnlist[[1]] +
        geom_hline(yintercept = input$num.ncountmin) +
        geom_hline(yintercept = input$num.ncountmax) +
        annotate(
          geom = "rect",
          alpha = 0.2,
          fill = "red",
          ymin = input$num.ncountmax,
          ymax = Inf,
          xmin = 0.5,
          xmax = 1.5
        ) +
        annotate(
          geom = "rect",
          alpha = 0.2,
          fill = "red",
          ymin = ifelse(test = check.qcscale, yes = 0, no = -Inf),
          ymax = input$num.ncountmin,
          xmin = 0.5,
          xmax = 1.5
        ) +
        NoLegend() +
        xlab("")
      # nFeature
      vlnlist[[2]] <- vlnlist[[2]] +
        geom_hline(yintercept = input$num.nfeaturemin) +
        geom_hline(yintercept = input$num.nfeaturemax) +
        annotate(
          geom = "rect",
          alpha = 0.2,
          fill = "red",
          ymin = input$num.nfeaturemax,
          ymax = Inf,
          xmin = 0.5,
          xmax = 1.5
        ) +
        annotate(
          geom = "rect",
          alpha = 0.2,
          fill = "red",
          ymin = ifelse(test = check.qcscale, yes = 0, no = -Inf),
          ymax = input$num.nfeaturemin,
          xmin = 0.5,
          xmax = 1.5
        ) +
        NoLegend() +
        xlab("")
      if (react.env$mt) {
        vlnlist[[3]] <- vlnlist[[3]] +
          geom_hline(yintercept = input$minmt) +
          geom_hline(yintercept = input$maxmt) +
          annotate(
            geom = "rect",
            alpha = 0.2,
            fill = "red",
            ymin = input$maxmt,
            ymax = Inf,
            xmin = 0.5,
            xmax = 1.5
          ) +
          annotate(
            geom = "rect",
            alpha = 0.2,
            fill = "red",
            ymin = ifelse(test = check.qcscale, yes = 0, no = -Inf),
            ymax = input$minmt,
            xmin = 0.5,
            xmax = 1.5
          ) +
          NoLegend() +
          xlab("")
      }
      wrap_plots(vlnlist, ncol = length(x = vlnlist))
    }
  })
  output$overlap_box <- renderUI(
    box(
      title = p(
        'Overlap QC',
        bsButton(
          inputId = 'q4',
          label = '',
          icon = icon(name = 'question'),
          style = 'info',
          size = 'extra-small'
        )
      ),
      bsPopover(
        id = 'q4',
        title = 'Overlap QC',
        content = paste(
          'The distribution of overlap percentages for each peak. A strongly left-skewed ',
          'distribution means that most of the peaks have ~100% overlap to the corresponding multiome peak', 
          'and thus the requantified peaks will (maintain) the data from the original peaks. Also, note the ', 
          'total overlap percentage for a summary of this information.'
        ),
        placement = 'right',
        trigger = 'focus',
        options = list(container = 'body')
      ),
      plotOutput(outputId = 'dist.qc'),
      width = 4
    )
  )
  output$dist.qc <- renderPlot(expr = {
    if (!is.null(x = isolate(expr = app.env$chromatin_assay_1)) & isTRUE(x = react.env$dist.qc)) {
      dist <- OverlapDistPlot(query_assay = isolate(app.env$chromatin_assay_1),
                              multiome = refs$map[["ATAC"]])
    }
  })
  output$refdim_intro <- renderPlot(expr = {
    # save plot dataframe to minimize on-hover computation
    app.env$plots.refdim_intro_df <- cbind(
      as.data.frame(x = Embeddings(object = refs$plot[['refUMAP']])),
      refs$plot[[]]
    )
    p <- DimPlot(
      object = refs$plot,
      combine = FALSE,
      group.by = default_xfer,
      cols = GetColorMap(object = refs$map)[[default_xfer]],
      repel = TRUE,
      label = TRUE,
      raster = FALSE
    )[[1]]
    # for later use by query plot:
    app.env$plot.ranges <- list(
      layer_scales(p)$x$range$range,
      layer_scales(p)$y$range$range
    )
    # strip down the intro plot-- no title, legend, or axes
    p + WelcomePlot()
  })
  output$refdim_intro_hover_box <- renderUI({
    hover <- input$refdim_intro_hover_location
    df <- app.env$plots.refdim_intro_df
    if (!is.null(x = hover)){
      hover[['mapping']] <- setNames(object = as.list(x = colnames(x = app.env$plots.refdim_intro_df)[1:2]), nm = c('x', 'y'))
    }
    point <- nearPoints(
      df = df,
      coordinfo = hover,
      threshold = 10,
      maxpoints = 1,
      addDist = TRUE
    )
    if (nrow(x = point) == 0) {
      return(NULL)
    }
    hovertext <- do.call(
      what = paste0,
      args = lapply(X = metadata.annotate, FUN = function(md) {
        paste0("<span>", md, "</span>: <i>", point[[md]], "</i><br>")
      })
    )
    wellPanel(
      style = HoverBoxStyle(x = hover$coords_css$x, y = hover$coords_css$y),
      p(HTML(text = hovertext))
    )
  })
  if (isTRUE(x = do.bridge)){
    output$all_qc <- renderUI(
      fluidRow(
        uiOutput(outputId = "overlap_box"),
        column(8, 
               fluidRow(
                 valueBoxOutput(outputId = 'valuebox.upload', width = 3),
                 div(
                   id = 'overlap_popup',
                   valueBoxOutput(outputId = "valuebox_overlap", width = 3),
                   bsTooltip(id = "valuebox_overlap", title = "Click for more info", placement = "top", trigger = 'hover'),
                 ),
                 div(
                   id = 'jaccard_popup',
                   valueBoxOutput(outputId = "valuebox_jaccard", width = 3),
                   bsTooltip(id = "valuebox_jaccard", title = "Click for more info", placement = "top", trigger = 'hover'),
                 ),
               ),
               fluidRow(
                 valueBoxOutput(outputId = 'valuebox.preproc', width = 3),
                 div(
                   id = 'panchors_popup',
                   valueBoxOutput(outputId = "valuebox_panchors", width = 3),
                   bsTooltip(id = "valuebox_panchors", title = "Click for more info", placement = "top", trigger = 'hover'),
                 ),
                 div(
                   id = 'mappingqcstat_popup',
                   valueBoxOutput(outputId = "valuebox_mappingqcstat", width = 3),
                   bsTooltip(id = "valuebox_mappingqcstat", title = "Click for more info", placement = "top", trigger = 'hover'),
                 ),
                 valueBoxOutput(outputId = 'valuebox.mapped', width = 3),
               )
        )
      )
    )
  } else {
    output$all_qc <- renderUI(
        fluidRow(
        column(8, 
               fluidRow(
                 valueBoxOutput(outputId = 'valuebox.upload', width = 3),
                 valueBoxOutput(outputId = 'valuebox.preproc', width = 3),
                 div(
                   id = 'panchors_popup',
                   valueBoxOutput(outputId = "valuebox_panchors", width = 3),
                   bsTooltip(id = "valuebox_panchors", title = "Click for more info", placement = "top", trigger = 'hover'),
                 ),
                 div(
                   id = 'mappingqcstat_popup',
                   valueBoxOutput(outputId = "valuebox_mappingqcstat", width = 3),
                   bsTooltip(id = "valuebox_mappingqcstat", title = "Click for more info", placement = "top", trigger = 'hover'),
                 ),
                 valueBoxOutput(outputId = 'valuebox.mapped', width = 3),
               )
        )
      )
    )
    
  }
  output$refdim <- renderPlot(expr = {
    if (!is.null(x = input$metacolor.ref)) {
      colormaps <- GetColorMap(object = refs$map)[input$metacolor.ref]
      # no interactivity if multiple plots per row (less useful in this case)
      if (length(x = colormaps) == 1) {
        ## already stored reference dataframe in app.env
        app.env$plots.refdim_df <- app.env$plots.refdim_intro_df
        DimPlot(
          object = refs$plot,
          label = isTRUE("labels" %in% input$dimplot.opts),
          group.by = input$metacolor.ref,
          cols = colormaps[[1]],
          repel = TRUE,
          raster = FALSE
        )[[1]] +
          labs(x = "UMAP 1", y = "UMAP 2") +
          if (isFALSE(x = "legend" %in% input$dimplot.opts) | OversizedLegend(refs$plot[[input$metacolor.ref, drop = TRUE]])) NoLegend()
      } else {
        app.env$plots.refdim_df <- NULL
        plots <- list()
        for (i in 1:length(x = colormaps)) {
          plots[[i]] <- DimPlot(
            object = refs$plot,
            label = isTRUE("labels" %in% input$dimplot.opts),
            group.by = input$metacolor.ref[i],
            cols = colormaps[[i]],
            repel = TRUE,
            raster = FALSE
          ) + labs(x = "UMAP 1", y = "UMAP 2") +
            if (isFALSE(x = "legend" %in% input$dimplot.opts) | OversizedLegend(refs$plot[[input$metacolor.ref[i], drop = TRUE]])) NoLegend()
        }
        wrap_plots(plots, nrow = 1)
      }
    }
  })
  
  output$refdim_hover_box <- renderUI({
    if (!is.null(x = app.env$plots.refdim_df)) {
      hover <- input$refdim_hover_location
      df <- app.env$plots.refdim_df
      if (!is.null(x = hover)){
        hover[['mapping']] <- setNames(object = as.list(x = colnames(x = app.env$plots.refdim_intro_df)[1:2]), nm = c('x', 'y'))
      }
      point <- nearPoints(
        df = df,
        coordinfo = hover,
        threshold = 10,
        maxpoints = 1,
        addDist = TRUE
      )
      if (nrow(x = point) == 0) {
        return(NULL)
      }
      hovertext <- do.call(
        what = paste0,
        args = as.list(c(
          paste0("<b>", point[[input$metacolor.ref]], "</b><br>"),
          sapply(X = setdiff(possible.metadata.transfer, input$metacolor.ref), FUN = function(md) {
            paste0("<span>", md, "</span>: <i>", point[[md]], "</i><br>")
          })
        ))
      )
      wellPanel(
        style = HoverBoxStyle(x = hover$coords_css$x, y = hover$coords_css$y),
        p(HTML(text = hovertext))
      )
    }
  })
  
  output$objdim <- renderPlot(expr = {
    if (!is.null(x = app.env$object) && app.env$disable == FALSE) {
      # create empty ref
      if (is.null(x = app.env$emptyref) | is.null(x = app.env$merged)) {
        app.env$emptyref <- refs$plot
        Idents(object = app.env$emptyref) <- '.'
        for (md in colnames(x = app.env$emptyref[[]])) {
          app.env$emptyref[[md]] <- '.'
        }
        for (md in setdiff(
          x = colnames(x = app.env$object[[]]),
          y = colnames(x = app.env$emptyref[[]])
        )) {
          app.env$emptyref[[md]] <- '.'
        }
        app.env$object[['refUMAP']] <- app.env$object[['umap.proj']]
        app.env$merged <- merge(app.env$emptyref, app.env$object, merge.dr = 'refUMAP')
      }
      
      if (isFALSE(x = input$showrefonly) &
          length(x = Reductions(object = app.env$object)) &
          !is.null(x = input$metacolor.query)) { # SHOW OVERLAY
        app.env$plots.refdim_df <- NULL # hide reference hover box
        if (length(x = input$metacolor.query) == 1) {
          # get colormap if avail
          group.var <- gsub(pattern = "^predicted.", replacement = "", x = input$metacolor.query)
          colormap <- GetColorMap(object = refs$map)[[group.var]]
          if (!grepl(pattern = "^predicted.", x = input$metacolor.query)) {
            colormap <- CreateColorMap(ids=unique(as.vector(app.env$object[[input$metacolor.query,drop=T]])))
          }
          colormap['.'] <- '#F1F1F1'
          
          # make dataframe so don't need to recompute during hover- QUERY only!
          app.env$plots.objdim_df <- cbind(
            as.data.frame(x = Embeddings(object = app.env$object[['umap.proj']])),
            app.env$object[[]]
          )
          p <- DimPlot(
            object = app.env$merged,
            group.by = input$metacolor.query,
            label = FALSE,
            cols = colormap[names(x = colormap) %in% c(
              '.', unique(x = as.vector(x = app.env$object[[input$metacolor.query, drop = TRUE]])))],
            repel = TRUE,
            raster = FALSE,
            reduction = "refUMAP"
          )[[1]] +
            xlim(app.env$plot.ranges[[1]]) +
            ylim(app.env$plot.ranges[[2]]) +
            labs(x = "UMAP 1", y = "UMAP 2") +
            if (isFALSE(x = input$legend)) NoLegend()
          if (isTRUE(x = 'labels' %in% input$label.opts)) {
            keep <- if (isTRUE(x = 'filterlabels' %in% input$label.opts)) {
              t <- table(as.vector(x = app.env$object[[input$metacolor.query, drop = TRUE]]))
              names(x = t)[which(x = t > 0.02 * ncol(x = app.env$object))]
            } else NULL
            return(LabelClusters(
              plot = p,
              id = input$metacolor.query,
              clusters = keep
            ))
          }
          return(p)
        } else {
          app.env$plots.objdim_df <- NULL # no interactivity
          plots <- list()
          for (i in 1:length(x = input$metacolor.query)) {
            group.var <- gsub(pattern = "^predicted.", replacement = "", x = input$metacolor.query[i])
            colormap <- GetColorMap(object = refs$map)[[group.var]]
            if (!grepl(pattern = "^predicted.", x = input$metacolor.query[i])) {
              colormap <- CreateColorMap(
                ids = unique(x = as.vector(x = app.env$object[[input$metacolor.query[i], drop = TRUE]]))
              )
            }
            colormap['.'] <- '#F1F1F1'
            p <- DimPlot(
              object = app.env$merged,
              group.by = input$metacolor.query[i],
              cols = colormap[names(x = colormap) %in% c(
                '.', unique(x = as.vector(x = app.env$object[[input$metacolor.query[i], drop = TRUE]])))],
              repel = TRUE,
              raster = FALSE,
              reduction = "refUMAP"
            )[[1]] + xlim(app.env$plot.ranges[[1]]) +
              ylim(app.env$plot.ranges[[2]]) +
              labs(x = "UMAP 1", y = "UMAP 2") +
              if (isFALSE(x = input$legend) | OversizedLegend(annotation.list = app.env$object[[input$metacolor.query[i], drop = TRUE]])) NoLegend()
            if (isTRUE('labels' %in% input$label.opts)) {
              keep <- if (isTRUE(x = 'filterlabels' %in% input$label.opts)) {
                t <- table(as.vector(x = app.env$object[[input$metacolor.query[i], drop = TRUE]]))
                print(names(x = t)[which(t > 0.02 * ncol(x = app.env$object))])
                names(x = t)[which(t > 0.02 * ncol(x = app.env$object))]
              } else NULL
              plots[[i]] <- LabelClusters(
                plot = p,
                id = input$metacolor.query[i],
                clusters = keep
              )
            } else {
              plots[[i]]<-p
            }
          }
          wrap_plots(plots, nrow = 1)
        }
      } else { # SHOW REFERENCE ONLY
        app.env$plots.objdim_df <- NULL # hide query hover box
        if (!is.null(x = input$metacolor.ref)) {
          colormaps <- GetColorMap(object = refs$map)[input$metacolor.ref]
          if (length(x = colormaps) == 1) {
            app.env$plots.refdim_df <- app.env$plots.refdim_intro_df
            DimPlot(
              object = refs$plot,
              label = isTRUE('labels' %in% input$label.opts),
              group.by = input$metacolor.ref,
              cols = colormaps[[1]],
              repel = TRUE,
              raster = FALSE
            )[[1]] +
              labs(x = "UMAP 1", y = "UMAP 2") +
              if (isFALSE(input$legend) | OversizedLegend(annotation.list = refs$plot[[input$metacolor.ref, drop = TRUE]])) NoLegend()
          } else {
            app.env$plots.refdim_df <- NULL
            plots <- list()
            for (i in 1:length(x = colormaps)) {
              plots[[i]] <- DimPlot(
                object = refs$plot,
                label = isTRUE('labels' %in% input$label.opts),
                group.by = input$metacolor.ref[i],
                cols = colormaps[[i]],
                repel = TRUE,
                raster = FALSE
              ) + labs(x = "UMAP 1", y = "UMAP 2") +
                if (isFALSE(x = input$legend) | OversizedLegend(annotation.list = refs$plot[[input$metacolor.ref[i], drop = TRUE]])) NoLegend()
            }
            wrap_plots(plots, nrow = 1)
          }
        }
      }
    }
  })
  output$querydim <- renderPlot(expr = {
    if (!is.null(x = app.env$object)) {
      if (length(x = Reductions(object = app.env$object)) & !is.null(x = input$metacolor.query)) {
        if (length(x = input$metacolor.query) == 1) {
          # get colormap if avail
          group.var <- gsub(pattern = "^predicted.", replacement = "", x = input$metacolor.query)
          colormap <- GetColorMap(object = refs$map)[[group.var]]
          if (!grepl(pattern = "^predicted.", x = input$metacolor.query)) {
            colormap <- NULL
          }
          # make dataframe so don't need to recompute during hover
          app.env$plots.querydim_df <- cbind(
            as.data.frame(x = Embeddings(object = app.env$object[['umap.proj']])),
            app.env$object[[]]
          )
          DimPlot(
            object = app.env$object,
            group.by = input$metacolor.query,
            label = isTRUE('labels' %in% input$dimplot.opts),
            cols = colormap[names(x = colormap) %in% unique(x = app.env$object[[input$metacolor.query, drop = TRUE]])],
            repel = TRUE,
            reduction = "umap.proj"
          )[[1]] +
            xlim(app.env$plot.ranges[[1]]) +
            ylim(app.env$plot.ranges[[2]]) +
            labs(x = "UMAP 1", y = "UMAP 2") +
            if (isFALSE(x = "legend" %in% input$dimplot.opts) | OversizedLegend(app.env$object[[input$metacolor.query, drop = TRUE]])) NoLegend()
        } else {
          app.env$plots.querydim_df <- NULL
          plots <- list()
          for (i in 1:length(x = input$metacolor.query)) {
            group.var <- gsub(pattern = "^predicted.", replacement = "", x = input$metacolor.query[i])
            colormap <- GetColorMap(object = refs$map)[[group.var]]
            if (!grepl(pattern = "^predicted.", x = input$metacolor.query[i])) {
              colormap <- NULL
            }
            plots[[i]] <- DimPlot(
              object = app.env$object,
              group.by = input$metacolor.query[i],
              label = isTRUE('labels' %in% input$dimplot.opts),
              cols = colormap[names(x = colormap) %in% unique(x = app.env$object[[input$metacolor.query[i], drop = TRUE]])],
              repel = TRUE,
              reduction = "umap.proj"
            ) + xlim(app.env$plot.ranges[[1]]) +
              ylim(app.env$plot.ranges[[2]]) +
              labs(x = "UMAP 1", y = "UMAP 2") +
              if (isFALSE(x = "legend" %in% input$dimplot.opts) | OversizedLegend(app.env$object[[input$metacolor.query[i], drop = TRUE]])) NoLegend()
          }
          wrap_plots(plots, nrow = 1)
        }
      }
    }
  })
  
  output$objdim_hover_box <- renderUI({
    if (!is.null(x = app.env$plots.objdim_df)) {
      hover <- input$objdim_hover_location
      df <- app.env$plots.objdim_df
      if (!is.null(x = hover)){
        hover[['mapping']] <- setNames(object = as.list(x = colnames(x = app.env$plots.objdim_df)[1:2]), nm = c('x', 'y'))
      }
      point <- nearPoints(
        df = df,
        coordinfo = hover,
        threshold = 10,
        maxpoints = 1,
        addDist = TRUE
      )
      if (nrow(x = point) == 0) {
        return(NULL)
      }
      hovertext <- do.call(
        what = paste0,
        args = as.list(c(
          paste0("<b>", point[[input$metacolor.query]], "</b><br>"),
          if (grepl(pattern = "^predicted.", x = input$metacolor.query)) {
            paste0(
              "<i>prediction score</i>: <span>",
              format(
                x = round(x = point[[paste0(input$metacolor.query,'.score')]], digits = 2),
                nsmall = 2
              ),
              "</span><br>"
            )
          }
        ))
      )
      wellPanel(
        style = HoverBoxStyle(x = hover$coords_css$x, y = hover$coords_css$y),
        p(HTML(text = hovertext))
      )
    } else if (!is.null(x = app.env$plots.refdim_df)) {
      hover <- input$objdim_hover_location
      df <- app.env$plots.refdim_df
      if (!is.null(x = hover)){
        hover[['mapping']] <- setNames(object = as.list(x = colnames(x = app.env$plots.refdim_intro_df)[1:2]), nm = c('x', 'y'))
      }
      point <- nearPoints(
        df = df,
        coordinfo = hover,
        threshold = 10,
        maxpoints = 1,
        addDist = TRUE
      )
      if (nrow(x = point) == 0) {
        return(NULL)
      }
      hovertext <- do.call(
        what = paste0,
        args = as.list(c(
          paste0("<b>", point[[input$metacolor.ref]], "</b><br>"),
          sapply(X = setdiff(metadata.annotate, input$metacolor.ref), FUN = function(md) {
            paste0("<span>", md, "</span>: <i>", point[[md]], "</i><br>")
          })
        ))
      )
      wellPanel(
        style = HoverBoxStyle(x = hover$coords_css$x, y = hover$coords_css$y),
        p(HTML(text = hovertext))
      )
    }
  })
  output$querydim_hover_box <- renderUI({
    if (!is.null(x = app.env$plots.querydim_df)) {
      hover <- input$querydim_hover_location
      df <- app.env$plots.querydim_df
      if (!is.null(x = hover)){
        hover[['mapping']] <- setNames(object = as.list(x = colnames(x = app.env$plots.querydim_df)[1:2]), nm = c('x', 'y'))
      }
      point <- nearPoints(
        df = df,
        coordinfo = hover,
        threshold = 10,
        maxpoints = 1,
        addDist = TRUE
      )
      if (nrow(x = point) == 0) {
        return(NULL)
      }
      hovertext <- do.call(
        what = paste0,
        args = as.list(c(
          paste0("<b>", point[[input$metacolor.query]], "</b><br>"),
          if (grepl(pattern = "^predicted.", x = input$metacolor.query)) {
            paste0(
              "<i>prediction score</i>: <span>",
              format(
                x = round(x = point[[paste0(input$metacolor.query,'.score')]], digits = 2),
                nsmall = 2
              ),
              "</span><br>"
            )
          }
        ))
      )
      wellPanel(
        style = HoverBoxStyle(x = hover$coords_css$x, y = hover$coords_css$y),
        p(HTML(text = hovertext))
      )
    }
  })
  output$evln <- renderPlot(expr = {
    if (!is.null(x = app.env$object)) {
      if (isTRUE(x = do.bridge)){
        avail <- c(
          paste0(
            Key(object = app.env$object[[app.env$gene.assay]]),
            rownames(x = app.env$object[[app.env$gene.assay]])
          ),
          colnames(x = app.env$object[[]])
        )
      } else {
        DefaultAssay(app.env$object) <- "refAssay"
        avail <- c(
          paste0(
            Key(object = app.env$object[["refAssay"]]),
            rownames(x = app.env$object)
          ),
          colnames(x = app.env$object[[]])
        )
      }
      # prediction assays
      prediction.names <- unlist(x = lapply(
        X = app.env$metadataxfer,
        FUN = function(x) {
          assay <- paste0("prediction.score.", x)
          pred <- rep(x = x, times = nrow(x = app.env$object[[assay]]))
          names(x = pred) <- paste0(
            Key(object = app.env$object[[assay]]),
            rownames(x = app.env$object[[assay]])
          )
          return(pred)
        })
      )
      max.pred.names <- paste0("predicted.", app.env$metadataxfer, ".score")
      avail <- c(avail, names(x = prediction.names))
      if (do.adt) {
        avail <- c(
          avail,
          paste0(
            Key(object = app.env$object[[adt.key]]),
            rownames(x = app.env$object[[adt.key]])
          )
        )
      }
      
      if (app.env$feature %in% avail) {
        if (app.env$feature == "mapping.score" && !resolved(x = app.env$mapping.score)) {
          ggplot() +
            annotate("text", x = 4, y = 25, size=8, label = "Mapping score still computing ... ") +
            theme_void()
        } else {
          title <- if (isTRUE(x = do.bridge)){
            ifelse(
              test = grepl(pattern = '^rna_', x = app.env$feature),
              yes = gsub(pattern = '^rna_', replacement = '', x = app.env$feature),
              no = app.env$feature
            )
          } else {
            ifelse(
            test = grepl(pattern = '^refassay_', x = app.env$feature),
            yes = gsub(pattern = '^refassay_', replacement = '', x = app.env$feature),
            no = app.env$feature
          )
          }
          if (app.env$feature %in% names(x = prediction.names)) {
            pred <- strsplit(x = app.env$feature, split = "_")[[1]][2]
            group <- prediction.names[app.env$feature]
            title <- paste0("Prediction Score (", group, ") ", pred)
          }
          if (app.env$feature %in% max.pred.names) {
            pred <- gsub(pattern = "predicted.", replacement = "", x = app.env$feature)
            pred <- gsub(pattern = ".score", replacement = "", x = pred)
            title <- paste0("Max Prediction Score - ", pred)
          }
          VlnPlot(
            object = app.env$object,
            features = app.env$feature,
            group.by = input$metagroup,
            pt.size = ifelse(
              test = input$check.featpoints,
              yes = 0,
              no = Seurat:::AutoPointSize(data = app.env$object)
            )
          ) +
            ggtitle(label = title) +
            NoLegend()
        }
      }
    }
  })
  output$edim <- renderPlot(expr = {
    if (!is.null(x = app.env$object)) {
      palettes <- list(
        c("lightgrey", "blue"),
        c('lightgrey', 'darkred')
      )
      if (isTRUE(x = do.bridge)){
        names(x = palettes) <- c(
          Key(object = app.env$object[[app.env$gene.assay]]),
          'md_'
        )
      } else{
        DefaultAssay(app.env$object) <- "refAssay"
        names(x = palettes) <- c(
          Key(object = app.env$object[["refAssay"]]),
          'md_'
        )
      }
      if (do.adt) {
        palettes[[Key(object = app.env$object[[adt.key]])]] <-  c('lightgrey', 'darkgreen')
      }
      # prediction assays
      prediction.names <- unlist(x = lapply(
        X = app.env$metadataxfer,
        FUN = function(x) {
          assay <- paste0("prediction.score.", x)
          pred <- rep(x = x, times = nrow(x = app.env$object[[assay]]))
          names(x = pred) <- paste0(
            Key(object = app.env$object[[assay]]),
            rownames(x = app.env$object[[assay]])
          )
          return(pred)
        })
      )
      max.pred.names <- paste0("predicted.", app.env$metadataxfer, ".score")
      md <- c(colnames(x = app.env$object[[]]), names(x = prediction.names))
      feature.key <- if (app.env$feature %in% md) {
        'md_'
      } else {
        paste0(
          unlist(x = strsplit(x = app.env$feature, split = '_'))[1],
          '_'
        )
      }
      pal.use <- palettes[[feature.key]]
      if (!is.null(x = pal.use)) {
        if (app.env$feature == "mapping.score" && !resolved(x = app.env$mapping.score)) {
          ggplot() +
            annotate("text", x = 4, y = 25, size=8, label = "Mapping score still computing ... ") +
            theme_void()
        } else {
          title <- if (isTRUE(x = do.bridge)){
            ifelse(
              test = grepl(pattern = '^rna_', x = app.env$feature),
              yes = gsub(pattern = '^rna_', replacement = '', x = app.env$feature),
              no = app.env$feature
            )
          } else {
            ifelse(
              test = grepl(pattern = '^refassay_', x = app.env$feature),
              yes = gsub(pattern = '^refassay_', replacement = '', x = app.env$feature),
              no = app.env$feature
            )
          }
          if (app.env$feature %in% names(x = prediction.names)) {
            pred <- strsplit(x = app.env$feature, split = "_")[[1]][2]
            group <- prediction.names[app.env$feature]
            title <- paste0("Prediction Score (", group, ") ", pred)
          }
          if (app.env$feature %in% max.pred.names) {
            pred <- gsub(pattern = "predicted.", replacement = "", x = app.env$feature)
            pred <- gsub(pattern = ".score", replacement = "", x = pred)
            title <- paste0("Max Prediction Score - ", pred)
          }
          suppressWarnings(expr = FeaturePlot(
            object = app.env$object,
            features = app.env$feature,
            cols = pal.use,
            reduction = "umap.proj"
          )) + xlim(app.env$plot.ranges[[1]]) +
            ylim(app.env$plot.ranges[[2]]) +
            ggtitle(label = title)
        }
      }
    }
  })
  
  
  output$motifdim <- renderPlot(expr = {
    if (!is.null(x = app.env$object)) {
      palettes <- list(c("lightgrey", "blue"), c("lightgrey", 
                                                 "darkred"))
      names(x = palettes) <- c(Key(object = app.env$object[[app.env$default.assay]]), 
                               "md_")
      prediction.names <- unlist(x = lapply(X = app.env$metadataxfer, 
                                            FUN = function(x) {
                                              assay <- paste0("prediction.score.", x)
                                              pred <- rep(x = x, times = nrow(x = app.env$object[[assay]]))
                                              names(x = pred) <- paste0(Key(object = app.env$object[[assay]]), 
                                                                        rownames(x = app.env$object[[assay]]))
                                              return(pred)
                                            }))
      max.pred.names <- paste0("predicted.", app.env$metadataxfer, 
                               ".score")
      md <- c(colnames(x = app.env$object[[]]), names(x = prediction.names))
      feature.key <- if (app.env$motif.feature %in% md) {
        "md_"
      }
      else {
        paste0(unlist(x = strsplit(x = app.env$motif.feature, 
                                   split = "_"))[1], "_")
      }

      pal.use <- palettes[[feature.key]]
      if (!is.null(x = pal.use)) {
        if (app.env$motif.feature == "mapping.score" && !resolved(x = app.env$mapping.score)) {
          ggplot() + annotate("text", x = 4, y = 25, 
                              size = 8, label = "Mapping score still computing ... ") + 
            theme_void()
        }
        else {
          title <- ifelse(test = grepl(pattern = "^motif_", 
                                       x = app.env$feature), yes = gsub(pattern = "^motif_", 
                                                                        replacement = "", x = app.env$motif.feature), no = app.env$motif.feature)
          if (app.env$motif.feature %in% names(x = prediction.names)) {
            pred <- strsplit(x = app.env$motif.feature, split = "_")[[1]][2]
            group <- prediction.names[app.env$motif.feature]
            title <- paste0("Prediction Score (", group, 
                            ") ", pred)
          }
          if (app.env$motif.feature %in% max.pred.names) {
            pred <- gsub(pattern = "predicted.", replacement = "", 
                         x = app.env$motif.feature)
            pred <- gsub(pattern = ".score", replacement = "", 
                         x = pred)
            title <- paste0("Max Prediction Score - ", 
                            pred)
          }
          suppressWarnings(expr = FeaturePlot(object = app.env$object, 
                                              features = app.env$motif.feature, cols = pal.use, 
                                              min.cutoff = 'q10', max.cutoff = 'q90',  
                                              reduction = "umap.proj")) + xlim(app.env$plot.ranges[[1]]) + 
            ylim(app.env$plot.ranges[[2]]) + ggtitle(label = title)
        }
      }
    }
  })
  # Messages
  output$message <- renderUI(expr = {
    p(HTML(text = paste(app.env$messages, collapse = "<br />")))
  })
  output$containerid <- renderUI(expr = {
    p(HTML(text = paste(
      paste("debug ID:", Sys.info()[["nodename"]]),
      paste('Azimuth version:', packageVersion(pkg = 'Azimuth')),
      paste('Seurat version:', packageVersion(pkg = 'Seurat')),
      paste('Reference version:', ReferenceVersion(object = refs$map)),
      sep = "<br />"
    )))
  })
  output$text.cellsremain <- renderText(expr = {
    if (!is.null(x = isolate(app.env$object))) {
      ncount <- paste0('nCount_', DefaultAssay(object = isolate(app.env$object)))
      nfeature <- paste0('nFeature_', DefaultAssay(object = isolate(app.env$object)))
      cells.use <- isolate(app.env$object)[[ncount, drop = TRUE]] >= input$num.ncountmin &
        isolate(app.env$object)[[ncount, drop = TRUE]] <= input$num.ncountmax &
        isolate(app.env$object)[[nfeature, drop = TRUE]] >= input$num.nfeaturemin &
        isolate(app.env$object)[[nfeature, drop = TRUE]] <= input$num.nfeaturemax
      if (any(grepl(pattern = mito.pattern, x = rownames(x = isolate(app.env$object))))) {
        cells.use <- cells.use &
          isolate(app.env$object)[[mt.key, drop = TRUE]] >= input$minmt &
          isolate(app.env$object)[[mt.key, drop = TRUE]] <= input$maxmt
      }
      paste(sum(cells.use), "cells remain after current filters")
    }
  })
  output$text.dladt <- renderText(
    expr = {
      c(
        "imputed.assay <- readRDS('azimuth_impADT.Rds')",
        "object <- object[, Cells(imputed.assay)]",
        "object[['impADT']] <- imputed.assay"
      )
    },
    sep = "\n"
  )
  output$text.dlumap <- renderText(
    expr = {
      c(
        "projected.umap <- readRDS('azimuth_umap.Rds')",
        "object <- object[, Cells(projected.umap)]",
        "object[['umap.proj']] <- projected.umap"
      )
    },
    sep = "\n"
  )
  output$text.dlpred <- renderText(
    expr = {
      c(
        "predictions <- read.delim('azimuth_pred.tsv', row.names = 1)",
        "object <- AddMetaData(",
        "\tobject = object,",
        "\tmetadata = predictions)"
      )
    },
    sep = "\n"
  )
  output$text.dlall <- renderText(
    expr = {
      c(
        "object <- AddAzimuthResults(object, filename = 'azimuth_results.Rds')"
      )
    },
    sep = "\n"
  )
  # Tables
  output$table.qc <- renderTable(
    expr = {
      if (!is.null(x = isolate(app.env$object))) {
        qc <- paste0(c('nCount_', 'nFeature_'), app.env$default.assay)
        tbl <- apply(X = isolate(app.env$object)[[qc]], MARGIN = 2, FUN = quantile)
        tbl <- as.data.frame(x = tbl)
        if (isTRUE(x = do.bridge)){
          colnames(x = tbl) <- c('Fragments per cell', 'Peaks detected per cell')
        } else{
          colnames(x = tbl) <- c('nUMI per cell', 'Genes detected per cell')
        }
        if (mt.key %in% colnames(x = isolate(app.env$object)[[]])) {
          tbl[, 3] <- quantile(x = isolate(app.env$object)[[mt.key, drop = TRUE]])
          colnames(x = tbl)[3] <- 'Mitochondrial percentage per cell'
        }
        t(x = tbl)
      }
    },
    rownames = TRUE
  )
  output$biomarkers <- renderDT(
    expr = {
      if (!is.null(x = app.env$diff.expr[[paste(app.env$gene.assay, input$markerclustersgroup, sep ="_")]])) {
        RenderDiffExp(
          diff.exp =  app.env$diff.expr[[paste(app.env$gene.assay, input$markerclustersgroup, sep ="_")]],
          groups.use = input$markerclusters,
          n = Inf
        )
      }
    },
    selection = 'single',
    options = list(dom = 't')
  )
  output$adtbio <- renderDT(
    expr = {
      if (!is.null(x = app.env$diff.expr[[paste(adt.key, input$markerclustersgroup, sep = "_")]])) {
        RenderDiffExp(
          diff.exp = app.env$diff.expr[[paste(adt.key, input$markerclustersgroup, sep = "_")]],
          groups.use = input$markerclusters,
          n = Inf
        )
      }
    },
    selection = 'single',
    options = list(dom = 't')
  )
  output$motifs <- renderDT(
    expr = {
      if (!is.null(x = app.env$motif.diff.expr[[paste(app.env$default.assay, input$markerclustersgroup.motif, sep ="_")]])) {
        RenderDiffMotifExp(
          diff.exp =  app.env$motif.diff.expr[[paste(app.env$default.assay, input$markerclustersgroup.motif, sep ="_")]],
          groups.use = input$markerclusters.motif,
          n = Inf
        )
      }
    },
    selection = 'single',
    options = list(dom = 't')
  )
  output$metadata.table <- renderTable(
    expr = {
      if (!is.null(x = app.env$object)) {
        CategoryTable(
          object = app.env$object,
          category.1 = input$metarow,
          category.2 = input$metacol,
          percentage = (input$radio.pct == "Percentage")
        )
      }
    },
    rownames = TRUE
  )
  output$metadata.heatmap <- renderPlotly({
    table <- CategoryTable(
      object = app.env$object,
      category.1 = input$metarow,
      category.2 = input$metacol,
      percentage = (input$radio.pct == "Percentage")
    )
    table <- as.matrix(table)
    plot_ly(x = colnames(table), y = rownames(table), z = table, type = 'heatmap',
            height='1000px')
  })
  # Downloads
  output$dlumap <- downloadHandler(
    filename = paste0(tolower(x = app.title), '_umap.Rds'),
    content = function(file) {
      if (!is.null(x = app.env$object)) {
        if ('umap.proj' %in% Reductions(object = app.env$object)) {
          saveRDS(object = app.env$object[['umap.proj']], file = file)
        }
      }
    }
  )
  output$dladt <- downloadHandler(
    filename = paste0(tolower(x = app.title), '_impADT.Rds'),
    content = function(file) {
      if (!is.null(x = app.env$object)) {
        if ('impADT' %in% Assays(object = app.env$object)) {
          saveRDS(object = app.env$object[['impADT']], file = file)
        }
      }
    }
  )
  output$dlpred <- downloadHandler(
    filename = paste0(tolower(x = app.title), '_pred.tsv'),
    content = function(file) {
      req <- paste0("predicted.", c(app.env$metadataxfer, paste0(app.env$metadataxfer, ".score")))
      if (resolved(x = app.env$mapping.score)) {
        req <- c(req, 'mapping.score')
      }
      if (all(req %in% colnames(x = app.env$object[[]]))) {
        pred.df <- app.env$object[[req]]
        if (resolved(x = app.env$mapping.score)) {
          pred.df$mapping.score <- value(app.env$mapping.score)
        }
        pred.df <- cbind(cell = rownames(x = pred.df), pred.df)
        write.table(
          x = pred.df,
          file = file,
          quote = FALSE,
          row.names = FALSE,
          col.names = TRUE,
          sep = '\t'
        )
      }
    }
  )
  output$dlall <- downloadHandler(
    filename = paste0(tolower(x = app.title), '_results.Rds'),
    content = function(file) {
      results <- list()
      if (!is.null(x = app.env$object)) {
        if ('impADT' %in% Assays(object = app.env$object)) {
          results$impADT <- app.env$object[['impADT']]
        }
        if ('umap.proj' %in% Reductions(object = app.env$object)) {
          results$umap <- app.env$object[['umap.proj']]
        }
      }
      
      req <- paste0("predicted.", c(app.env$metadataxfer, paste0(app.env$metadataxfer, ".score")))
      if (resolved(x = app.env$mapping.score)) {
        req <- c(req, 'mapping.score')
      }
      if (all(req %in% colnames(x = app.env$object[[]]))) {
        pred.df <- app.env$object[[req]]
        if (resolved(x = app.env$mapping.score)) {
          pred.df$mapping.score <- value(app.env$mapping.score)
        }
        pred.df <- cbind(cell = rownames(x = pred.df), pred.df)
        results$pred.df <- pred.df
      }
      
      saveRDS(results, file = file)
    }
  )
  output$dlscript <- downloadHandler(
    filename = paste0(tolower(x = app.title), '_analysis.R'),
    content = function(file) {
      template <- readLines(con = system.file(
        file.path('resources', 'template.R'),
        package = 'Azimuth'
      ))
      template <- paste(template, collapse = '\n')
      e <- new.env()
      # e$ref.uri <- 'https://seurat.nygenome.org/references/pbmc/'
      e$ref.uri <- getOption(
        x = 'Azimuth.app.refuri',
        default = getOption(x = 'Azimuth.app.reference')
      )
      e$path <- input$file$name
      e$mito.pattern <- getOption(x = 'Azimuth.app.mito', default = '^MT-')
      e$mito.key <- mt.key
      e$ncount.max <- input$num.ncountmax
      e$ncount.min <- input$num.ncountmin
      e$nfeature.max <- input$num.nfeaturemax
      e$nfeature.min <- input$num.nfeaturemin
      e$mito.max <- input$maxmt
      e$mito.min <- input$minmt
      e$sct.ncells <- getOption(x = 'Azimuth.sct.ncells')
      e$sct.nfeats <- getOption(x = 'Azimuth.sct.nfeats')
      e$ntrees <- getOption(x = 'Azimuth.map.ntrees')
      e$ndims <- getOption(x = "Azimuth.map.ndims")
      e$adt.key <- adt.key
      e$do.adt <- do.adt
      e$metadataxfer <- app.env$metadataxfer
      if (length(x = e$metadataxfer == 1)) {
        e$metadataxfer <- paste0("\"", e$metadataxfer, "\"")
      }
      e$plotgene <- getOption(x = 'Azimuth.app.default_gene')
      e$plotadt <- getOption(x = 'Azimuth.app.default_adt')
      writeLines(text = str_interp(string = template, env = e), con = file)
    }
  )
  # render UI elements that depend on arguments
  output$refdescriptor <- renderText(
    expr = eval(expr = HTML(getOption(x = "Azimuth.app.refdescriptor")))
  )
  output$welcomebox <- renderUI(
    expr = eval(expr = parse(text = getOption(x = "Azimuth.app.welcomebox")))
  )
  
  # render popup UI elements
  onclick('upload_popup', showModal(modalDialog(
    title = "Upload QC",
    div(
      paste(
        "The Azimuth reference-mapping procedure first identifies a set of 'anchors', ",
        "or pairwise correspondences between cells predicted to be in a similar biological state, ",
        "between query and reference datasets. Here we report the percentage of query cells ",
        "participating in an anchor correspondence. The box color corresponds to the following bins: "
      ),
      tags$ul(list(
        tags$li(paste0("0% to ", getOption(x = "Azimuth.map.panchorscolors")[1], "%: Likely problematic (red)")),
        tags$li(paste0(getOption(x = "Azimuth.map.panchorscolors")[1], "% to ", getOption(x = "Azimuth.map.panchorscolors")[2], "%: Possibly problematic (yellow)")),
        tags$li(paste0(getOption(x = "Azimuth.map.panchorscolors")[2], "% to 100%: Likely successful (green)"))
      )),
      tags$h4("Caveats"),
      paste0(
        "If the query dataset consists of a homogeneous group of cells, or if the ",
        "query dataset contains cells from multiple batches (which would be corrected ",
        "by Azimuth), this metric may return a low value even in cases where mapping is ",
        "successful. Users in these cases should check results carefully. In particular, ",
        "we encourage users to verify identified differentially expressed marker genes for annotated cell types."
      )
    )
  )))
  onclick('overlap_popup', showModal(modalDialog(
    title = "Overlap QC",
    div(
      paste(
        "In order to conduct bridge integration for ATAC data without uploading a large ",
        "fragment file, we requantify the ATAC query peaks to match the multiomic bridge ",
        "based on the overlap between each query peak to a bridge peak and rename the query ",
        "peak to the bridge peak with highest overlap. The box color corresponds to the following bins: "
      ),
      tags$ul(list(
        tags$li(paste0("0% to 60%: Likely problematic (red)")),
        tags$li(paste0("60% to 80%:  Possibly problematic (yellow)")),
        tags$li(paste0("80% to 100%: Likely successful (green)"))
      )),
      tags$h4("Caveats"),
      paste0(
        "A high percentage of overlap is expected if the query ATAC data and bridge ATAC data ",
        "were processed with the same versions of Cell Ranger and means that there will ",
        "likely be little loss of information by using this overlap renaming process. ",
        "The mapping can still be sucessesful if this value has a low percentage, but downstream motif",
        "calculations may be innacurate as this again uses another overlap process to requantify peaks to motifs."
      )
    )
  )))
  onclick('jaccard_popup', showModal(modalDialog(
    title = "Blank QC",
    div(
      paste(
        "In order to conduct bridge integration for ATAC data without uploading a large ", 
        "fragment file, we requantify the ATAC query peaks to match the multiomic bridge ", 
        "based on the overlap between each query peak to a bridge peak and rename the query ", 
        "peak to the bridge peak with highest overlap. The box color corresponds to the following bins: "
      ),
      tags$ul(list(
        tags$li(paste0("0% to 20%: Likely problematic (red)")),
        tags$li(paste0("20% to 50%:  Possibly problematic (yellow)")),
        tags$li(paste0("50% to 100%: Likely successful (green)"))
      )),
      tags$h4("Caveats"),
      paste0(
        "A high jaccard similarity is expected if most of the peaks in the ATAC query are represented in the ", 
        "multiome data. This is expected to be lower than the overlap percentage as the query may contain extraneous ", 
        "peaks not captured in the mutliome. If this is low, you can still get good mapping if overlap is high.",
        "Gene activity scores are calculated with the original peaks, however, motifs are calculated based ", 
        "on the requantified counts, so be sure to check if the motif results make sense if your jaccard similarity is low."
      )
    )
  )))
  onclick('panchors_popup', showModal(modalDialog(
    title = "Anchor QC",
    div(
      paste(
        "The Azimuth reference-mapping procedure first identifies a set of 'anchors', ",
        "or pairwise correspondences between cells predicted to be in a similar biological state, ",
        "between query and reference datasets. Here we report the percentage of query cells ",
        "participating in an anchor correspondence. The box color corresponds to the following bins: "
      ),
      tags$ul(list(
        tags$li(paste0("0% to ", getOption(x = "Azimuth.map.panchorscolors")[1], "%: Likely problematic (red)")),
        tags$li(paste0(getOption(x = "Azimuth.map.panchorscolors")[1], "% to ", getOption(x = "Azimuth.map.panchorscolors")[2], "%: Possibly problematic (yellow)")),
        tags$li(paste0(getOption(x = "Azimuth.map.panchorscolors")[2], "% to 100%: Likely successful (green)"))
      )),
      tags$h4("Caveats"),
      paste0(
        "If the query dataset consists of a homogeneous group of cells, or if the ",
        "query dataset contains cells from multiple batches (which would be corrected ",
        "by Azimuth), this metric may return a low value even in cases where mapping is ",
        "successful. Users in these cases should check results carefully. In particular, ",
        "we encourage users to verify identified differentially expressed marker genes for annotated cell types."
      )
    )
  )))
  onclick('mappingqcstat_popup', showModal(modalDialog(
    title = "Cluster Preservation",
    div(
      tags$h4("Overview"),
      paste0(
        "For each query dataset, we downsample to at most 5,000 cells, and perform an ",
        "unsupervised clustering. This score reflects the preservation of the unsupervised ",
        "cluster structure, and is based on the entropy of unsupervised cluster labels in ",
        "each query cell's local neighborhood after mapping. Scores are scaled from 0 (poor) to 5 (best)"
      ),
      tags$ul(list(
        tags$li(paste0("0 to ", getOption(x = "Azimuth.map.postmapqccolors")[1], ": Likely problematic (red)")),
        tags$li(paste0(getOption(x = "Azimuth.map.postmapqccolors")[1], " to ", getOption(x = "Azimuth.map.postmapqccolors")[2], ": Possibly problematic (yellow)")),
        tags$li(paste0(getOption(x = "Azimuth.map.postmapqccolors")[2], " to 5: Likely successful (green)"))
      )),
      tags$h4("Caveats"),
      paste0(
        "This metric relies on the unsupervised clustering representing corresponding to ",
        "biologically distinct cell states. If the query dataset consists of a homogeneous ",
        "group of cells, or if the query dataset contains cells from multiple batches ",
        "(which would be corrected by Azimuth), this metric may return a low value even ",
        "in cases where mapping is successful. Users in these cases should check results ",
        "carefully. In particular, we encourage users to verify identified differentially ",
        "expressed marker genes for annotated cell types."
      ),
      # tags$h4("Details"),
      # paste0(
      #   "To compute the mapping statistic, we first randomly downsample the ",
      #   "query to ", getOption(x = "Azimuth.map.postmapqcds"), " cells for ",
      #   "computational efficiency. We then compute an independent unsupervised ",
      #   "clustering on the query. Using these cluster IDs, we then examine the ",
      #   "neighborhoods of each cell in query PCA space and also in the mapped ",
      #   "(projected) space. We compute an entropy of cluster labels and then ",
      #   "take the mean entropy averaged over each cluster in both spaces. For ",
      #   "each cluster we take the difference and report a single statistic as ",
      #   "the median -log2 of these values, clipped to range between 0 and 5.",
      #   "For the exact implementation details, please see the ",
      #   "ClusterPreservationScore function in the azimuth github repo."
      # ),
      
    )
  )))
}
satijalab/azimuth documentation built on Nov. 19, 2023, 8:34 a.m.