--- a
+++ b/R/objects.R
@@ -0,0 +1,2152 @@
+#' @include zzz.R
+#' @include allgenerics.R
+NULL
+
+####
+# Methods ####
+####
+
+#' Methods for VoltRon
+#'
+#' Methods for \code{\link{VoltRon}} objects for generics defined in other
+#' packages
+#'
+#' @param x A VoltRon object
+#' @param i,value Depends on the usage
+#' \describe{
+#'  \item{\code{$}, \code{$<-}}{Name (\code{i}) of a single metadata column from the main assay, see \link{vrMainAssay}}
+#'  \item{\code{[[}, \code{[[<-}}{
+#'    If only \code{i} is given, either a vrSample object or a vrAssay for \code{i} (and \code{value}) being name of the sample or assay.
+#'    If both \code{i} and \code{j} are given, vrLayer with layer name \code{j} (and \code{value}) of vrSample with same name \code{i}.
+#'  }
+#' }
+#' @param j Depends on the usage, see \code{i}.
+#' @param ... Arguments passed to other methods
+#'
+#' @name VoltRon-methods
+#' @rdname VoltRon-methods
+#'
+#' @concept voltron
+#'
+NULL
+
+## $ method ####
+
+#' @describeIn VoltRon-methods Metadata access for \code{VoltRon} objects
+#' 
+#' @export
+#' @method $ VoltRon
+"$.VoltRon" <- function(x, i, ...) {
+
+  # get assay names
+  assay_names <- vrAssayNames(x)
+
+  # metadata
+  metadata <- Metadata(x, assay = assay_names)
+
+  # get metadata column
+  # return(metadata[[i]])
+  # return(metadata[,i, drop = TRUE])
+  return(as.vector(metadata[[i]]))
+}
+
+#' @describeIn VoltRon-methods Metadata overwrite for \code{VoltRon} objects
+#'
+#' @export
+#' @method $<- VoltRon
+"$<-.VoltRon" <- function(x, i, value) {
+
+  # sample metadata
+  sample.metadata <- SampleMetadata(x)
+
+  # get assay names
+  assay_names <- vrAssayNames(x)
+
+  # metadata
+  metadata <- Metadata(x, assay = assay_names)
+
+  # dont change Assays or Layers
+  if(i %in% c("Assay", "Layer")){
+    stop("Changing names of assay types or layers aren't allowed!")
+  }
+
+  # change/insert either sample names of metadata columns of main assays
+  if(i == "Sample"){
+    if(!any(length(value) %in% c(1,nrow(sample.metadata)))){
+      stop("New sample names should of length 1 or the same number of assays!")
+    } else {
+      sample.metadata[[i]] <- value
+      x <- changeSampleNames(x, samples = value)
+    }
+  } else {
+    if(length(value) == 1 | nrow(metadata) == length(value)){
+      # metadata[[i]] <- value
+      # Metadata(x, assay = assay_names) <- metadata
+      x <- addMetadata(x, assay = assay_names, label = i, value = value)
+    } else {
+      stop("The new or the existing column should of length 1 or the same as the number of rows")
+    }
+  }
+
+  return(x)
+}
+
+#' @describeIn VoltRon-methods Autocompletion for \code{$} access for \code{VoltRon} objects
+#'
+#' @inheritParams utils::.DollarNames
+#'
+#' @importFrom utils .DollarNames
+#' @method .DollarNames VoltRon
+".DollarNames.VoltRon" <- function(x, pattern = '') {
+  meta.data <- as.list(x = Metadata(x))
+  return(.DollarNames(x = meta.data, pattern = pattern))
+}
+
+### subset of samples and layers ####
+
+#' @describeIn VoltRon-methods Accessing vrAssay or vrSample objects from \code{VoltRon} objects
+#'
+#' @aliases [[,VoltRon-methods
+#' @docType methods
+#'
+#' @export
+setMethod(
+  f = '[[',
+  signature = c('VoltRon', "character", "missing"),
+  definition = function(x, i, j, ...){
+
+    # if no assay were found, check sample names
+    sample_names <- names(slot(x, "samples"))
+
+    # check query sample name
+    if(!i %in% sample_names){
+
+      # check assays
+      sample.metadata <- SampleMetadata(x)
+      assay_names <- rownames(sample.metadata)
+      if(i %in% assay_names){
+        cur_assay <- sample.metadata[i,]
+        assay_list <- x@samples[[cur_assay$Sample]]@layer[[cur_assay$Layer]]@assay
+        assay_names <- vapply(assay_list, vrAssayNames, character(1))
+        return(assay_list[[which(assay_names == rownames(cur_assay))]])
+      } else {
+        stop("There are no samples or assays named ", i, " in this object")
+      }
+
+    } else {
+      return(x@samples[[i]])
+    }
+  }
+)
+
+#' @describeIn VoltRon-methods Overwriting vrAssay or vrSample objects from \code{VoltRon} objects
+#'
+#' @aliases [[<-,VoltRon-methods
+#' @docType methods
+#'
+#' @return \code{[[<-}: \code{x} with the metadata or associated objects added
+#' as \code{i}; if \code{value} is \code{NULL}, removes metadata or associated
+#' object \code{i} from object \code{x}
+#'
+#' @export
+#'
+setMethod(
+  f = '[[<-',
+  signature = c('VoltRon', "character", "missing"),
+  definition = function(x, i, j, ..., value){
+
+    # sample names
+    sample_names <- names(slot(x, "samples"))
+
+    # check query sample name
+    if(!i %in% sample_names){
+
+      # check assays
+      sample.metadata <- SampleMetadata(x)
+      assay_names <- rownames(sample.metadata)
+      if(i %in% assay_names){
+        cur_assay <- sample.metadata[i,]
+        x@samples[[cur_assay$Sample]]@layer[[cur_assay$Layer]]@assay[[cur_assay$Assay]] <- value
+      } else {
+        stop("There are no samples named ", i, " in this object")
+      }
+    } else {
+      if(!inherits(value, "vrSample") & !inherits(value, "vrBlock")  ) {
+        stop("The provided object is not of class vrSample")
+      }
+      x@samples[[i]] <- value
+    }
+    return(x)
+  }
+)
+
+#' @describeIn VoltRon-methods Accessing vrLayer objects from \code{VoltRon} objects
+#'
+#' @aliases [[,VoltRon-methods
+#' @docType methods
+#'
+#' @export
+#'
+setMethod(
+  f = '[[',
+  signature = c('VoltRon', "character", "character"),
+  definition = function(x, i, j, ...){
+    return(x[[i]]@layer[[j]])
+  }
+)
+
+#' @describeIn VoltRon-methods Overwriting vrLayer objects from \code{VoltRon} objects
+#'
+#' @aliases [[<-,VoltRon-methods
+#' @docType methods
+#'
+#' @return \code{[[<-}: \code{x} with the metadata or associated objects added
+#' as \code{i}; if \code{value} is \code{NULL}, removes metadata or associated
+#' object \code{i} from object \code{x}
+#'
+#' @export
+#'
+setMethod(
+  f = '[[<-',
+  signature = c('VoltRon', "character", "character"),
+  definition = function(x, i, j, ..., value){
+
+    if(!inherits(value, "vrLayer")){
+      stop("The provided object is not of class vrLayer")
+    }
+
+    x[[i]]@layer[[j]] <- value
+    return(x)
+  }
+)
+
+### Create VoltRon object ####
+
+#' formVoltRon
+#'
+#' Create a VoltRon object
+#'
+#' @param data the feature matrix of spatialpoints
+#' @param metadata a metadata object of class \link{vrMetadata}
+#' @param image a singelton or list of images as magick-image objects
+#' @param coords the coordinates of the spatial points
+#' @param segments the list of segments each associated with a spatial point
+#' @param sample.metadata a data frame of the sample metadata, see \link{SampleMetadata}
+#' @param main.assay the name of the main assay
+#' @param assay.type the type of the assay (tile, molecule, cell, spot or ROI)
+#' @param params additional parameters
+#' @param sample_name the name of the sample
+#' @param layer_name the name of the layer
+#' @param image_name the name/key of the image
+#' @param feature_name the name/key of the feature set
+#' @param project project name
+#' @param version the assay version, V1 or V2
+#' @param ... additional parameters passed to \link{formAssay}
+#'
+#' @importFrom igraph make_empty_graph V vertices
+#' @importFrom methods new
+#' @importFrom data.table data.table
+#' @importFrom rlang %||%
+#' @importFrom ids random_id
+#' @importFrom Matrix colSums
+#'
+#' @export
+#'
+formVoltRon <- function(data = NULL, 
+                        metadata = NULL, 
+                        image = NULL,
+                        coords,
+                        segments = list(),
+                        sample.metadata = NULL,
+                        main.assay = NULL, 
+                        assay.type = "cell", 
+                        params = list(),
+                        sample_name = NULL, 
+                        layer_name = NULL, 
+                        image_name = NULL, 
+                        feature_name = NULL,
+                        project = NULL, 
+                        version = "v2", ...){
+
+  # set project name
+  if(is.null(project))
+    project <- "VoltRon"
+
+  # check VoltRon object version
+  if(!version %in% c("v1", "v2")){
+    stop("'version' has to be set to either 'v1' or 'v2'")
+  }
+  
+  # layer and sample names
+  if(is.null(main.assay))
+    main.assay <- paste0("Custom_", assay.type)
+  layer_name <- ifelse(is.null(layer_name), "Section1", layer_name)
+  if(main.assay == layer_name)
+      stop("'", layer_name, "' cannot be a layer name, since main assay is named '", main.assay, "'.")
+  sample_name <- ifelse(is.null(sample_name), "Sample1", sample_name)
+  if(main.assay == sample_name)
+    stop("'", sample_name, "' cannot be a sample name, since main assay is named '", main.assay, "'.")
+  image_name <- ifelse(is.null(image_name), "image_1", image_name)
+
+  # entity IDs from either the data or metadata
+  if(!is.null(data)){
+
+    # check for colnames of the raw data
+    if(is.null(colnames(data))){
+      entityID_nopostfix <- paste0(assay.type, seq_len(ncol(data)))
+    } else {
+      entityID_nopostfix <- colnames(data)
+    }
+
+  } else{
+
+    # make empty data if data is missing
+    data <- matrix(nrow = 0, ncol = nrow(metadata))
+
+    # check for metadata
+    if(!is.null(metadata)) {
+
+      # check row names if exists
+      if(is.null(rownames(metadata)) && is.null(metadata$id)){
+        entityID_nopostfix <- paste0(assay.type, seq_len(nrow(metadata)))
+        rownames(metadata) <- entityID
+      } else {
+        entityID_nopostfix <- metadata$id %||% rownames(metadata)
+      }
+    } else {
+      stop("Either data or metadata has to be provided to build a VoltRon object")
+    }
+  }
+
+  # Metadata
+  vr_metadata_list <- setVRMetadata(metadata, 
+                              data, 
+                              entityID_nopostfix, 
+                              main.assay, 
+                              assay.type,
+                              sample_name, 
+                              layer_name, 
+                              version)
+  vr_metadata <- vr_metadata_list$vr_metadata
+  entityID <- vr_metadata_list$entityID
+  colnames(data) <- entityID
+  
+  # Coordinates
+  if(!is.null(coords)){
+    if(inherits(coords, "data.frame")){
+      coords <- as.matrix(coords)
+    }
+    if(!inherits(coords, "matrix")){
+      stop("Coordinates table should either of a matrix or data.frame class!")
+    }
+    if(ncol(coords) == 2){
+      coords <- cbind(coords,0)
+    } else if(ncol(coords) == 3){
+      rownames(coords) <- entityID
+    } else {
+      stop("The length of colnames of the coordinates matrix should be either two or three!")
+    }
+    rownames(coords) <- entityID
+    colnames(coords) <- c("x", "y", "z")
+  } else {
+    stop("There are no coordinate matrix provided!")
+  }
+
+  # create vrAssay
+  Assay <- formAssay(data = data, 
+                     coords = coords, 
+                     segments = segments, 
+                     image = image, 
+                     params = params, 
+                     type = assay.type, 
+                     name = "Assay1", 
+                     main_image = image_name, 
+                     main_featureset = feature_name, 
+                     ...)
+  listofAssays <- list(Assay)
+  names(listofAssays) <- main.assay
+
+  # create layers
+  listofLayers <- list(methods::new("vrLayer",
+                                    assay = listofAssays,
+                                    connectivity = igraph::make_empty_graph(directed = FALSE) + igraph::vertices(entityID)))
+  names(listofLayers) <- layer_name
+  
+  # create samples
+  listofSamples <- list(methods::new("vrBlock", 
+                                     layer = listofLayers, 
+                                     zlocation = c("Section1" = 0),
+                                     adjacency = matrix(0, nrow = 1, ncol = 1,
+                                                        dimnames = list("Section1", "Section1"))))
+                        
+
+  names(listofSamples) <- sample_name
+
+  # set sample meta data
+  if(is.null(sample.metadata)){
+    sample.metadata <- setVRSampleMetadata(listofSamples)
+  }
+
+  # set VoltRon class
+  methods::new("VoltRon", samples = listofSamples, metadata = vr_metadata, sample.metadata = sample.metadata, main.assay = main.assay, project = project)
+}
+
+### Assay Methods ####
+
+updateAssayVoltRon <- function(object, assay = NULL) {
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # set embeddings
+  for(assy in assay_names)
+    object[[assy]] <- updateAssay(object[[assy]])
+  
+  return(object)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' 
+#' @rdname updateAssay
+#' @method updateAssay VoltRon
+#' @export
+setMethod("updateAssay", "VoltRon", updateAssayVoltRon)
+
+#' Main Assay
+#'
+#' Get and set the main assay of a VoltRon object
+#'
+#' @param object a VoltRon object
+#' @rdname vrMainAssay
+#'
+#' @export
+setMethod("vrMainAssay", "VoltRon", function(object) {
+  object@main.assay
+})
+
+#' @rdname vrMainAssay
+#'
+#' @export
+setMethod("vrMainAssay<-", "VoltRon", function(object, value) {
+  sample.metadata <- SampleMetadata(object)
+  assay_names <- unique(sample.metadata$Assay)
+  if(!value %in% assay_names){
+    stop("There is no assay names '", value, "' in this object")
+  } else {
+    object@main.assay <- value
+  }
+  return(object)
+})
+
+addAssayVoltRon <- function(object, assay, metadata = NULL, assay_name, sample = "Sample1", layer = "Section1"){
+
+  # sample metadata
+  sample.metadata <- SampleMetadata(object)
+
+  # get assay id
+  assay_ids <- as.numeric(gsub("Assay", "", rownames(sample.metadata)))
+  assay_id <- paste0("Assay", max(assay_ids)+1)
+  assay_names <- c(rownames(sample.metadata), assay_id)
+
+  # update sample.metadata and metadata
+  object@sample.metadata <- rbind(sample.metadata, c(assay_name, layer, sample))
+  rownames(object@sample.metadata) <- assay_names
+  object@metadata <- addAssay(object@metadata, metadata = metadata,
+                              assay = assay, assay_name = assay_name,
+                              sample = sample, layer = layer)
+
+  # get sample and layer
+  curlayer <- object[[sample, layer]]
+  assay_list <- curlayer@assay
+
+  # change assay name and add to the layer
+  vrAssayNames(assay) <- assay_id
+  new_assay_list <- list(assay)
+  names(new_assay_list) <- assay_name
+  assay_list <- c(assay_list, new_assay_list)
+  object[[sample, layer]]@assay <- assay_list
+
+  # add connectivities of assay to the layer
+  catch_connect <- try(slot(curlayer, name = "connectivity"), silent = TRUE)
+  if(!is(catch_connect, 'try-error') && !methods::is(catch_connect,'error')){
+    g_assay <- igraph::make_empty_graph(directed = FALSE) + igraph::vertices(vrSpatialPoints(object, assay = assay_id))
+    g_layer <- curlayer@connectivity + g_assay
+    object[[sample, layer]]@connectivity <- g_layer 
+  }
+
+  # return
+  return(object)
+}
+
+#' @param assay a vrAssay object
+#' @param metadata a predefined metadata
+#' @param assay_name assay name of the new added assay
+#' @param sample sample name
+#' @param layer layer name
+#' 
+#' @rdname addAssay
+#' @method addAssay VoltRon
+#'
+#' @importFrom igraph make_empty_graph add_edges vertices
+#'
+#' @export
+setMethod("addAssay", "VoltRon", addAssayVoltRon)
+
+vrAssayNamesVoltRon <- function(object, assay = NULL){
+  
+  # sample metadata
+  sample.metadata <- SampleMetadata(object)
+  
+  # check assays
+  if(is.null(assay))
+    assay <- vrMainAssay(object)
+  
+  # get assay names
+  if(any(assay == "all")){
+    assay_names <- rownames(sample.metadata)
+  } else {
+    if(all(assay %in% sample.metadata$Assay)){
+      assay_names <- rownames(sample.metadata)[sample.metadata$Assay %in% assay]
+    } else {
+      if(all(assay %in% rownames(sample.metadata))) {
+        assay_names <- assay
+      } else {
+        stop("Assay name or type is not found in the object")
+      }
+    }
+  }
+  
+  return(assay_names)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#'
+#' @rdname vrAssayNames
+#' @order 2
+#' @export
+setMethod("vrAssayNames", "VoltRon", vrAssayNamesVoltRon)
+
+vrAssayTypesVoltRon <- function(object, assay = NULL){
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # get assay types
+  assay_types <- vapply(assay_names, function(x) vrAssayTypes(object[[x]]), character(1))
+  
+  return(assay_types)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' 
+#' @rdname vrAssayTypes
+#' @order 2
+#'
+#' @export
+setMethod("vrAssayTypes", "VoltRon", vrAssayTypesVoltRon)
+
+changeSampleNamesVoltRon <- function(object, samples = NULL){
+  
+  # sample metadata
+  sample.metadata <- SampleMetadata(object)
+  
+  # old to new samples table
+  samples_table <- data.frame(sample.metadata, AssayID = rownames(sample.metadata), NewSample = samples)
+  
+  # check if multiple new sample names are associated with the same section of one sample
+  check_samples_table <- samples_table %>%
+    dplyr::group_by(Assay, Sample) %>% dplyr::mutate(n = dplyr::n_distinct(NewSample)) %>%
+    select(c("Assay", "Sample", "n")) %>% distinct()
+  if(any(check_samples_table$n > 1)){
+    message("Overwriting the sample names of assays that were original from a single layer of a sample aren't allowed")
+    stop("Check Sample Metadata for the correct Sample reassignment")
+  }
+  
+  # assign new sample names to samples and sample metadata
+  new_sample.metadata <- NULL
+  new_listofSamples <- list()
+  for(cur_sample in unique(samples)){
+    
+    # current sample and sample table
+    cur_sample.metadata <- samples_table[samples_table$NewSample == cur_sample,]
+    
+    # for each unique sample names, combine layers and multiple samples into one
+    listofLayers <- NULL
+    uniq_old_samples <- unique(cur_sample.metadata$Sample)
+    for(i in seq_len(length(uniq_old_samples))){
+      listofLayers <- c(listofLayers, object[[uniq_old_samples[i]]]@layer)
+    }
+    cur_sample.metadata$comb <- paste(cur_sample.metadata$Sample, cur_sample.metadata$Layer, sep = "_")
+    cur_sample.metadata$NewLayer <- paste0("Section", as.numeric(factor(cur_sample.metadata$comb, levels = unique(cur_sample.metadata$comb))))
+    # names(listofLayers) <- cur_sample.metadata$NewLayer
+    names(listofLayers) <- unique(cur_sample.metadata$NewLayer) ## CHANGE THIS LATER IF NEEDED ####
+    
+    # make layer adjacency and get distance
+    adjacency <- matrix(0, nrow = length(listofLayers), ncol = length(listofLayers),
+                        dimnames = list(names(listofLayers), names(listofLayers)))
+    diag(adjacency) <- 1
+    # distance <- matrix(NA, nrow = length(listofLayers), ncol = length(listofLayers),
+    #                    dimnames = list(names(listofLayers), names(listofLayers)))
+    # diag(distance) <- 0
+    zlocation <- rep(0,length(listofLayers))
+    names(zlocation) <- names(listofLayers)
+    
+    # make new block
+    # listofSamples <- list(methods::new("vrBlock", 
+    #                                    layer = listofLayers, adjacency = adjacency, distance = distance))
+    listofSamples <- list(methods::new("vrBlock",
+                                       layer = listofLayers, zlocation = zlocation, adjacency = adjacency))
+    names(listofSamples) <- cur_sample
+    new_listofSamples <- c(new_listofSamples, listofSamples)
+    new_sample.metadata <- rbind(new_sample.metadata, cur_sample.metadata)
+  }
+  
+  # assign new samples and layers to metadata
+  metadata <- changeSampleNames(Metadata(object, type = "all"), sample_metadata_table = new_sample.metadata)
+  
+  # sample metadata
+  new_sample.metadata <- new_sample.metadata[,c("Assay", "NewLayer", "NewSample")]
+  colnames(new_sample.metadata) <- c("Assay", "Layer", "Sample")
+  
+  # reinsert object elements
+  object@sample.metadata <- new_sample.metadata
+  object@samples <- new_listofSamples
+  object@metadata <- metadata
+  
+  # return
+  return(object)
+}
+
+#' changeSampleNames.VoltRon
+#'
+#' Change the sample names of the VoltRon object and reorient layers if needed
+#'
+#' @param samples a single or a set of sample names
+#' 
+#' @rdname changeSampleNames
+#'
+#' @importFrom dplyr n_distinct %>% distinct select mutate group_by
+#' @importFrom methods new
+#'
+#' @noRd
+setMethod("changeSampleNames", "VoltRon", changeSampleNamesVoltRon)
+
+changeAssayNamesVoltRon <- function(object, assays = NULL){
+
+  # sample metadata
+  sample.metadata <- SampleMetadata(object)
+
+  # check the length of the new assay names
+  if(nrow(sample.metadata) != length(assays))
+    stop("The set of new assay names should be of the number of assays in the VoltRon object.")
+
+  # check the uniqueness of the assay names
+  if(length(unique(assays)) != length(assays))
+    stop("Each new assay name should be unique")
+
+  # attach new names of sample.metadata
+  sample.metadata$NewAssayNames <- assays
+
+  # change assay names in layers
+  samples <- unique(sample.metadata$Sample)
+  for(samp in samples){
+    object[[samp]] <- changeAssayNames(object[[samp]], sample.metadata = sample.metadata[sample.metadata$Sample == samp,])
+  }
+
+  # return
+  return(object)
+}
+
+#' changeAssayNames.VoltRon
+#'
+#' Change the sample names of the VoltRon object and reorient layers if needed
+#'
+#' @rdname changeAssayNames
+#' @method changeAssayNames VoltRon
+#'
+#' @param object a VoltRon object
+#' @param assays a set of assay names
+#'
+#' @noRd
+setMethod("changeAssayNames", "VoltRon", changeAssayNamesVoltRon)
+
+#' addLayerConnectivity
+#'
+#' add connectivity information to the assays (vrAssay) of the same layer (vrLayer)
+#'
+#' @param object a VoltRon object
+#' @param connectivity a metadata of edges representing connected spatial points across assays
+#' @param sample sample name
+#' @param layer layer name
+#'
+#' @importFrom igraph add_edges
+#'
+#' @noRd
+addLayerConnectivity <- function(object, connectivity, sample, layer){
+
+  # get sample and layer
+  curlayer <- object[[sample, layer]]
+
+  # make edges from connectivity matrix
+  connectivity <- as.vector(t(as.matrix(connectivity)))
+
+  # add edges
+  object[[sample, layer]]@connectivity <- igraph::add_edges(curlayer@connectivity, edges = connectivity)
+
+  # return
+  return(object)
+}
+
+### Layer Methods ####
+
+#' addBlockConnectivity
+#'
+#' add connectivity information to the layers (vrLayer) of the same block (Block)
+#'
+#' @param object a VoltRon object
+#' @param connectivity a metadata of edges representing connected layers within a block
+#' @param zlocation 
+#' @param sample sample name
+#'
+#' @noRd
+addBlockConnectivity <- function(object, connectivity, zlocation = NULL, sample){
+  
+  # get sample and layer
+  cursample <- object[[sample]]
+  
+  # update z location/coordinates
+  if(!is.null(zlocation)){
+    cursample@zlocation[names(cursample@zlocation)] <- zlocation
+  }
+  
+  # update adjacency
+  adjacency <- cursample@adjacency
+  for(i in seq_len(nrow(connectivity))){
+    adjacency[connectivity[i,1], connectivity[i,2]] <- 
+      adjacency[connectivity[i,2], connectivity[i,1]] <- 1
+  }
+  cursample@adjacency <- adjacency
+  
+  # return sample
+  object[[sample]] <- cursample
+  
+  # return
+  return(object)
+}
+
+#' getBlockConnectivity
+#'
+#' get connected assays
+#'
+#' @param object a VoltRon object
+#' @param connectivity a metadata of edges representing connected layers within a block
+#' @param zlocation 
+#' @param sample sample name
+#'
+#' @importFrom igraph components graph_from_adjacency_matrix
+#' 
+#' @noRd
+getBlockConnectivity <- function(object, assay){
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # get samples
+  sample_metadata <- SampleMetadata(object)
+  samples <- unique(sample_metadata[assay_names, "Sample"])
+  
+  # get list of connected assays
+  assay_list <- list()
+  for(samp in samples){
+    cur_sample_metadata <- sample_metadata[sample_metadata$Sample == samp,]
+    cur_assaynames <- assay_names[assay_names %in% rownames(cur_sample_metadata)]
+    cur_sections <- cur_sample_metadata[cur_assaynames, "Layer"]
+    
+    catch_connect <- try(slot(object[[samp]], name = "adjacency"), silent = TRUE)
+    if(!is(catch_connect, 'try-error') && !methods::is(catch_connect,'error')){
+      adjacency <- object[[samp]]@adjacency
+      adjacency <- adjacency[match(cur_sections,rownames(adjacency)), match(cur_sections,rownames(adjacency)), drop = FALSE]
+      colnames(adjacency) <- rownames(adjacency) <- cur_assaynames
+      components <- igraph::components(igraph::graph_from_adjacency_matrix(adjacency))
+      assay_list <- c(assay_list, split(names(components$membership), components$membership))
+    } else {
+      assay_list <- c(assay_list, cur_assaynames)
+    }
+  }
+  
+  # return list
+  assay_list
+}
+
+### Object Methods ####
+
+subsetVoltRon <- function(x, subset, samples = NULL, assays = NULL, spatialpoints = NULL, features = NULL, image = NULL, interactive = FALSE, use.points.only = FALSE, 
+                           shiny.options = list(launch.browser = getOption("shiny.launch.browser", interactive()))) {
+
+  # start
+  object <- x
+  
+  # subseting based on subset argument
+  if (!missing(x = subset)) {
+    # subset_data <- subset
+    subset <- rlang::enquo(arg = subset)
+  }
+  if(!missing(subset)){
+    metadata <- Metadata(object)
+    name <- strsplit(rlang::quo_text(subset), split = " ")[[1]][1]
+    if(name %in% colnames(metadata)){
+      if(inherits(metadata, "data.table")){
+        spatialpoints <- metadata$id[eval_tidy(rlang::quo_get_expr(subset), data = metadata)]
+      } else if(inherits(metadata, c("HDF5DataFrame", "ZarrDataFrame", "DataFrame"))){
+        stop("Direct subsetting for Ondisk VoltRon objects are currently not possible!")
+        # spatialpoints <- as.vector(metadata$id)[eval_tidy(rlang::quo_get_expr(subset), data = metadata)]
+      } else {
+        if(!is.null(rownames(metadata))){
+          cur_data <- rownames(metadata)
+        } else {
+          cur_data <- metadata$id
+        }
+        spatialpoints <- rownames(metadata)[eval_tidy(rlang::quo_get_expr(subset), data = metadata)]
+      }
+    } else {
+      stop("Column '", name, "' is not found in the metadata")
+    }
+    object <- subsetVoltRon(object, spatialpoints = spatialpoints)
+    return(object)
+  }
+
+  # subseting on other attributes
+  attrinfo <- c(vapply(list(samples, assays, spatialpoints, features), function(x) length(x) > 0, logical(1)), interactive)
+  if(sum(attrinfo) > 1){
+    stop("Please choose only one of the subsetting attributes: 'samples', 'assays', 'spatialpoints', 'features' or 'interactive'")
+  }
+
+  # sample metadata
+  sample.metadata <- SampleMetadata(object)
+
+  # subsetting
+  if(!is.null(samples)){
+
+    # check assays associated with samples and subset for assays
+    if(all(samples %in% sample.metadata$Sample)){
+      assays <- rownames(sample.metadata)[sample.metadata$Sample %in% samples]
+      # return(subset.VoltRon(object, assays = assays))
+      return(subsetVoltRon(object, assays = assays))
+    } else {
+      stop("Some requested samples are not found in this VoltRon object!")
+    }
+
+  } else if(!is.null(assays)){
+
+    # subset for assays
+    sample.metadata <- subset_sampleMetadata(sample.metadata, assays = assays)
+    # metadata <- subset.vrMetadata(Metadata(object, type = "all"), assays = assays)
+    metadata <- subsetvrMetadata(Metadata(object, type = "all"), assays = assays)
+    samples <- unique(sample.metadata$Sample)
+    listofSamples <- sapply(object@samples[samples], function(samp) {
+      # subset.vrSample(samp, assays = assays)
+      subsetvrSample(samp, assays = assays)
+    }, USE.NAMES = TRUE)
+
+  } else if(!is.null(spatialpoints)) {
+
+    # subsetting on entity names
+    # metadata <- subset.vrMetadata(Metadata(object, type = "all"), spatialpoints = spatialpoints)
+    metadata <- subsetvrMetadata(Metadata(object, type = "all"), spatialpoints = spatialpoints)
+    samples <- vrSampleNames(metadata)
+    listofSamples <- sapply(object@samples[samples], function(samp) {
+      subsetvrSample(samp, spatialpoints = spatialpoints)
+    }, USE.NAMES = TRUE)
+    # spatialpoints <-  do.call("c", lapply(listofSamples, vrSpatialPoints.vrSample))
+    spatialpoints <-  do.call("c", lapply(listofSamples, vrSpatialPoints))
+    # metadata <- subset.vrMetadata(Metadata(object, type = "all"), spatialpoints = spatialpoints)
+    metadata <- subsetvrMetadata(Metadata(object, type = "all"), spatialpoints = spatialpoints)
+    sample.metadata <- subset_sampleMetadata(sample.metadata, assays = vrAssayNamesvrMetadata(metadata))
+
+  } else if(!is.null(features)){
+    
+    # subsetting on features
+    assay_names <- vrAssayNames(object)
+    for(assy in assay_names){
+      if(inherits(object[[assy]], "vrAssay")){
+        # object[[assy]] <- subset.vrAssay(object[[assy]], features = features) 
+        object[[assy]] <- subsetvrAssay(object[[assy]], features = features) 
+      } else {
+        # object[[assy]] <- subset.vrAssayV2(object[[assy]], features = features)
+        object[[assy]] <- subsetvrAssay(object[[assy]], features = features) 
+      }
+    } 
+    metadata <- Metadata(object, type = "all")
+    listofSamples <- object@samples
+
+  } else if(!is.null(image)) {
+
+    # subsetting on image
+    if(inherits(image, "character")){
+
+      # check if there are only one image and one assay
+      numlayers <- paste0(sample.metadata$Layer, sample.metadata$Sample)
+      if(length(unique(numlayers)) > 1){
+        stop("Subseting on images can only be performed on VoltRon objects with a single layer")
+      } else {
+        samples <- unique(sample.metadata$Sample)
+        listofSamples <- sapply(object@samples[samples], function(samp) {
+          subsetvrSample(samp, image = image)
+        }, USE.NAMES = TRUE)
+        # spatialpoints <-  do.call(c, lapply(listofSamples, vrSpatialPoints.vrSample))
+        spatialpoints <-  do.call("c", lapply(listofSamples, vrSpatialPoints))
+        # metadata <- subset.vrMetadata(Metadata(object, type = "all"), spatialpoints = spatialpoints)
+        metadata <- subsetvrMetadata(Metadata(object, type = "all"), spatialpoints = spatialpoints)
+      }
+    } else {
+      stop("Please provide a character based subsetting notation, see magick::image_crop documentation")
+    }
+  } else if(interactive){
+    
+    # interactive subsetting
+    results <- demuxVoltRon(object, use.points.only = use.points.only, shiny.options = shiny.options)
+    return(results)
+  }
+
+  # main.assay
+  main.assay <- unique(sample.metadata$Assay)[unique(sample.metadata$Assay) == names(table(sample.metadata$Assay))[which.max(table(sample.metadata$Assay))]]
+
+  # project
+  project <- object@project
+
+  # subset graphs
+  graph_list <- subset_graphs(object, 
+                              spatialpoints = vrSpatialPoints(metadata, assay = vrAssayNames(object)))
+
+  # set VoltRon class
+  methods::new("VoltRon",
+               samples = listofSamples, metadata = metadata, sample.metadata = sample.metadata,
+               graph = graph_list, main.assay = main.assay, project = project)
+}
+
+#' Subsetting VoltRon objects
+#'
+#' Given a VoltRon object, subset the object given one of the attributes
+#'
+#' @param x a VoltRon object
+#' @param subset Logical statement for subsetting
+#' @param samples the set of samples to subset the object
+#' @param assays the set of assays to subset the object
+#' @param spatialpoints the set of spatial points to subset the object
+#' @param features the set of features to subset the object
+#' @param image the subseting string passed to \link{image_crop}
+#' @param interactive TRUE if interactive subsetting on the image is demanded
+#' @param use.points.only if \code{interactive} is \code{TRUE}, use spatial points instead of the reference image
+#' @param shiny.options a list of shiny options (launch.browser, host, port etc.) passed \code{options} arguement of \link{shinyApp}. For more information, see \link{runApp}
+#' 
+#' @rdname subset
+#' @aliases subset
+#' @method subset VoltRon
+#'
+#' @importFrom rlang enquo eval_tidy quo_get_expr quo_text
+#' @importFrom stringr str_extract
+#' @importFrom methods new
+#'
+#' @export
+#'
+#' @examples
+#' # example data
+#' data("visium_data")
+#' 
+#' # subset based on assay
+#' subset(visium_data, assays = "Assay1")
+#' subset(visium_data, assays = "Visium")
+#' 
+#' # subset based on samples
+#' subset(visium_data, samples = "Anterior1")
+#' 
+#' # subset based on assay
+#' subset(visium_data, spatialpoints = c("GTTATATTATCTCCCT-1_Assay1", "GTTTGGGTTTCGCCCG-1_Assay1"))
+#' 
+#' # subset based on features
+#' subset(visium_data, features = c("Map3k19", "Rab3gap1"))
+#' 
+#' # interactive subsetting
+#' \dontrun{
+#' visium_subset_data <- subset(visium_data, interactive = TRUE)
+#' visium_subset <- visium_subset_data$subsets[[1]]
+#' }
+setMethod("subset", "VoltRon", subsetVoltRon)
+
+mergeVoltRon <- function(x, y, samples = NULL, main.assay = NULL, verbose = TRUE) {
+
+  # start 
+  object <- x 
+  object_list <- y
+  
+  # combine all elements
+  if(!is.list(object_list))
+    object_list <- list(object_list)
+  object_list <- c(object, object_list)
+
+  # check if all are VoltRon
+  if(!all(lapply(object_list, class) == "VoltRon"))
+    stop("All arguements have to be of VoltRon class")
+
+  # sample metadata list
+  sample.metadata_list <- lapply(object_list, function(x) slot(x, name = "sample.metadata"))
+
+  # old assay names
+  old_assay_names <- do.call(c, lapply(sample.metadata_list, rownames))
+
+  # merge sample metadata
+  sample.metadata <- merge_sampleMetadata(sample.metadata_list)
+
+  # merge metadata and sample metadata
+  if(verbose)
+    message("Merging metadata ...")
+  metadata_list <- lapply(object_list, function(x) slot(x, name = "metadata"))
+  metadata <- mergevrMetadata(metadata_list[[1]], metadata_list[-1])
+
+  # combine samples and rename layers
+  if(verbose)
+    message("Merging blocks and layers ...")
+  listofSamples <- NULL
+  for(i in seq_len(length(object_list))){
+    cur_object <- object_list[[i]]@samples
+    listofSamples <- c(listofSamples, cur_object)
+  }
+
+  # get main assay
+  if(is.null(main.assay))
+      main.assay <- names(sort(table(sample.metadata$Assay), decreasing = TRUE))[1]
+
+  # project
+  project <- slot(object_list[[1]], "project")
+
+  # set VoltRon class
+  object <- methods::new("VoltRon", samples = listofSamples, metadata = metadata, sample.metadata = sample.metadata, main.assay = main.assay, project = project)
+
+  # change assay names and sample names
+  object <- changeAssayNames(object, assays = rownames(sample.metadata))
+
+  # change sample names
+  if(!is.null(samples))
+    object$Sample <- samples
+
+  # return
+  object
+}
+
+#' Merging VoltRon objects
+#'
+#' Given a VoltRon object, and a list of VoltRon objects, merge all.
+#'
+#' @param x a VoltRon Object
+#' @param y a single or a list of VoltRon objects
+#' @param samples a single sample name or multiple sample names of the same size as the given VoltRon objects
+#' @param main.assay the name of the main assay
+#' @param verbose verbose
+#'
+#' @rdname merge
+#' @aliases merge
+#' @method merge VoltRon
+#' @importFrom methods new
+#'
+#' @export
+setMethod("merge", signature = "VoltRon", mergeVoltRon)
+
+#' @rdname vrSpatialPoints
+#' @order 2
+#' 
+#' @export
+setMethod("vrSpatialPoints", "VoltRon", function(object, assay = NULL) {
+
+  # get assays
+  assay <- vrAssayNames(object, assay = assay)
+
+  # return
+  return(vrSpatialPoints(object@metadata, assay = assay))
+})
+
+vrFeaturesVoltRon <- function(object, assay = NULL) {
+
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+
+  # get all features
+  features <- NULL
+  for(assy in assay_names)
+    features <- c(features, vrFeatures(object[[assy]]))
+
+  return(unique(features))
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#'
+#' @rdname vrFeatures
+#' @method vrFeatures VoltRon
+#' @order 2
+#' @export
+setMethod("vrFeatures", "VoltRon", vrFeaturesVoltRon)
+
+vrFeatureDataVoltRon <- function(object, assay = NULL, feat_type = NULL) {
+
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+
+  # get all features
+  features <- vrFeatureData(object[[assay_names[1]]], feat_type = feat_type)
+
+  # return
+  return(features)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#'
+#' @rdname vrFeatureData
+#' @order 2
+#' @export
+setMethod("vrFeatureData", "VoltRon", vrFeatureDataVoltRon)
+
+vrFeatureDataReplaceVoltRon <- function(object, assay = NULL, value) {
+
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+
+  # set embeddings
+  for(assy in assay_names)
+    vrFeatureData(object[[assy]]) <- value
+
+  return(object)
+}
+
+#' @param value new feature metadata
+#' 
+#' @rdname vrFeatureData
+#' @order 4
+#' @export
+setMethod("vrFeatureData<-", "VoltRon", vrFeatureDataReplaceVoltRon)
+
+vrDataVoltRon <- function(object, assay = NULL, features = NULL, feat_type = NULL, norm = FALSE, ...) {
+
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+
+  # get all coordinates
+  data <- NULL
+  for(i in seq_len(length(assay_names))){
+    cur_data <- vrData(object[[assay_names[i]]], features = features, feat_type = feat_type, norm = norm, ...)
+    if(inherits(cur_data, c("dgCMatrix", "CsparseMatrix", "dsparseMatrix"))){
+      cur_data <- as.matrix(cur_data)
+    }
+    if(inherits(cur_data, c("data.frame", "Matrix", "matrix"))){
+      cur_data <- data.frame(cur_data, feature.ID = rownames(cur_data), check.names = FALSE) 
+    } 
+    if(i == 1){
+      data <- cur_data
+    } else {
+      data <- merge_data(data, cur_data, by = "feature.ID")
+    }
+  }
+  if("feature.ID" %in% colnames(data)){
+    rownames(data) <- data$feature.ID
+    data <- data[,!colnames(data) %in% "feature.ID"] 
+    data <- as.matrix(data)
+    data <- replaceNaMatrix(data, 0)
+    colnames(data) <- gsub("\\.","-", colnames(data))
+  }
+
+  return(data)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' @param features the set of features
+#' @param feat_type the feature set type 
+#' @param norm TRUE if normalized data should be returned
+#' @param ... additional parameters passed to other methods and \link{vrImages}
+#'
+#' @rdname vrData
+#' @order 2
+#' 
+#' @importFrom dplyr full_join mutate_all coalesce
+#'
+#' @export
+setMethod("vrData", "VoltRon", vrDataVoltRon)
+
+#' @importFrom Matrix Matrix
+merge_data <- function(data1, data2, by = "feature.ID"){
+  if(inherits(data1, c("data.frame", "Matrix"))){
+    
+    # merge
+    data1 <- dplyr::full_join(data1, data2, by = "feature.ID")
+    
+  } else if(inherits(data1, c("IterableMatrix"))) {
+    rownames_all <- unique(c(rownames(data1), rownames(data2)))
+    
+    # first data
+    m <- Matrix::Matrix(nrow = length(rownames_all) - length(rownames(data1)), ncol = ncol(data1), data = 0, sparse = TRUE)
+    data1_new <- rbind(data1, m)
+    rownames(data1_new) <- c(rownames(data1), setdiff(rownames_all, rownames(data1)))
+    data1_new <- data1_new[rownames_all,]
+    
+    # second data
+    m <- Matrix::Matrix(nrow = length(rownames_all) - length(rownames(data2)), ncol = ncol(data2), data = 0, sparse = TRUE)
+    data2_new <- rbind(data2, m)
+    rownames(data2_new) <- c(rownames(data2), setdiff(rownames_all, rownames(data2)))
+    data2_new <- data2_new[rownames_all,]
+   
+    # merge 
+    data1 <- cbind(data1_new, data2_new)
+  }
+  return(data1)
+}
+
+generateTileDataVoltRon <- function(object, assay = NULL, ...) {
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # check if assay types are all tiles
+  assay_types <- vrAssayTypes(object, assay = assay)
+  if(!all(assay_types == "tile"))
+    stop("generateTileData can only be used for tile-based assays")
+  
+  # get tile data for all assays
+  for(assy in assay_names)
+    object[[assy]] <- generateTileData(object[[assy]], ...)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' @param ... additional parameters passed to vrAssay.
+#' 
+#' @rdname generateTileData
+#' @order 2
+#'
+#' @export
+setMethod("generateTileData", "VoltRon", generateTileDataVoltRon)
+
+vrEmbeddingsVoltRon <- function(object, assay = NULL, type = "pca", dims = seq_len(30)) {
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # get all coordinates
+  returndata_list <- list()
+  for(i in seq_len(length(assay_names)))
+    returndata_list[[i]] <- vrEmbeddings(object[[assay_names[i]]], type = type, dims = dims)
+  
+  return(do.call(rbind, returndata_list))
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' @param type the key name for the embedding, i.e. "pca" or "umap"
+#' @param dims the set of dimensions of the embedding data
+#' 
+#' @rdname vrEmbeddings
+#' @order 2
+#'
+#' @export
+setMethod("vrEmbeddings", "VoltRon", vrEmbeddingsVoltRon)
+
+vrEmbeddingsReplaceVoltRon <- function(object, assay = NULL, type = "pca", overwrite = FALSE, value) {
+  
+  # check if the embedding exists
+  if(type %in% vrEmbeddingNames(object) && !overwrite)
+    stop("An embedding named '", type, "' already exists in this object. Do overwrite = TRUE for replacing with the existing one.")
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # set embeddings
+  for(assy in assay_names){
+    assayobject <- object[[assy]]
+    if(vrAssayTypes(assayobject) %in% c("ROI", "cell", "spot")){
+      vrEmbeddings(assayobject, type = type) <- value[grepl(paste0(assy, "$"), rownames(value)),, drop = FALSE]
+    } else {
+      vrEmbeddings(assayobject, type = type) <- value[vrSpatialPoints(assayobject),, drop = FALSE]
+    }
+    object[[assy]] <- assayobject
+  }
+  
+  return(object)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' @param type the key name for the embedding
+#' @param overwrite Whether the existing embedding with name 'type' should be overwritten
+#' @param value new embedding data
+#'
+#' @rdname vrEmbeddings
+#' @order 4
+#'
+#' @export
+setMethod("vrEmbeddings<-", "VoltRon", vrEmbeddingsReplaceVoltRon)
+
+vrEmbeddingNamesVoltRon <- function(object, assay = NULL){
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # get assay types
+  embed_names <- unique(unlist(lapply(assay_names, function(x) vrEmbeddingNames(object[[x]]))))
+  
+  return(embed_names)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#'
+#' @rdname vrEmbeddingNames
+#' @order 2
+#'
+#' @export
+setMethod("vrEmbeddingNames", "VoltRon", vrEmbeddingNamesVoltRon)
+
+#### Feature ####
+
+addFeatureVoltRon <- function(object, assay = NULL, data, feature_name){
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  if(length(assay_names) > 1){
+    stop("You cannot add new features to multiple assays at once!")
+  }
+  
+  # add assay
+  object[[assay_names]] <- addFeature(object[[assay_names]], data = data, feature_name = feature_name)
+  
+  # return
+  return(object)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' If NULL, the default assay will be used, see \link{vrMainAssay}. If given as "all", then provides a summary of spatial systems across all assays.
+#' @param data new data matrix for new feature set
+#' @param feature_name the name of the new feature set
+#' 
+#' @rdname addFeature
+#' @method addFeature VoltRon
+#' 
+#' @importFrom stringr str_replace
+#' 
+#' @export
+setMethod("addFeature", "VoltRon", addFeatureVoltRon)
+
+vrMainFeatureTypeVoltRon <- function(object, assay = NULL){
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # if assay = all, give a summary
+  if(!is.null(assay)){
+    if(assay == "all"){
+      featuretype_names <- unlist(lapply(rownames(SampleMetadata(object)), function(x) paste(vrMainFeatureType(object[[x]]), collapse = ",")))
+      featuretype_names <- data.frame(Assay = assay_names, Feature = featuretype_names)
+      return(featuretype_names)
+    }
+  }
+  
+  # get assay types
+  featuretype_names <- unlist(lapply(assay_names, function(x) vrMainFeatureType(object[[x]])))
+  
+  # return data
+  if(!is.null(featuretype_names)){
+    featuretype_data <- data.frame(Assay = assay_names, Feature = featuretype_names)
+    return(featuretype_data)
+  } else {
+    return(NULL)
+  }
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' If NULL, the default assay will be used, see \link{vrMainAssay}. If given as "all", then provides a summary of spatial systems across all assays.
+#'
+#' @rdname vrMainFeatureType
+#' @order 2
+#' @export
+setMethod("vrMainFeatureType", "VoltRon", vrMainFeatureTypeVoltRon)
+
+vrMainFeatureTypeReplaceVoltRon <- function(object, assay = NULL, value){
+  
+  # sample metadata
+  sample_metadata <- SampleMetadata(object)
+  
+  # assays 
+  assay_names <- vrAssayNames(object, assay = assay)
+  unique_assays <- unique(sample_metadata[assay_names, "Assay"])
+  if(length(unique_assays) > 1){
+    stop("You can only set the main feature type of a single assay type")
+  } else {
+    for(assy in assay_names){
+      vrMainFeatureType(object[[assy]], ignore = TRUE) <- value
+    }
+  }
+  
+  return(object)
+}
+
+#' @rdname vrMainFeatureType
+#' @order 4
+#' @export
+setMethod("vrMainFeatureType<-", "VoltRon", vrMainFeatureTypeReplaceVoltRon)
+
+vrFeatureTypeNamesVoltRon <- function(object, assay = NULL){
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # if assay = all, give a summary
+  if(!is.null(assay)){
+    if(assay == "all"){
+      feature_names <- unlist(lapply(assay_names, function(x) paste(vrFeatureTypeNames(object[[x]]), collapse = ",")))
+      feature_names <- data.frame(Assay = assay_names, Feature = feature_names)
+      return(feature_names)
+    }
+  }
+  
+  feature_names <- unique(unlist(lapply(assay_names, function(x) vrFeatureTypeNames(object[[x]]))))
+  
+  return(feature_names)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' If NULL, the default assay will be used, see \link{vrMainAssay}. If given as "all", then provides a summary of spatial systems across all assays
+#'
+#' @rdname vrFeatureTypeNames
+#'
+#' @export
+setMethod("vrFeatureTypeNames", "VoltRon", vrFeatureTypeNamesVoltRon)
+
+#### Metadata ####
+  
+MetadataVoltRon <- function(object, assay = NULL, type = NULL){
+
+  # check type
+  if(!is.null(type)){
+    
+    if(type == "all"){
+      return(object@metadata)
+    } else {
+      if(!is.null(assay)){
+        stop("Please specify either assay or type, not both!")
+      }
+      if(type %in% methods::slotNames(object@metadata)){
+        return(slot(object@metadata, name = type))
+      }
+    }
+  } else{
+    type <- unique(vrAssayTypes(object, assay = assay))
+    if(length(type) > 1)
+      stop("You cannot get the metadata of multiple spatial entity types in the same time! See SampleMetadata()")
+  }
+
+  # get assay metadata from matching type
+  if(type %in% methods::slotNames(object@metadata)){
+
+    # sample metadata
+    sample.metadata <- SampleMetadata(object)
+
+    # get assay names
+    assay_names <- vrAssayNames(object, assay = assay)
+
+    # get metadata
+    metadata <- slot(object@metadata, name = type)
+    if(inherits(metadata, "data.table")){
+      metadata <- subset(metadata, assay_id %in% assay_names)
+    } else if(inherits(metadata, c("HDF5DataFrame", "ZarrDataFrame", "DataFrame"))){
+      if("assay_id" %in% colnames(metadata)){
+        metadata_list <- list()
+        for(assy in assay_names){
+          metadata_list[[assy]] <- metadata[metadata$assay_id == assy,]
+        }
+        metadata <- do.call("rbind", metadata_list)
+      } else {
+        ind <- stringr::str_extract(as.vector(metadata$id), "Assay[0-9]+") %in% assay_names
+        metadata <- metadata[ind,]
+      }
+    } else {
+      metadata <- metadata[stringr::str_extract(rownames(metadata), "Assay[0-9]+") %in% assay_names, ]
+    }
+    return(metadata)
+  } else {
+    stop("Please provide one of five assay types: 'ROI', 'cell', 'spot', 'molecule' or 'tile'.")
+  }
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' @param type the assay type: ROI, spot or cell, or all for the entire metadata object
+#' 
+#' @rdname Metadata
+#'
+#' @importFrom methods slotNames
+#' @export
+setMethod("Metadata", "VoltRon", MetadataVoltRon)
+          
+MetadataReplaceVoltRon <- function(object, assay = NULL, type = NULL, value) {
+
+  if(!is.data.frame(value) && !inherits(value, c("HDF5DataFrame", "ZarrDataFrame", "DataFrame")))
+    stop("The new or updated metadata has to be a data frame")
+
+  `%notin%` <- Negate(`%in%`)
+  if(is.null(rownames(value)) && "id" %notin% colnames(value))
+    stop("The new metadata should have row names or a column called 'id' to match its rows with the existing one")
+
+  if(is.null(type)){
+    type <- unique(vrAssayTypes(object, assay = assay))
+  }
+
+  # sample metadata
+  sample.metadata <- SampleMetadata(object)
+
+  # get assay names
+  # assay_names <- vrAssayNames(object, assay = assay)
+
+  # get metadata
+  metadata <- slot(object@metadata, name = type)
+
+  if("id" %in% colnames(metadata)){
+
+    # replace the metadata (or some part of it) with the new value
+    if(length(setdiff(value$id, metadata$id)) == 0){
+
+      # check columns of the new table
+      new_columns <- setdiff(colnames(value), colnames(metadata))
+
+      # current metadata shouldnt have columns that value doesnt have
+      if(length(setdiff(colnames(metadata), colnames(value))) > 0)
+        stop("Some columns of new data frame are not available in the metadata")
+
+      # if new columns appear, update the column names of the metadata'
+      if(length(new_columns) > 0){
+        if(inherits(metadata, "data.table")){
+          value <- value[,colnames(value)[colnames(value) %in% c(colnames(metadata), new_columns)], with = FALSE]
+        } else {
+          value <- value[,c(colnames(metadata), new_columns)]
+        }
+        for(cur_col in new_columns){
+          if(is.numeric(value[[cur_col]])){
+            metadata[[cur_col]] <- NA
+          } else {
+            metadata[[cur_col]] <- ""
+          }
+        }
+      }
+
+      # replace data
+      if(!inherits(metadata, "DataFrame")){
+        # TODO: is this replace method appropriate for all dataframe types ? 
+        # metadata[match(value$id, metadata$id), ] <- value
+        ind <- match(value$id, metadata$id)
+        for(cur_col in new_columns){
+          metadata[[cur_col]][ind] <- value[[cur_col]]
+        }
+      } else {
+        ind <- match(as.vector(value$id), as.vector(metadata$id))
+        for(cur_col in new_columns){
+          metadata[[cur_col]][ind] <- value[[cur_col]]
+        }
+      }
+      slot(object@metadata, name = type) <- metadata
+
+    } else {
+      stop("Some rows of new data frame are not available in the metadata")
+    }
+    
+  } else if(!is.null(rownames(metadata))){
+
+      # replace the metadata (or some part of it) with the new value
+      if(length(setdiff(rownames(value), rownames(metadata))) == 0){
+  
+        # check columns of the new table
+        new_columns <- setdiff(colnames(value), colnames(metadata))
+  
+        # current metadata shouldn't have columns that value doesnt have
+        if(length(setdiff(colnames(metadata), colnames(value))) > 0)
+          stop("Some columns of new data frame are not available in the metadata")
+  
+        # if new columns appear, update the column names of the metadata'
+        if(length(new_columns) > 0){
+          value <- value[,c(colnames(metadata), new_columns)]
+          for(cur_col in new_columns){
+            if(is.numeric(value[[cur_col]])){
+              metadata[[cur_col]] <- NA
+            } else {
+              metadata[[cur_col]] <- ""
+            }
+          }
+        }
+  
+        # replace data
+        metadata[rownames(value), ] <- value
+        slot(object@metadata, name = type) <- metadata
+      } else {
+        stop("Some rows of new data frame are not available in the metadata")
+      }
+    
+  } else {
+    stop("The metadata should either have rownames or a column called 'id'!")
+  }
+  
+  return(object)
+}
+
+#' @param value new metadata
+#'
+#' @rdname Metadata
+#' @method Metadata<- VoltRon
+#'
+#' @export
+setMethod("Metadata<-", "VoltRon", MetadataReplaceVoltRon)
+
+#' addMetadata
+#' 
+#' adding new columns or updating the values of the existing columns
+#' 
+#' @param object a VoltRon object
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' @param type the assay type: ROI, spot or cell, or all for the entire metadata object
+#' @param value the new values of the metadata column
+#' @param label the label of the new column, either a new column or an existing one
+#'
+#' @export
+addMetadata <- function(object, assay = NULL, type = NULL, value, label) {
+  
+  if(!inherits(object, "VoltRon"))
+    stop("Object must be of VoltRon class!")
+  
+  # auxiliary
+  `%notin%` <- Negate(`%in%`)
+
+  # check type
+  if(is.null(type)){
+    type <- unique(vrAssayTypes(object, assay = assay))
+    if(length(type) > 1){
+      stop("You cannot update the metadata of multiple spatial entity types in the same time! See SampleMetadata()")
+    }
+  }
+  
+  # sample metadata
+  sample.metadata <- SampleMetadata(object)
+  
+  # get assay names
+  entities <- vrSpatialPoints(object, assay = assay)
+
+  # get metadata
+  metadata <- slot(object@metadata, name = type)
+  
+  # add or replace the new column
+  if(label %notin% colnames(metadata)){
+    
+    # add empty values if the column is new
+    if(is.numeric(value)){
+      metadata[[label]] <- NA
+    } else {
+      metadata[[label]] <- ""
+    }
+  }
+  
+  # replace data
+  if(length(value) == length(entities) || length(value) == 1){
+    if(is.null(rownames(metadata)) || inherits(metadata, "data.table")){
+      metadata[[label]][match(entities, as.vector(metadata$id))] <- value
+    } else {
+      metadata[entities,][[label]] <- value
+    } 
+  } else {
+    stop("value should be of the same length as the rows of metadata or 1!")
+  }
+  
+  # replace metadata
+  slot(object@metadata, name = type) <- metadata
+  
+  # return
+  return(object)
+}
+
+
+#' SampleMetadata
+#'
+#' Get the sample metadata of a VoltRon object
+#'
+#' @param object a VoltRon object
+#'
+#' @export
+SampleMetadata <- function(object) {
+  object@sample.metadata
+}
+
+#### Spatial ####
+
+vrCoordinatesVoltRon <- function(object, assay = NULL, image_name = NULL, spatial_name = NULL, reg = FALSE) {
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # get sample metadata
+  sample_metadata <- SampleMetadata(object)
+  
+  # get spatial name
+  if(!is.null(spatial_name)) 
+    image_name <- spatial_name
+  
+  # get all coordinates
+  coords <- NULL
+  for(assy in assay_names){
+    
+    # get coordinates
+    cur_coords <- vrCoordinates(object[[assy]], image_name = image_name, reg = reg)
+    if(inherits(cur_coords, "IterableMatrix"))
+      cur_coords <- as.matrix(as(cur_coords, "dgCMatrix"))
+    
+    # update zlocation
+    sample_name <- sample_metadata[assy, "Sample"]
+    
+    catch_connect <- try(slot(object[[sample_name]], name = "zlocation"), silent = TRUE)
+    if(!is(catch_connect, 'try-error') && !methods::is(catch_connect,'error')){
+      zlocation <- object[[sample_name]]@zlocation 
+      cur_coords[,"z"] <- rep(zlocation[sample_metadata[assy, "Layer"]], nrow(cur_coords)) 
+    }
+    
+    # merge coordinates
+    if(!is.null(coords)){
+      coords <- rbind(coords, cur_coords)
+    } else {
+      coords <- cur_coords
+    }
+  }
+  
+  # return image
+  return(coords)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' @param image_name (deprecated, use \code{spatial_name}) the name/key of the image associated with the coordinates
+#' @param spatial_name the name/key of the spatial system associated with the coordinates
+#' @param reg TRUE if registered coordinates of the main image (\link{vrMainImage}) is requested
+#'
+#' @rdname vrCoordinates
+#' @order 2
+#' @export
+setMethod("vrCoordinates", "VoltRon", vrCoordinatesVoltRon)
+
+vrCoordinatesReplaceVoltRon <- function(object, image_name = NULL, spatial_name = NULL, reg = FALSE, value) {
+  
+  # sample metadata
+  sample.metadata <- SampleMetadata(object)
+  
+  # check the number of assays in the object
+  if(nrow(sample.metadata) > 1)
+    stop("Changing the coordinates of multiple assays in the same time are not permitted!")
+  
+  # get assay
+  cur_assay <- sample.metadata[1,]
+  vrlayer <- object[[cur_assay$Sample, cur_assay$Layer]]
+  vrassay <- vrlayer[[cur_assay$Assay]]
+  
+  # get spatial name
+  if(!is.null(spatial_name)) 
+    image_name <- spatial_name
+  
+  # change coordinates
+  vrCoordinates(vrassay, spatial_name = image_name, reg = reg) <- value
+  vrlayer[[cur_assay$Assay]] <- vrassay
+  object[[cur_assay$Sample, cur_assay$Layer]] <- vrlayer
+  
+  return(object)
+}
+
+#' @param value new coordinates of spatial points
+#' 
+#' @rdname vrCoordinates
+#' @order 4
+#' @export
+setMethod("vrCoordinates<-", "VoltRon", vrCoordinatesReplaceVoltRon)
+
+vrSegmentsVoltRon <- function(object, assay = NULL, image_name = NULL, spatial_name = NULL, reg = FALSE, as.data.frame = FALSE) {
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # get spatial name
+  if(!is.null(spatial_name)) 
+    image_name <- spatial_name
+  
+  # get all coordinates
+  segts <- NULL
+  for(assy in assay_names)
+    segts <- c(segts, vrSegments(object[[assy]], spatial_name = image_name, reg = reg))
+  
+  if(as.data.frame)
+    segts <- do.call(rbind, segts)
+
+  # return image
+  return(segts)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' @param image_name (deprecated, use \code{spatial_name}) the name/key of the image associated with the coordinates
+#' @param spatial_name the name/key of the spatial system associated with the coordinates
+#' @param reg TRUE if registered coordinates of the main image (\link{vrMainImage}) is requested
+#' @param as.data.frame if TRUE, the coordinates of segment nodes will be returned as a data frame
+#'
+#' @rdname vrSegments
+#' @order 2
+#' @export
+setMethod("vrSegments", "VoltRon", vrSegmentsVoltRon)
+
+vrSegmentsReplaceVoltRon <- function(object, image_name = NULL, spatial_name = NULL, reg = FALSE, value) {
+  
+  # sample metadata
+  sample.metadata <- SampleMetadata(object)
+  
+  # check the number of assays in the object
+  if(nrow(sample.metadata) > 1)
+    stop("Changing the coordinates of multiple assays are not permitted!")
+  
+  # get assay
+  cur_assay <- sample.metadata[1,]
+  vrlayer <- object[[cur_assay$Sample, cur_assay$Layer]]
+  vrassay <- vrlayer[[cur_assay$Assay]]
+  
+  # get spatial name
+  if(!is.null(spatial_name)) 
+    image_name <- spatial_name
+  
+  # change coordinates
+  vrSegments(vrassay, spatial_name = image_name, reg = reg) <- value
+  vrlayer[[cur_assay$Assay]] <- vrassay
+  object[[cur_assay$Sample, cur_assay$Layer]] <- vrlayer
+  
+  return(object)
+}
+
+#' @param value new segment coordinates of spatial points
+#' 
+#' @rdname vrSegments
+#' @order 5
+#' @export
+setMethod("vrSegments<-", "VoltRon", vrSegmentsReplaceVoltRon)
+
+flipCoordinatesVoltRon <- function(object, assay = NULL, image_name = NULL, spatial_name = NULL, ...){
+  
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  
+  # get spatial name
+  if(!is.null(spatial_name)) 
+    image_name <- spatial_name
+  
+  # flip coordinates
+  for(assy in assay_names){
+    object[[assy]] <- flipCoordinates(object[[assy]], spatial_name = image_name, ...)
+  }
+  return(object)
+}
+
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' @param image_name (deprecated, use \code{spatial_name}) the name/key of the image
+#' @param spatial_name the name/key of the spatial system associated with the coordinates
+#' @param ... additional parameters passed to \link{vrCoordinates} and \link{vrSegments}
+#' 
+#' @rdname flipCoordinates
+#' @order 2
+#'
+#' @export
+setMethod("flipCoordinates", "VoltRon", flipCoordinatesVoltRon)
+
+#### Graphs ####
+
+#' vrGraph
+#'
+#' Get graph of a VoltRon object
+#'
+#' @param object a VoltRon object
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#' @param graph.type the type of the graph, either custom or given by \link{getProfileNeighbors} or \link{getSpatialNeighbors} functions
+#'
+#' @rdname vrGraph
+#'
+#' @importFrom igraph induced_subgraph V
+#' 
+#' @export
+vrGraph <- function(object, assay = NULL, graph.type = NULL) {
+
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  node_names <- vrSpatialPoints(object, assay = assay_names)
+
+  # check if there exists graphs
+  if(length(names(object@graph)) == 0)
+    stop("There are no graphs in this VoltRon object!")
+
+  # check graph type
+  if(is.null(graph.type)){
+    graph.type <- vrGraphNames(object)
+    if(length(graph.type) == 0){
+      stop("There are no graphs in this VoltRon object!")
+    }
+    graph.type <- graph.type[1]
+  } else {
+    if(!graph.type %in% vrGraphNames(object))
+      stop("The graph name '", graph.type, "' can't be found in this VoltRon object!")
+  }
+
+  # return graph
+  if(length(vrGraphNames(object)) > 0){
+    node_names <- intersect(igraph::V(object@graph[[graph.type]])$name, node_names)
+    returngraph <- igraph::induced_subgraph(object@graph[[graph.type]], node_names)
+    return(returngraph)
+  } else {
+    warning("This VoltRon object does not have any graphs yet!")
+    return(NULL)
+  }
+}
+
+#' @param value new graph
+#' 
+#' @rdname vrGraph
+#'
+#' @importFrom igraph disjoint_union induced_subgraph V
+#' @export
+"vrGraph<-" <- function(object, assay = NULL, graph.type = "kNN", value) {
+
+  # check value
+  if(!inherits(value, "igraph"))
+    stop("The 'value' should be of an igraph class!")
+
+  # get assay names
+  assay_names <- vrAssayNames(object, assay = assay)
+  spobject <- vrSpatialPoints(object, assay = assay_names)
+
+  # check if there exists graphs
+  graph <- object@graph
+  if(length(names(object@graph)) == 0 || !graph.type %in% names(object@graph)){
+    
+    # graph[[graph.type]] <- make_empty_graph(directed = FALSE) + vertices(spobject)
+    graph[[graph.type]] <- value
+    
+  } else {
+   
+    # vertices
+    new_vert <- igraph::V(value)$name
+    
+    # edges
+    subg_inv <- igraph::induced_subgraph(graph[[graph.type]], spobject[!spobject%in%new_vert])
+    graph[[graph.type]] <- igraph::disjoint_union(value, subg_inv)
+  }
+  
+  # update object
+  object@graph <- graph 
+
+  # return
+  return(object)
+}
+
+#' vrGraphNames
+#'
+#' Get names of all graphs
+#'
+#' @param object a VoltRon object
+#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
+#' if NULL, the default assay will be used, see \link{vrMainAssay}.
+#'
+#' @rdname vrGraphNames
+#'
+#' @export
+vrGraphNames <- function(object, assay = NULL){
+  return(names(object@graph))
+}
+
+#' subset_graphs
+#'
+#' Given a VoltRon object and a vrMetadata, subset the graph
+#'
+#' @param object a VoltRon Object
+#' @param spatialpoints a set of spatial points
+#'
+#' @importFrom igraph subgraph V
+#'
+#' @noRd
+subset_graphs <- function(object, spatialpoints){
+
+  # graph names
+  graphnames <- vrGraphNames(object)
+
+  # for all graphs
+  if(!is.null(graphnames)){
+    graph_list <- object@graph
+    for(g in vrGraphNames(object)){
+      cur_graph <- graph_list[[g]]
+      cur_graph<- igraph::subgraph(cur_graph, igraph::V(cur_graph)[names(igraph::V(cur_graph)) %in% spatialpoints])
+      graph_list[[g]] <- cur_graph
+    }
+  } else {
+    graph_list <- list()
+  }
+
+  return(graph_list)
+}
+
+#' merge_graphs
+#'
+#' Given a VoltRon object, and a list of VoltRon objects, merge their graphs.
+#'
+#' @param object a VoltRon Object
+#' @param object_list a list of VoltRon objects
+#'
+#' @importFrom igraph disjoint_union
+#'
+#' @noRd
+merge_graphs <- function(object, object_list){
+
+  # combine all elements
+  if(!is.list(object_list))
+    object_list <- list(object_list)
+  if(inherits(object, "VoltRon")){
+    object_list <- c(object, object_list)
+  } else {
+    object_list <- c(list(object), object_list)
+  }
+
+  # choose objects
+  obj1 <- object_list[[1]]
+  obj2 <- object_list[[2]]
+
+  # initial combination
+  if(length(object_list) > 2){
+    combined_graph <- merge_graphs(obj1, obj2)
+    for(i in 3:(length(object_list))){
+      combined_graph <- merge_graphs(combined_graph, object_list[[i]])
+    }
+  } else {
+    updateobjects <- updateGraphAssay(obj1, obj2)
+    obj1 <- updateobjects$object1
+    obj2 <- updateobjects$object2
+    combined_graph <- igraph::disjoint_union(obj1, obj2)
+  }
+
+  return(combined_graph)
+}
+
+#' updateGraphAssay
+#'
+#' @param object1 VoltRon object
+#' @param object2 VoltRon object
+#'
+#' @importFrom igraph V
+#' @importFrom stringr str_extract
+#'
+#' @noRd
+updateGraphAssay <- function(object1, object2){
+
+  if(inherits(object1, "VoltRon"))
+    object1 <- vrGraph(object1, assay = "all")
+  if(inherits(object2, "VoltRon"))
+    object2 <- vrGraph(object2, assay = "all")
+
+  # get assay types
+  assaytype <- unique(stringr::str_extract(igraph::V(object1)$name, "Assay[0-9]+$"))
+  assaytype <- assaytype[order(nchar(assaytype), assaytype)]
+
+  # replace assay names
+  replacement <- paste0("Assay", seq_len(length(assaytype)))
+  vertex_names <- igraph::V(object1)$name
+  temp <- vertex_names
+  for(i in seq_len(length(assaytype)))
+    temp[grepl(paste0(assaytype[i],"$"), vertex_names)] <- gsub(paste0(assaytype[i],"$"), replacement[i],
+                                                                vertex_names[grepl(paste0(assaytype[i],"$"), vertex_names)])
+  igraph::V(object1)$name <- temp
+
+  # get assay types
+  assaytype <- unique(stringr::str_extract(igraph::V(object2)$name, "Assay[0-9]+$"))
+  assaytype <- assaytype[order(nchar(assaytype), assaytype)]
+
+  # replace assay names
+  replacement <- paste0("Assay", (length(replacement)+1):(length(replacement) + length(assaytype)))
+  vertex_names <- igraph::V(object2)$name
+  temp <- vertex_names
+  for(i in seq_len(length(assaytype)))
+    temp[grepl(paste0(assaytype[i],"$"), vertex_names)] <- gsub(paste0(assaytype[i],"$"), replacement[i],
+                                                                vertex_names[grepl(paste0(assaytype[i],"$"), vertex_names)])
+  igraph::V(object2)$name <- temp
+
+  # return
+  return(list(object1 = object1, object2 = object2))
+}
+
+#' combineGraphs
+#'
+#' Combining the edges of multiple graphs
+#'
+#' @param object a VoltRon Object
+#' @param graph.names a vector of graph names
+#' @param graph.weights the weights for edges of each graph.
+#' @param graph.key the name of the combined graph
+#'
+#' @importFrom igraph union edge_attr_names as_adjacency_matrix graph_from_adjacency_matrix
+#'
+#' @export
+combineGraphs <- function(object, graph.names = NULL, graph.weights = NULL, graph.key = "combined"){
+
+  if(!inherits(object, "VoltRon"))
+    stop("Object must be of VoltRon class!")
+
+  if(length(graph.names) == 0)
+    stop("Please provide graph names")
+
+  if(any(!graph.names %in% vrGraphNames(object))){
+    graph.names <- setdiff(graph.names, vrGraphNames(object))
+    stop("The following graphs are not included in the VoltRon object: ",
+         paste(graph.names, sep = ",", collapse = TRUE))
+  }
+
+  # check weights
+  if(is.null(graph.weights)){
+    graph.weights <- rep(0.5, length(graph.names))
+  }
+  if(length(graph.weights) != length(graph.names)){
+    stop("The weights should be of the length of graph names")
+  }
+  if(any(!is.numeric(graph.weights))){
+    stop("Weights should be numeric")
+  }
+  if(sum(graph.weights) != 1){
+    stop("Weights should sum up to 1!")
+  }
+  names(graph.weights) <- graph.names
+
+  # collect graphs
+  allmat <- NULL
+  for(gr in graph.names){
+    cur_graph <- vrGraph(object, graph.type = gr)
+    if("weight" %in% igraph::edge_attr_names(cur_graph)){
+      adjmat <- igraph::as_adjacency_matrix(cur_graph, attr = "weight")
+    } else {
+      adjmat <- igraph::as_adjacency_matrix(cur_graph)
+    }
+    adjmat <- adjmat*graph.weights[gr]
+    if(is.null(allmat)){
+      allmat <- adjmat
+    } else {
+      allmat <- allmat + adjmat
+    }
+  }
+
+  # union of graphs
+  vrGraph(object, graph.type = graph.key) <- igraph::graph_from_adjacency_matrix(allmat, mode = "undirected", weighted = TRUE, diag = FALSE)
+
+  # return
+  return(object)
+}
+