diff --git a/DESCRIPTION b/DESCRIPTION index 716118c..3f5586a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: SeuratWrappers Title: Community-Provided Methods and Extensions for the Seurat Object -Version: 0.3.5 -Date: 2024-04-04 +Version: 0.3.6 +Date: 2024-05-10 Authors@R: c( person(given = 'Andrew', family = 'Butler', email = 'abutler@nygenome.org', role = 'aut', comment = c(ORCID = '0000-0003-3608-0463')), person(given = "Saket", family = "Choudhary", email = "schoudhary@nygenome.org", role = "ctb", comment = c(ORCID = "0000-0001-5202-7633")), @@ -9,6 +9,7 @@ Authors@R: c( person(given = "Yuhan", family = "Hao", email = "yhao@nygenome.org", role = "ctb", comment = c(ORCID = "0000-0002-1810-0822")), person(given = "Austin", family = "Hartman", email = "ahartman@nygenome.org", role = "ctb", comment = c(ORCID = "0000-0001-7278-1852")), person(given = 'Paul', family = 'Hoffman', email = 'nygcSatijalab@nygenome.org', role = c('aut', 'cre'), comment = c(ORCID = '0000-0002-7693-8957')), + person(given = 'Skylar', family = 'Li', email = 'sli@nygenome.org', role = 'ctb', comment = c(ORCID = '0009-0002-3486-5785')), person(given = "Gesmira", family = "Molla", email = 'gmolla@nygenome.org', role = 'ctb', comment = c(ORCID = '0000-0002-8628-5056')), person(given = 'Rahul', family = 'Satija', email = 'rsatija@nygenome.org', role = 'aut', comment = c(ORCID = '0000-0001-9448-8833')), person(given = 'Tim', family = 'Stuart', email = 'tstuart@nygenome.org', role = 'aut', comment = c(ORCID = '0000-0002-3044-0897')) @@ -29,7 +30,8 @@ Remotes: welch-lab/liger, mojaveazure/seurat-disk, powellgenomicslab/Nebulosa, atakanekiz/CIPR-Package, - prabhakarlab/Banksy + prabhakarlab/Banksy, + JEFworks-Lab/SEraster Depends: R (>= 3.5.0) biocViews: @@ -62,6 +64,7 @@ Collate: 'monocle3.R' 'presto.R' 'scVI.R' + 'SEraster.R' 'tricycle.R' 'velocity.R' Encoding: UTF-8 @@ -86,4 +89,5 @@ Suggests: presto, flexmix, tricycle, - Banksy + Banksy, + SEraster diff --git a/NAMESPACE b/NAMESPACE index dd9bdac..e77bf9a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -26,6 +26,9 @@ export(RunQuantileNorm) export(RunSNF) export(RunVelocity) export(Runtricycle) +export(rasterizeGeneExpression) +export(rasterizeCellType) +export(permutateByRotation) export(StopCellbrowser) export(as.cell_data_set) export(scVIIntegration) diff --git a/R/SEraster.R b/R/SEraster.R new file mode 100644 index 0000000..910a6e9 --- /dev/null +++ b/R/SEraster.R @@ -0,0 +1,564 @@ +#' @include internal.R +#' +NULL +#' createRasterizedObject +#' @keyword internal +#' +createRasterizedObject <- function(input, out, name) { + image <- ifelse(name %in% Assays(input), Images(input, assay = name)[1], Images(input, assay = DefaultAssay(input))[1]) + input_fov <- input[[image]] + + data_rast <- out$data_rast + meta_rast <- out$meta_rast[,c("num_cell","type","resolution")] + resolution <- meta_rast[["resolution"]][1] + for (i in seq_along(out$meta_rast$cellID_list)) { + meta_rast$cellID_list[i] <- paste(unlist(out$meta_rast$cellID_list[[i]]), collapse = ", ") + } + + output_image_name <- paste0("ras.", resolution,".", image) + output_coordinates <- as.data.frame(out$pos_rast) + + output <- CreateSeuratObject( + counts = data_rast, + assay = name, + meta.data = meta_rast + ) + + input_molecules <- tryCatch(input_fov[["molecules"]], error = function(e) NULL) + + # `scale_factors` will be set to `NULL` unless there a matching + # implementation of the `ScaleFactors` generic available for `input_fov` + scale_factors <- tryCatch( + ScaleFactors(input_fov), + error = function(e) { + return (NULL) + } + ) + + output_radius <- sqrt(nrow(input[[]]) / nrow(meta_rast)) * ifelse(!is.null(Radius(input_fov, scale = NULL)), Radius(input_fov, scale = NULL), Radius(input_fov[['centroids']], scale = NULL)) + + output_centroids <- CreateCentroids( + coords = output_coordinates, + radius = output_radius + ) + + output_fov <- CreateFOV( + coords = output_centroids, + type = 'centroids', + molecules = input_molecules, + assay = name, + key = Key(output_image_name, quiet = TRUE) + ) + + if (!is.null(scale_factors)) { + scale_factors$spot <- output_radius + output_fov <- new( + Class = "VisiumV2", + boundaries = output_fov@boundaries, + molecules = output_fov@molecules, + assay = output_fov@assay, + key = output_fov@key, + image = input_fov@image, + scale.factors = scale_factors + ) + } + + output[[output_image_name]] <- output_fov + return(output) +} + +#' RunRasterizeGeneExpression +#' +#' @description Function to rasterize feature x observation matrix in spatially-resolved +#' omics data represented as Seurat objects. +#' +#' @description This function assumes that the input is provided as a \code{Seurat} +#' object or a \code{list} of \code{Seurat} objects. +#' +#' @param input \code{Seurat} object or \code{list}: Input data represented as a +#' \code{Seurat} object or \code{list} of \code{Seurat} objects. +#' +#' @param assay_name (character) Assay in Seurat object to use. If not specified, selects the default assay +#' +#' @param image (character) Image in Seurat object to use. If not specified, defaults to the first image available associated with the assay +#' +#' @param slot (character) Slot in Seurat assay to use. If not specified, defaults to counts +#' +#' @param resolution \code{integer} or \code{double}: Resolution refers to the side +#' length of each pixel for square pixels and the distance between opposite edges +#' of each pixel for hexagonal pixels. The unit of this parameter is assumed to +#' be the same as the unit of spatial coordinates of the input data. +#' +#' @param square \code{logical}: If TRUE (default), rasterize into square pixels. If FALSE, rasterize into hexagonal pixels, although not supported by Seurat plotting functions. +#' +#' @param fun \code{character}: If "mean", pixel value for each pixel +#' would be the proportion of each cell type based on the one-hot-encoded cell type +#' labels for all cells within the pixel. If "sum", pixel value for each pixel would +#' be the number of cells of each cell type based on the one-hot-encoded cell type +#' labels for all cells within the pixel. +#' +#' @param n_threads \code{integer}: Number of threads for parallelization. Default = 1. +#' Inputting this argument when the \code{BPPARAM} argument is missing would set parallel +#' exeuction back-end to be \code{BiocParallel::MulticoreParam(workers = n_threads)}. +#' We recommend setting this argument to be the number of cores available +#' (\code{parallel::detectCores(logical = FALSE)}). If \code{BPPARAM} argument is +#' not missing, the \code{BPPARAM} argument would override \code{n_threads} argument. +#' +#' @param BPPARAM \code{BiocParallelParam}: Optional additional argument for parallelization. +#' This argument is provided for advanced users of \code{BiocParallel} for further +#' flexibility for setting up parallel-execution back-end. Default is NULL. If +#' provided, this is assumed to be an instance of \code{BiocParallelParam}. +#' +#' @param verbose \code{logical}: Whether to display verbose output or warning. Default is FALSE +#' +#' @return If the input was given as \code{Seurat} object, the output is returned +#' as a new \code{Seurat} object with \code{assay} slot containing the +#' feature (cell types) x observations (pixels) matrix (dgCmatrix), \code{field of view} +#' slot containing spatial x,y coordinates of pixel centroids, and meta data for pixels +#' (number of cells that were aggregated in each pixel, +#' cell IDs of cells that were aggregated in each pixel, pixel type based on the +#' \code{square} argument, pixel resolution based on the \code{resolution} argument. +#' If the input was provided as \code{list} of \code{Seurat} objects, the output is +#' returned as a new \code{list} of \code{Seurat} objects containing information described +#' above for corresponding \code{Seurat} object. +#' +#' @importFrom SEraster rasterizeMatrix +#' @importFrom Matrix colSums +#' +#' @export +RunRasterizeGeneExpression <- function( + input, + assay_name = NULL, + image = NULL, + slot = "counts", + resolution = 100, + square = TRUE, + fun = "mean", + n_threads = 1, + BPPARAM = NULL, + verbose = FALSE +) { + if (is.list(input)) { + ## create a common bbox + bbox_mat <- do.call(rbind, lapply(seq_along(input), function(i) { + pos <- GetTissueCoordinates(input[[i]],scale = NULL) + if (!is.null(names(input))) { + dataset <- names(input)[[i]] + } else { + dataset <- i + } + return(data.frame(dataset = dataset, xmin = min(pos[,1]), xmax = max(pos[,1]), ymin = min(pos[,2]), ymax = max(pos[,2]))) + })) + + bbox_common <- sf::st_bbox(c( + xmin = floor(min(bbox_mat$xmin)-resolution/2), + xmax = ceiling(max(bbox_mat$xmax)+resolution/2), + ymin = floor(min(bbox_mat$ymin)-resolution/2), + ymax = ceiling(max(bbox_mat$ymax)+resolution/2) + )) + + ## rasterize iteratively + output_list <- lapply(seq_along(input), function(i) { + ## get Seurat object of the given index + spe <- input[[i]] + image <- ifelse(is.null(image), Images(spe, assay=assay_name)[1], image) + coords <- GetTissueCoordinates(spe, image = image, scale = NULL) + if("cell" %in% colnames(coords)){ + rownames(coords) <- colnames(spe) + } + + if (is.null(assay_name)) { + assay_name <- DefaultAssay(spe) + counts <- LayerData(spe[[assay_name]], layer = slot) + out <- SEraster::rasterizeMatrix(counts, coords, bbox = bbox_common, resolution = resolution, square = square, fun = fun, n_threads = n_threads, BPPARAM = BPPARAM, verbose = verbose) + } else { + stopifnot(is.character(assay_name)) + stopifnot("assay_name does not exist in the input Seurat object"= assay_name %in% Assays(spe)) + counts <- LayerData(spe[[assay_name]], layer = slot) + out <- SEraster::rasterizeMatrix(counts, coords, bbox = bbox_common, resolution = resolution, square = square, fun = fun, n_threads = n_threads, BPPARAM = BPPARAM, verbose = verbose) + } + + output <- createRasterizedObject(spe, out, assay_name) + return(output) + }) + + if (!is.null(names(input))) { + names(output_list) <- names(input) + } + ## return a list of Seurat objects + return(output_list) + } else { + ## create bbox + image <- ifelse(is.null(image), Images(input, assay=assay_name)[1], image) + pos <- GetTissueCoordinates(input, image = image, scale = NULL) + if("cell" %in% colnames(pos)){ + rownames(pos) <- pos$cell + } + + bbox <- sf::st_bbox(c( + xmin = floor(min(pos[,1])-resolution/2), + xmax = ceiling(max(pos[,1])+resolution/2), + ymin = floor(min(pos[,2])-resolution/2), + ymax = ceiling(max(pos[,2])+resolution/2) + )) + + ## rasterize + if (is.null(assay_name)) { + assay_name <- DefaultAssay(input) + counts <- LayerData(input[[assay_name]], layer = slot) + out <- SEraster::rasterizeMatrix(counts, pos, bbox = bbox, resolution = resolution, square = square, fun = fun, n_threads = n_threads, BPPARAM = BPPARAM, verbose = verbose) + } else { + stopifnot(is.character(assay_name)) + stopifnot("assay_name does not exist in the input Seurat object"= assay_name %in% Assays(input)) + counts <- LayerData(input[[assay_name]], layer = slot) + out <- SEraster::rasterizeMatrix(counts, pos, bbox = bbox, resolution = resolution, square = square, fun = fun, n_threads = n_threads, BPPARAM = BPPARAM, verbose = verbose) + } + + output <- createRasterizedObject(input=input, out=out, name=assay_name) + return(output) + } +} + +#' RunRasterizeCellType +#' +#' @description Function to rasterize cell type labels in spatially-resolved +#' omics data represented as Seurat object class. +#' +#' @description This function assumes that the input is provided as a \code{Seurat object} +#' object or a \code{list} of \code{Seurat} objects. +#' +#' @param input \code{Seurat} object or \code{list}: Input data represented as a +#' \code{Seurat} object or \code{list} of \code{Seurat} objects. +#' +#' @param col_name (character) Name of metadata column to rasterize on +#' +#' @param assay_name (character) Assay in Seurat object to use. If not specified, selects the default assay +#' +#' @param image (character) Image in Seurat object to use. If not specified, defaults to the first image available associated with the assay +#' +#' @param resolution \code{integer} or \code{double}: Resolution refers to the side +#' length of each pixel for square pixels and the distance between opposite edges +#' of each pixel for hexagonal pixels. The unit of this parameter is assumed to +#' be the same as the unit of spatial coordinates of the input data. +#' +#' @param square \code{logical}: If TRUE (default), rasterize into square pixels. If FALSE, rasterize into hexagonal pixels, although not supported by Seurat plotting functions. +#' +#' @param fun \code{character}: If "mean", pixel value for each pixel +#' would be the proportion of each cell type based on the one-hot-encoded cell type +#' labels for all cells within the pixel. If "sum", pixel value for each pixel would +#' be the number of cells of each cell type based on the one-hot-encoded cell type +#' labels for all cells within the pixel. +#' +#' @param n_threads \code{integer}: Number of threads for parallelization. Default = 1. +#' Inputting this argument when the \code{BPPARAM} argument is missing would set parallel +#' exeuction back-end to be \code{BiocParallel::MulticoreParam(workers = n_threads)}. +#' We recommend setting this argument to be the number of cores available +#' (\code{parallel::detectCores(logical = FALSE)}). If \code{BPPARAM} argument is +#' not missing, the \code{BPPARAM} argument would override \code{n_threads} argument. +#' +#' @param BPPARAM \code{BiocParallelParam}: Optional additional argument for parallelization. +#' This argument is provided for advanced users of \code{BiocParallel} for further +#' flexibility for setting up parallel-execution back-end. Default is NULL. If +#' provided, this is assumed to be an instance of \code{BiocParallelParam}. +#' +#' @param verbose \code{logical}: Whether to display verbose output or warning. Default is FALSE +#' +#' @return If the input was given as \code{Seurat} object, the output is returned +#' as a new \code{Seurat} object with \code{assay} slot containing the +#' feature (cell types) x observations (pixels) matrix (dgCmatrix), \code{field of view} +#' slot containing spatial x,y coordinates of pixel centroids, and meta data for pixels +#' (number of cells that were aggregated in each pixel, +#' cell IDs of cells that were aggregated in each pixel, pixel type based on the +#' \code{square} argument, pixel resolution based on the \code{resolution} argument. +#' If the input was provided as \code{list} of \code{Seurat} objects, the output is +#' returned as a new \code{list} of \code{Seurat} objects containing information described +#' above for corresponding \code{Seurat} object. +#' +#' @importFrom SEraster rasterizeMatrix +#' @importFrom Matrix sparse.model.matrix t colSums +#' +#' @export +#' +RunRasterizeCellType <- function( + input, + col_name, + assay_name = NULL, + image = NULL, + resolution = 100, + square = TRUE, + fun = "sum", + n_threads = 1, + BPPARAM = NULL, + verbose = FALSE +) { + if (is.list(input)) { + ## create a common bbox + bbox_mat <- do.call(rbind, lapply(seq_along(input), function(i) { + pos <- GetTissueCoordinates(input[[i]], scale = NULL) + if (!is.null(names(input))) { + dataset <- names(input)[[i]] + } else { + dataset <- i + } + return(data.frame(dataset = dataset, xmin = min(pos[,1]), xmax = max(pos[,1]), ymin = min(pos[,2]), ymax = max(pos[,2]))) + })) + bbox_common <- sf::st_bbox(c( + xmin = floor(min(bbox_mat$xmin)-resolution/2), + xmax = ceiling(max(bbox_mat$xmax)+resolution/2), + ymin = floor(min(bbox_mat$ymin)-resolution/2), + ymax = ceiling(max(bbox_mat$ymax)+resolution/2) + )) + + ## rasterize iteratively + output_list <- lapply(seq_along(input), function(i) { + ## get Seurat object of the given index + spe <- input[[i]] + image <- ifelse(is.null(image), Images(spe, assay=assay_name)[1], image) + coords <- GetTissueCoordinates(spe, image = image, scale = NULL) + if("cell" %in% colnames(coords)){ + rownames(coords) <- coords$cell + } + + ## extract cell type labels from Seurat object + stopifnot(is.character(col_name)) + stopifnot("col_name does not exist in the input Seurat object"= col_name %in% colnames(spe[[]])) + cellTypes <- as.factor(spe@meta.data[,col_name]) + + ## one-hot encode cell type labels as sparse matrix + mat_ct <- Matrix::t(Matrix::sparse.model.matrix(~ 0 + cellTypes)) + rownames(mat_ct) <- levels(cellTypes) + colnames(mat_ct) <- rownames(coords) + + ## rasterize + out <- SEraster::rasterizeMatrix(mat_ct, coords, bbox_common, resolution = resolution, square = square, fun = fun, n_threads = 1, BPPARAM = BPPARAM, verbose = verbose) + output <- createRasterizedObject(spe, out, col_name) + return(output) + }) + + if (!is.null(names(input))) { + names(output_list) <- names(input) + } + + ## return a list of Seurat objects + return(output_list) + + } else { + ## create bbox + image <- ifelse(is.null(image), Images(input, assay=assay_name)[1], image) + pos <- GetTissueCoordinates(input, image = image, scale = NULL) + if("cell" %in% colnames(pos)){ + rownames(pos) <- pos$cell + } + bbox <- sf::st_bbox(c( + xmin = floor(min(pos[,1])-resolution/2), + xmax = ceiling(max(pos[,1])+resolution/2), + ymin = floor(min(pos[,2])-resolution/2), + ymax = ceiling(max(pos[,2])+resolution/2) + )) + + ## extract cell type labels + stopifnot(is.character(col_name)) + stopifnot("col_name does not exist in the input Seurat object"= col_name %in% colnames(input[[]])) + cellTypes <- as.factor(input@meta.data[,col_name]) + + ## one-hot encode cell type labels as sparse matrix + mat_ct <- Matrix::t(Matrix::sparse.model.matrix(~ 0 + cellTypes)) + rownames(mat_ct) <- levels(cellTypes) + colnames(mat_ct) <- rownames(pos) + + ## rasterize + out <- SEraster::rasterizeMatrix(mat_ct, pos, bbox, resolution = resolution, square = square, fun = fun, n_threads = 1, BPPARAM = BPPARAM, verbose = verbose) + output <- createRasterizedObject(input=input, out=out, name=col_name) + return(output) + } +} + +#' RunPermutateByRotation +#' +#' @description Function to permutate a given input Seurat object(s) +#' by rotating the x,y coordinates around the midrange point. +#' +#' @description This function assumes that the input is provided as a \code{Seurat} +#' object or a \code{list} of \code{Seurat} objects. +#' +#' @description When the input is a \code{list} of \code{Seurat} objects, +#' all \code{Seurat} objects will be rotated around a common midrange +#' point computed based on combined x,y coordinates. +#' +#' @param input \code{Seurat} object or \code{list}: Input data represented as a +#' \code{Seurat} object or \code{list} of \code{Seurat} objects. +#' +#' @param n_perm \code{integer}: Number of permutations. Default = 1. This number is used to compute the angles at which the input is rotated at. +#' +#' @param verbose \code{logical}: Whether to display verbose output or warning. Default is FALSE. +#' +#' @return If the input was given as \code{Seurat} object, the output is returned +#' as a \code{list} of \code{n_perm} \code{Seurat} objects. Each \code{SpatialExperiment} +#' object has an updated field of view containing the spatial x,y coordinates +#' rotated at a corresponding angle. \code{assay} and metadata are inherited. +#' If the input was given as \code{list} of \code{Seurat} objects, +#' the output is returned as a new \code{list} of \code{length(input)} * \code{n_perm} +#' \code{Seurat} objects. +#' +#' @importFrom rearrr rotate_2d midrange +#' +#' @export +#' +RunPermutateByRotation <- function(input, n_perm = 1, verbose = FALSE) { + angles <- seq(0, 360, by = 360/n_perm)[1:n_perm] + + if (verbose) { + message(paste0("Number of permutations = ", n_perm)) + message(paste0("Angles used for rotations are ", paste(angles, collapse = ", "), " degrees")) + } + + if (is.list(input)) { + pos_comb <- do.call(rbind, lapply(seq_along(input), function(i) { + pos <- GetTissueCoordinates(input[[i]], scale = NULL) + if ("cell" %in% colnames(pos)) { + rownames(pos) <- pos$cell + } + dataset <- ifelse(!is.null(names(input)), names(input)[[i]], i) + dataset <- data.frame(dataset = dataset, x = pos[, 1], y = pos[, 2]) + rownames(dataset) <- rownames(pos) + return (dataset) + })) + midrange_pt <- rearrr::midrange(pos_comb, cols = c("x", "y")) + + if (verbose) { + message(paste0("Rotating all datasets around (x, y) = (", midrange_pt$x, ", ", midrange_pt$y, ").")) + } + + output <- unlist(lapply(input, function(spe) { + assay_name <- DefaultAssay(spe) + pos_orig <- data.frame(GetTissueCoordinates(spe, scale = NULL)) + colnames(pos_orig) <- c("x", "y") + if("cell" %in% colnames(pos_orig)){ + rownames(pos_orig) <- pos_orig$cell + } + + stopifnot("Column 1 and 2 of the spatialCoords slot should be named x and y, respectively." = colnames(pos_orig)[1:2] == c("x", "y")) + + lapply(angles, function(angle) { + pos_rotated <- rearrr::rotate_2d(data = pos_orig, degrees = angle, x_col = "x", y_col = "y", origin = as.numeric(midrange_pt), overwrite = F) + pos_rotated <- as.data.frame(pos_rotated[, c("x_rotated", "y_rotated")]) + + image_name <- Images(spe, assay = assay_name)[[1]] + class <- class(spe[[image_name]]) + + if (class == 'VisiumV1' || class == 'VisiumV2') { + message("Returning objects as VisiumV2 classes...") + input <- updateVisiumV2(spe, pos_rotated, assay_name, angle, image_name) + } else if (class == 'FOV') { + input <- updateFOV(spe, pos_rotated, assay_name, angle, image_name) + } + }) + })) + + return(output) + + } else { + assay_name <- DefaultAssay(input) + pos_orig <- GetTissueCoordinates(input, scale = NULL) + colnames(pos_orig) <- c("x", "y") + if("cell" %in% colnames(pos_orig)){ + rownames(pos_orig) <- pos_orig$cell + } + stopifnot("Column 1 and 2 of the spatialCoords slot should be named x and y, respectively." = colnames(pos_orig)[1:2] == c("x", "y")) + + output_list <- list() + for (angle in angles) { + image_name <- Images(input, assay = assay_name)[[1]] + image <- input[[image_name]] + midrange_pt <- rearrr::midrange(pos_orig, cols = c("x", "y")) + pos_rotated <- rearrr::rotate_2d(data = pos_orig, degrees = angle, x_col = "x", y_col = "y", origin = as.numeric(midrange_pt), overwrite = F) + pos_rotated <- as.data.frame(pos_rotated[, c("x_rotated", "y_rotated")]) + + class <- class(input[[image_name]]) + + if (class == 'VisiumV1' || class == 'VisiumV2') { + message("Returning objects as VisiumV2 classes...") + output <- updateVisiumV2(input, pos_rotated, assay_name, angle, image_name) + } else if (class == 'FOV') { + output <- updateFOV(input, pos_rotated, assay_name, angle, image_name) + } + output_list <- append(output_list, output) + } + return(output_list) + } +} + + +#' @keyword internal +#' +updateFOV <- function(object, image_name, rotated_coordinates, angle) { + fov <- object[[image_name]] + + rotated_name <- paste0(image_name, ".rotated", angle) + fov@key <- Key(rotated_name, quiet = TRUE) + + rotated_centroids <- tryCatch({ + input_centroids <- fov[["centroids"]] + rotated_centroids <- CreateCentroids( + coords = rotated_coordinates[, c("x_rotated", "y_rotated")], + nside = input_centroids@nsides, + radius = input_centroids@radius, + theta = angle + ) + + rotated_centroids + }, error = function(e) { + rotated_centroids <- CreateCentroids( + coords = rotated_coordinates[, c("x_rotated", "y_rotated")], + theta = angle + ) + return (rotated_centroids) + }) + + fov[["centroids"]] <- rotated_centroids + + # rotate a background image if one is present + tryCatch({ + background_image <- fov@image + fov@image <- rotate(background_image) + }, error = function(e) NULL) + + object[[image_name]] <- NULL + object[[rotated_name]] <- fov + + return (object) +} + +#' rotate +#' @keyword internal +#' +rotate <- function(arr, angle, pos) { + angle_rad <- angle * pi / 180 + rows <- dim(arr)[1] + cols <- dim(arr)[2] + + center_x <- cols / 2 + center_y <- rows / 2 + + size <- max(center_x*2,center_y*2) + + rotated_arr <- array(0, dim = c(size,size,3)) + + for (i in 1:rows) { + for (j in 1:cols) { + x <- j - center_x + y <- i - center_y + + rotated_x <- x * cos(-angle_rad) - y * sin(-angle_rad) + rotated_y <- x * sin(-angle_rad) + y * cos(-angle_rad) + + rotated_x <- rotated_x + center_x + rotated_y <- rotated_y + center_y + + if (rotated_x >= 1 && rotated_x <= dim(rotated_arr)[2] && rotated_y >= 1 && rotated_y <= dim(rotated_arr)[1]) { + rotated_arr[ceiling(rotated_y), ceiling(rotated_x), ] <- arr[i, j, ] + } + } + } + return(rotated_arr) +} diff --git a/README.md b/README.md index b19706e..f37570d 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,5 @@ remotes::install_github('satijalab/seurat-wrappers') | CIPR | [Using CIPR with human PBMC data](http://htmlpreview.github.io/?https://github.com/satijalab/seurat-wrappers/blob/master/docs/cipr.html) | Ekiz et. al., BMC Bioinformatics 2020 | https://github.com/atakanekiz/CIPR-Package | | miQC | [Running miQC on Seurat objects](http://htmlpreview.github.io/?https://github.com/satijalab/seurat-wrappers/blob/master/docs/miQC.html) | Hippen et. al., bioRxiv 2021 | https://github.com/greenelab/miQC | | tricycle | [Running estimate_cycle_position from tricycle on Seurat Objects](http://htmlpreview.github.io/?https://github.com/satijalab/seurat-wrappers/blob/master/docs/tricycle.html) | Zheng et. al., bioRxiv 2021 | https://www.bioconductor.org/packages/release/bioc/html/tricycle.html | +| BANKSY | [Running BANKSY with Seurat](https://github.com/satijalab/seurat-wrappers/blob/master/docs/banksy.md) | Singhal et al. Nature Genetics 2024 | https://github.com/prabhakarlab/Banksy | +| SEraster | [Running SEraster with Seurat](https://github.com/satijalab/seurat-wrappers/blob/master/docs/SEraster.md) | Aihara et al., bioRxiv 2024 | https://github.com/JEFworks-Lab/SEraster/tree/main | diff --git a/docs/SEraster.Rmd b/docs/SEraster.Rmd new file mode 100644 index 0000000..43da050 --- /dev/null +++ b/docs/SEraster.Rmd @@ -0,0 +1,176 @@ +--- +title: "Getting Started With SEraster" +date: 'Compiled: `r format(Sys.Date(), "%B %d, %Y")`' +output: + github_document: + html_preview: true + toc: true + html_document: + df_print: kable + theme: united + fig_height: 5 + fig_width: 16 + out_height: 4 +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set( + message = FALSE, + warning = FALSE +) +``` + +# Getting Started with SEraster + +In this vignette, we show how to run basic functionalities of `SEraster` using SeuratWrappers on Seurat objects. `SEraster` is a rasterization preprocessing framework that aggregates cellular information into spatial pixels to reduce resource requirements for spatial omics data analysis, developed by the JEFworks Lab. Functionalities provided by `SEraster` reduces the number of spots in spatial datasets for downstream analysis through a process of rasterization, where single cells' gene expression or cell-type labels are aggregated into equally sized pixels based on a user-defined resolution. + +While the original framework was created to accomodate SpatialExperiment objects, running `SEraster` with SeuratWrapper allows users to perform rasterization on Seurat spatial objects, supporting datatypes such as Vizgen MERSCOPE, Nanostring CosMx, Akoya CODEX, the 10x Genomics Visium system, and SLIDE-seq. + +## Load libraries + +```{r, include = T} +library(Seurat) +library(SeuratWrappers) +library(SeuratData) +library(SEraster) +library(ggplot2) +``` + +## Load example dataset + +This vignette uses the Tiny subset dataset provided in the [Fresh Frozen Mouse Brain for Xenium Explorer Demo](https://www.10xgenomics.com/resources/datasets/fresh-frozen-mouse-brain-for-xenium-explorer-demo-1-standard) and the [Visium HD mouse brain dataset](https://support.10xgenomics.com/spatial-gene-expression/datasets), both from from 10x Genomics. When using Seurat, we use different plotting functions for image-based and sequencing-based spatial datasets, and will therefore demonstrate SEraster behavior using both examples. + +```{r} +path <- "/brahms/hartmana/vignette_data/xenium_tiny_subset" +# Load the Xenium data +xenium.obj <- LoadXenium(path, fov = "fov") +# remove cells with 0 counts +xenium.obj <- subset(xenium.obj, subset = nCount_Xenium > 0) +``` +```{r} +# visualize +ImageFeaturePlot(xenium.obj, features = "nCount_Xenium") +``` +```{r} +localdir <- "/brahms/lis/visium_hd/mouse/new_mousebrain/" +# Load the VisiumHD data +visiumhd.obj <- Load10X_Spatial(data.dir = localdir, bin.size = 16) +``` +```{r} +# visualize +SpatialDimPlot(visiumhd.obj, alpha = 0.5) + NoLegend() +``` + +### Rasterize gene expression + +For continuous variables such as gene expression or other molecular information, `SEraster` aggregates the observed raw counts or normalized expression values for each molecule within each pixel using means by default (can be changed using the `fun` argument). When rasterizing Seurat objects, users can specify the `assay_name`, `slot`, and `image` for spatial objects with associated images. + +Let's try rasterizing the gene expression of the Xenium mouse brain dataset we loaded. + +```{r} +rastGexp <- RunRasterizeGeneExpression(xenium.obj, assay_name = "Xenium", resolution = 100) + +# check dimensions of spot by gene matrix after rasterization +dim(rastGexp[['Xenium']]) +``` + +Here, 36602 spots were rasterized into 248 spots. + +```{r} +# plot total rasterized gene expression +ImageDimPlot(rastGexp, size = 2) +``` + +```{r} +# plot specific genes on rasterized object +ImageFeaturePlot(rastGexp, size = 1.5, features = c("Cux2", "Rorb", "Bcl11b", "Foxp2"), max.cutoff = c(25, 35, 12, 10), cols = c("white", "red")) +# compare to original object +ImageFeaturePlot(xenium.obj, features = c("Cux2", "Rorb", "Bcl11b", "Foxp2"), max.cutoff = c(25, 35, 12, 10), size = 0.75, cols = c("white", "red")) +``` + +### Rasterize cell-type + +For categorical variables such as cell-type or cluster labels, `SEraster` aggregates the number of cells for each label within each pixel using sums by default (can be changed using the `fun` argument). + +Let's try rasterizing the cluster labels of the Xenium Tiny dataset. + +```{r} +xenium.obj <- NormalizeData(xenium.obj, assay = "Xenium") +xenium.obj <- ScaleData(xenium.obj, assay = "Xenium") +xenium.obj <- RunPCA(xenium.obj, npcs = 30, features = rownames(xenium.obj)) +xenium.obj <- RunUMAP(xenium.obj, dims = 1:30) +xenium.obj <- FindNeighbors(xenium.obj, reduction = "pca", dims = 1:30) +xenium.obj <- FindClusters(xenium.obj, resolution = 0.3) +``` +```{r} +DimPlot(xenium.obj, group.by='seurat_clusters') +ImageDimPlot(xenium.obj, group.by='seurat_clusters') +``` + +```{r} +rastCt <- RunRasterizeCellType(xenium.obj, col_name = "seurat_clusters", resolution = 200) + +# check the dimension of the cell-types-by-cells matrix after rasterizing cell-type labels +dim(rastCt[['seurat_clusters']]) +``` + +```{r} +# plot total cell counts per rasterized spot +ImageFeaturePlot(rastCt, size = 5, features = 'num_cell') +# plot number of seurat clusters for each rasterized spot +ImageFeaturePlot(rastCt, size = 5, features = 'nFeature_seurat_clusters') +``` + +### Setting rasterization resolution + +Rasterization resolution can be controlled by the `resolution` argument of the `rasterizeGeneExpression` and `rasterizeCellType` functions. Here, we refer to a particular resolution of rasterization by the side length for square pixels and the distance between opposite edges for hexagonal pixels such that finer resolution indicates smaller pixel size and vice versa. + +Let's see how the rasterized VisiumHD mouse brain dataset looks with various resolutions using square pixels, which is specified by `shape`. + +```{r} +# rasterize at defined resolution +rast2000 <- RunRasterizeGeneExpression(visiumhd.obj, assay_name="Spatial.016um", resolution = 2000) +rast2000 <- NormalizeData(rast2000) + +rast1000 <- RunRasterizeGeneExpression(visiumhd.obj, assay_name="Spatial.016um", resolution = 1000) +rast1000 <- NormalizeData(rast1000) + +# visualize +SpatialFeaturePlot(rast2000, shape = 22, pt.size.factor = 2, features = "Hpca") + ggtitle("Hpca expression (res = 2000)") +SpatialFeaturePlot(rast1000, shape = 22, pt.size.factor = 2, features = "Hpca") + ggtitle("Hpca expression (res = 1000)") +``` + +### Creating and rasterizing permutations + +Since rasterized values may be sensitive to edge effects such as the specific boundaries of grids upon rasterization, `SEraster` enables permutation by rotating the dataset at various angles before rasterization, which has been enabled in the SeuratWrappers implementation. + +For example, we create 3 permutations of the Xenium Tiny mouse brain dataset, which would return the object with x,y coordinates rotated at 0, 120, and 240 degrees around the midrange point as new field of views. + +In addition to a single `Seurat` spatial object, `rasterizeGeneExpression` and `rasterizeCellType` functions can both take a `list` of `Seurat` objects. This essentially allows users to streamline the preprocessing of permutations with `SEraster`; followed by a downstream analysis of choice. When`SEraster` rasterizes a `list` of objects, all objects in the inputted `list` are rasterized with the same pixel coordinate framework (same bounding box, resolution, centroid coordinates). This feature may not be particularly useful for permutations; however, it can potentially be applied to compare two or more datasets, such as structurally aligned tissues as well as healthy vs. disease tissues. + +```{r} +# permutate +DefaultAssay(xenium.obj) <- "Xenium" +spe_list <- RunPermutateByRotation(xenium.obj, n_perm = 3) + +# rasterize permutated datasets at once +out_list <- RunRasterizeGeneExpression(spe_list, assay_name = "Xenium", resolution = 200) + +for (i in seq_along(out_list)) { + # extract rotated angle + angle <- Images(out_list[[i]]) + # plot a specific gene + plt <- ImageFeaturePlot(out_list[[i]], features = "Rorb", max.cutoff = 35, size = 5, cols = c("white", "red")) + ggtitle(angle) + show(plt) +} +``` + +
+ +**Session Info** + +```{r} +sessionInfo() +``` + +
\ No newline at end of file diff --git a/docs/SEraster.md b/docs/SEraster.md new file mode 100644 index 0000000..1b2550e --- /dev/null +++ b/docs/SEraster.md @@ -0,0 +1,389 @@ +Getting Started With SEraster +================ +Compiled: June 07, 2024 + +# Getting Started with SEraster + +In this vignette, we show how to run basic functionalities of `SEraster` +using SeuratWrappers on Seurat objects. `SEraster` is a rasterization +preprocessing framework that aggregates cellular information into +spatial pixels to reduce resource requirements for spatial omics data +analysis, developed by the JEFworks Lab. Functionalities provided by +`SEraster` reduces the number of spots in spatial datasets for +downstream analysis through a process of rasterization, where single +cells’ gene expression or cell-type labels are aggregated into equally +sized pixels based on a user-defined resolution. + +While the original framework was created to accomodate SpatialExperiment +objects, running `SEraster` with SeuratWrapper allows users to perform +rasterization on Seurat spatial objects, supporting datatypes such as +Vizgen MERSCOPE, Nanostring CosMx, Akoya CODEX, the 10x Genomics Visium +system, and SLIDE-seq. + +## Load libraries + +``` r +library(Seurat) +library(SeuratWrappers) +library(SeuratData) +library(SEraster) +library(ggplot2) +``` + +## Load example dataset + +This vignette uses the Tiny subset dataset provided in the [Fresh Frozen +Mouse Brain for Xenium Explorer +Demo](https://www.10xgenomics.com/resources/datasets/fresh-frozen-mouse-brain-for-xenium-explorer-demo-1-standard) +and the [Visium HD mouse brain +dataset](https://support.10xgenomics.com/spatial-gene-expression/datasets), +both from from 10x Genomics. When using Seurat, we use different +plotting functions for image-based and sequencing-based spatial +datasets, and will therefore demonstrate SEraster behavior using both +examples. + +``` r +path <- "/brahms/hartmana/vignette_data/xenium_tiny_subset" +# Load the Xenium data +xenium.obj <- LoadXenium(path, fov = "fov") +# remove cells with 0 counts +xenium.obj <- subset(xenium.obj, subset = nCount_Xenium > 0) +``` + +``` r +# visualize +ImageFeaturePlot(xenium.obj, features = "nCount_Xenium") +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-4-1.png) + +``` r +localdir <- "/brahms/lis/visium_hd/mouse/new_mousebrain/" +# Load the VisiumHD data +visiumhd.obj <- Load10X_Spatial(data.dir = localdir, bin.size = 16) +``` + +``` r +# visualize +SpatialDimPlot(visiumhd.obj, alpha = 0.5) + NoLegend() +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-6-1.png) + +### Rasterize gene expression + +For continuous variables such as gene expression or other molecular +information, `SEraster` aggregates the observed raw counts or normalized +expression values for each molecule within each pixel using means by +default (can be changed using the `fun` argument). When rasterizing +Seurat objects, users can specify the `assay_name`, `slot`, and `image` +for spatial objects with associated images. + +Let’s try rasterizing the gene expression of the Xenium mouse brain +dataset we loaded. + +``` r +rastGexp <- RunRasterizeGeneExpression(xenium.obj, assay_name = "Xenium", resolution = 100) + +# check dimensions of spot by gene matrix after rasterization +dim(rastGexp[['Xenium']]) +``` + + ## [1] 248 1424 + +Here, 36602 spots were rasterized into 248 spots. + +``` r +# plot total rasterized gene expression +ImageDimPlot(rastGexp, size = 2) +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-8-1.png) + +``` r +# plot specific genes on rasterized object +ImageFeaturePlot(rastGexp, size = 1.5, features = c("Cux2", "Rorb", "Bcl11b", "Foxp2"), max.cutoff = c(25, 35, 12, 10), cols = c("white", "red")) +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-9-1.png) + +``` r +# compare to original object +ImageFeaturePlot(xenium.obj, features = c("Cux2", "Rorb", "Bcl11b", "Foxp2"), max.cutoff = c(25, 35, 12, 10), size = 0.75, cols = c("white", "red")) +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-9-2.png) + +### Rasterize cell-type + +For categorical variables such as cell-type or cluster labels, +`SEraster` aggregates the number of cells for each label within each +pixel using sums by default (can be changed using the `fun` argument). + +Let’s try rasterizing the cluster labels of the Xenium Tiny dataset. + +``` r +xenium.obj <- NormalizeData(xenium.obj, assay = "Xenium") +xenium.obj <- ScaleData(xenium.obj, assay = "Xenium") +xenium.obj <- RunPCA(xenium.obj, npcs = 30, features = rownames(xenium.obj)) +xenium.obj <- RunUMAP(xenium.obj, dims = 1:30) +xenium.obj <- FindNeighbors(xenium.obj, reduction = "pca", dims = 1:30) +xenium.obj <- FindClusters(xenium.obj, resolution = 0.3) +``` + + ## Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck + ## + ## Number of nodes: 36553 + ## Number of edges: 1410292 + ## + ## Running Louvain algorithm... + ## Maximum modularity in 10 random starts: 0.9504 + ## Number of communities: 23 + ## Elapsed time: 8 seconds + +``` r +DimPlot(xenium.obj, group.by='seurat_clusters') +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-11-1.png) + +``` r +ImageDimPlot(xenium.obj, group.by='seurat_clusters') +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-11-2.png) + +``` r +rastCt <- RunRasterizeCellType(xenium.obj, col_name = "seurat_clusters", resolution = 200) + +# check the dimension of the cell-types-by-cells matrix after rasterizing cell-type labels +dim(rastCt[['seurat_clusters']]) +``` + + ## [1] 23 396 + +``` r +# plot total cell counts per rasterized spot +ImageFeaturePlot(rastCt, size = 5, features = 'num_cell') +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-13-1.png) + +``` r +# plot number of seurat clusters for each rasterized spot +ImageFeaturePlot(rastCt, size = 5, features = 'nFeature_seurat_clusters') +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-13-2.png) + +### Setting rasterization resolution + +Rasterization resolution can be controlled by the `resolution` argument +of the `rasterizeGeneExpression` and `rasterizeCellType` functions. +Here, we refer to a particular resolution of rasterization by the side +length for square pixels and the distance between opposite edges for +hexagonal pixels such that finer resolution indicates smaller pixel size +and vice versa. + +Let’s see how the rasterized VisiumHD mouse brain dataset looks with +various resolutions using square pixels, which is specified by `shape`. + +``` r +# rasterize at defined resolution +rast2000 <- RunRasterizeGeneExpression(visiumhd.obj, assay_name="Spatial.016um", resolution = 2000) +rast2000 <- NormalizeData(rast2000) + +rast1000 <- RunRasterizeGeneExpression(visiumhd.obj, assay_name="Spatial.016um", resolution = 1000) +rast1000 <- NormalizeData(rast1000) + +# visualize +SpatialFeaturePlot(rast2000, shape = 22, pt.size.factor = 2, features = "Hpca") + ggtitle("Hpca expression (res = 2000)") +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-14-1.png) + +``` r +SpatialFeaturePlot(rast1000, shape = 22, pt.size.factor = 2, features = "Hpca") + ggtitle("Hpca expression (res = 1000)") +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-14-2.png) + +### Creating and rasterizing permutations + +Since rasterized values may be sensitive to edge effects such as the +specific boundaries of grids upon rasterization, `SEraster` enables +permutation by rotating the dataset at various angles before +rasterization, which has been enabled in the SeuratWrappers +implementation. + +For example, we create 3 permutations of the Xenium Tiny mouse brain +dataset, which would return the object with x,y coordinates rotated at +0, 120, and 240 degrees around the midrange point as new field of views. + +In addition to a single `Seurat` spatial object, +`rasterizeGeneExpression` and `rasterizeCellType` functions can both +take a `list` of `Seurat` objects. This essentially allows users to +streamline the preprocessing of permutations with `SEraster`; followed +by a downstream analysis of choice. When`SEraster` rasterizes a `list` +of objects, all objects in the inputted `list` are rasterized with the +same pixel coordinate framework (same bounding box, resolution, centroid +coordinates). This feature may not be particularly useful for +permutations; however, it can potentially be applied to compare two or +more datasets, such as structurally aligned tissues as well as healthy +vs. disease tissues. + +``` r +# permutate +DefaultAssay(xenium.obj) <- "Xenium" +spe_list <- RunPermutateByRotation(xenium.obj, n_perm = 3) + +# rasterize permutated datasets at once +out_list <- RunRasterizeGeneExpression(spe_list, assay_name = "Xenium", resolution = 200) + +for (i in seq_along(out_list)) { + # extract rotated angle + angle <- Images(out_list[[i]]) + # plot a specific gene + plt <- ImageFeaturePlot(out_list[[i]], features = "Rorb", max.cutoff = 35, size = 5, cols = c("white", "red")) + ggtitle(angle) + show(plt) +} +``` + +![](SEraster_files/figure-gfm/unnamed-chunk-15-1.png)![](SEraster_files/figure-gfm/unnamed-chunk-15-2.png)![](SEraster_files/figure-gfm/unnamed-chunk-15-3.png) + +
+ +**Session Info** + +``` r +sessionInfo() +``` + + ## R version 4.2.2 Patched (2022-11-10 r83330) + ## Platform: x86_64-pc-linux-gnu (64-bit) + ## Running under: Ubuntu 20.04.6 LTS + ## + ## Matrix products: default + ## BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0 + ## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0 + ## + ## locale: + ## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C + ## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8 + ## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 + ## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C + ## [9] LC_ADDRESS=C LC_TELEPHONE=C + ## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C + ## + ## attached base packages: + ## [1] stats graphics grDevices utils datasets methods base + ## + ## other attached packages: + ## [1] ggplot2_3.5.1 SEraster_0.99.0 + ## [3] thp1.eccite.SeuratData_3.1.5 stxBrain.SeuratData_0.1.1 + ## [5] ssHippo.SeuratData_3.1.4 pbmcsca.SeuratData_3.0.0 + ## [7] pbmcref.SeuratData_1.0.0 pbmcMultiome.SeuratData_0.1.4 + ## [9] pbmc3k.SeuratData_3.1.4 panc8.SeuratData_3.0.2 + ## [11] mousecortexref.SeuratData_1.0.0 ifnb.SeuratData_3.0.0 + ## [13] hcabm40k.SeuratData_3.0.0 cbmc.SeuratData_3.1.4 + ## [15] bonemarrowref.SeuratData_1.0.0 bmcite.SeuratData_0.3.0 + ## [17] SeuratData_0.2.2.9001 SeuratWrappers_0.3.6 + ## [19] Seurat_5.0.3.9922 SeuratObject_5.0.2 + ## [21] sp_2.1-3 + ## + ## loaded via a namespace (and not attached): + ## [1] rappdirs_0.3.3 scattermore_1.2 + ## [3] SpatialExperiment_1.8.1 R.methodsS3_1.8.2 + ## [5] tidyr_1.3.1 bit64_4.0.5 + ## [7] knitr_1.45 irlba_2.3.5.1 + ## [9] DelayedArray_0.24.0 R.utils_2.12.3 + ## [11] data.table_1.15.2 RCurl_1.98-1.14 + ## [13] generics_0.1.3 BiocGenerics_0.44.0 + ## [15] callr_3.7.3 cowplot_1.1.3 + ## [17] usethis_2.1.6 RANN_2.6.1 + ## [19] proxy_0.4-27 future_1.33.1 + ## [21] bit_4.0.5 spatstat.data_3.0-4 + ## [23] httpuv_1.6.14 assertthat_0.2.1 + ## [25] SummarizedExperiment_1.28.0 xfun_0.43 + ## [27] evaluate_0.23 promises_1.2.1 + ## [29] fansi_1.0.6 igraph_2.0.3 + ## [31] DBI_1.2.2 htmlwidgets_1.6.4 + ## [33] spatstat.geom_3.2-9 stats4_4.2.2 + ## [35] purrr_1.0.2 ellipsis_0.3.2 + ## [37] RSpectra_0.16-1 backports_1.4.1 + ## [39] dplyr_1.1.4 deldir_2.0-4 + ## [41] sparseMatrixStats_1.10.0 MatrixGenerics_1.10.0 + ## [43] vctrs_0.6.5 SingleCellExperiment_1.20.1 + ## [45] Biobase_2.58.0 remotes_2.5.0 + ## [47] ROCR_1.0-11 abind_1.4-5 + ## [49] cachem_1.0.8 withr_3.0.0 + ## [51] progressr_0.14.0 checkmate_2.3.1 + ## [53] sctransform_0.4.1 prettyunits_1.2.0 + ## [55] goftest_1.2-3 cluster_2.1.6 + ## [57] dotCall64_1.1-1 lazyeval_0.2.2 + ## [59] crayon_1.5.2 arrow_15.0.1 + ## [61] hdf5r_1.3.10 spatstat.explore_3.2-7 + ## [63] edgeR_3.40.2 pkgconfig_2.0.3 + ## [65] labeling_0.4.3 units_0.8-5 + ## [67] GenomeInfoDb_1.34.9 nlme_3.1-162 + ## [69] pkgload_1.3.3 devtools_2.4.5 + ## [71] rlang_1.1.3 globals_0.16.3 + ## [73] lifecycle_1.0.4 miniUI_0.1.1.1 + ## [75] fastDummies_1.7.3 rsvd_1.0.5 + ## [77] rprojroot_2.0.4 polyclip_1.10-6 + ## [79] RcppHNSW_0.6.0 matrixStats_1.3.0 + ## [81] lmtest_0.9-40 Matrix_1.6-5 + ## [83] Rhdf5lib_1.20.0 zoo_1.8-12 + ## [85] ggridges_0.5.6 processx_3.8.2 + ## [87] png_0.1-8 viridisLite_0.4.2 + ## [89] rjson_0.2.21 bitops_1.0-7 + ## [91] R.oo_1.26.0 KernSmooth_2.23-22 + ## [93] spam_2.10-0 rhdf5filters_1.10.1 + ## [95] DelayedMatrixStats_1.20.0 classInt_0.4-10 + ## [97] stringr_1.5.1 parallelly_1.37.1 + ## [99] spatstat.random_3.2-3 S4Vectors_0.36.2 + ## [101] beachmat_2.14.2 scales_1.3.0 + ## [103] memoise_2.0.1 magrittr_2.0.3 + ## [105] plyr_1.8.9 ica_1.0-3 + ## [107] zlibbioc_1.44.0 compiler_4.2.2 + ## [109] dqrng_0.3.2 RColorBrewer_1.1-3 + ## [111] fitdistrplus_1.1-11 cli_3.6.2 + ## [113] XVector_0.38.0 urlchecker_1.0.1 + ## [115] listenv_0.9.1 patchwork_1.2.0 + ## [117] pbapply_1.7-2 ps_1.7.5 + ## [119] MASS_7.3-58.2 tidyselect_1.2.1 + ## [121] stringi_1.8.3 highr_0.10 + ## [123] yaml_2.3.8 locfit_1.5-9.9 + ## [125] ggrepel_0.9.5 grid_4.2.2 + ## [127] tools_4.2.2 future.apply_1.11.1 + ## [129] parallel_4.2.2 rstudioapi_0.16.0 + ## [131] gridExtra_2.3 farver_2.1.1 + ## [133] Rtsne_0.17 DropletUtils_1.18.1 + ## [135] rearrr_0.3.4 digest_0.6.35 + ## [137] BiocManager_1.30.22 shiny_1.8.0 + ## [139] Rcpp_1.0.12 GenomicRanges_1.50.2 + ## [141] scuttle_1.8.4 later_1.3.2 + ## [143] RcppAnnoy_0.0.22 httr_1.4.7 + ## [145] sf_1.0-16 colorspace_2.1-0 + ## [147] fs_1.6.3 tensor_1.5 + ## [149] reticulate_1.35.0 IRanges_2.32.0 + ## [151] splines_4.2.2 uwot_0.1.16 + ## [153] spatstat.utils_3.0-4 plotly_4.10.4 + ## [155] sessioninfo_1.2.2 xtable_1.8-4 + ## [157] jsonlite_1.8.8 R6_2.5.1 + ## [159] profvis_0.3.7 pillar_1.9.0 + ## [161] htmltools_0.5.7 mime_0.12 + ## [163] glue_1.7.0 fastmap_1.1.1 + ## [165] BiocParallel_1.32.6 class_7.3-21 + ## [167] codetools_0.2-19 pkgbuild_1.4.2 + ## [169] utf8_1.2.4 lattice_0.21-9 + ## [171] spatstat.sparse_3.0-3 tibble_3.2.1 + ## [173] leiden_0.4.3.1 magick_2.8.3 + ## [175] survival_3.5-7 limma_3.54.2 + ## [177] rmarkdown_2.26 desc_1.4.2 + ## [179] munsell_0.5.1 e1071_1.7-14 + ## [181] rhdf5_2.42.1 GenomeInfoDbData_1.2.9 + ## [183] HDF5Array_1.26.0 reshape2_1.4.4 + ## [185] gtable_0.3.5 + +
diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-11-1.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-11-1.png new file mode 100644 index 0000000..1a0df46 Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-11-1.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-11-2.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-11-2.png new file mode 100644 index 0000000..5f3f34a Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-11-2.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-13-1.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-13-1.png new file mode 100644 index 0000000..84cc66b Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-13-1.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-13-2.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-13-2.png new file mode 100644 index 0000000..626f08e Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-13-2.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-14-1.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-14-1.png new file mode 100644 index 0000000..89a21b5 Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-14-1.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-14-2.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-14-2.png new file mode 100644 index 0000000..ff4a6cc Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-14-2.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-15-1.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-15-1.png new file mode 100644 index 0000000..86dbfce Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-15-1.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-15-2.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-15-2.png new file mode 100644 index 0000000..7e0687b Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-15-2.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-15-3.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-15-3.png new file mode 100644 index 0000000..f545eeb Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-15-3.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-4-1.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-4-1.png new file mode 100644 index 0000000..70bbfae Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-4-1.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-6-1.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-6-1.png new file mode 100644 index 0000000..cddc0e1 Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-6-1.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-8-1.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-8-1.png new file mode 100644 index 0000000..9c34a9c Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-8-1.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-9-1.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-9-1.png new file mode 100644 index 0000000..eb65bf1 Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-9-1.png differ diff --git a/docs/SEraster_files/figure-gfm/unnamed-chunk-9-2.png b/docs/SEraster_files/figure-gfm/unnamed-chunk-9-2.png new file mode 100644 index 0000000..3864e6d Binary files /dev/null and b/docs/SEraster_files/figure-gfm/unnamed-chunk-9-2.png differ