diff --git a/DESCRIPTION b/DESCRIPTION index 279374c..53eb6d6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -24,7 +24,7 @@ LazyData: false Language: en-US StagedInstall: no Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.1 VignetteBuilder: knitr Imports: Matrix, diff --git a/NAMESPACE b/NAMESPACE index 6d02c14..6bb8769 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,13 +3,17 @@ export(get_giotto_obj) export(get_sce_obj) export(get_seurat_obj) +export(get_seurat_v5_obj) export(get_spe_obj) -export(giotto_to_anndata_zarr) +export(giotto_to_spe) export(obj_list) export(sce_to_anndata_zarr) -export(seurat_to_anndata_zarr) +export(seurat_to_sce) +export(seurat_to_spe) export(spe_to_anndata_zarr) export(spe_to_ome_zarr) +import(Seurat) +import(SeuratObject) importFrom(SingleCellExperiment,"reducedDims<-") importFrom(SingleCellExperiment,int_colData) importFrom(SingleCellExperiment,reducedDims) @@ -19,7 +23,6 @@ importFrom(SummarizedExperiment,colData) importFrom(grDevices,as.raster) importFrom(grDevices,col2rgb) importFrom(methods,new) -importFrom(methods,slot) importFrom(stats,rnorm) importFrom(stats,rpois) importFrom(stats,runif) diff --git a/R/bioc_to_zarr.R b/R/bioc_to_zarr.R new file mode 100644 index 0000000..9b271e2 --- /dev/null +++ b/R/bioc_to_zarr.R @@ -0,0 +1,127 @@ +#' Save a SingleCellExperiment to an AnnData-Zarr store. +#' +#' @param sce_obj The object to save. +#' @param out_path A path to the output Zarr store. +#' @return TRUE if the conversion succeeds. +#' +#' @export +#' @examples +#' obj <- get_sce_obj() +#' sce_to_anndata_zarr(obj, out_path = "data/sce.zarr") +#' @importFrom SingleCellExperiment reducedDims reducedDims<- +sce_to_anndata_zarr <- function(sce_obj, out_path, to_dense = TRUE) { + obsm_keys <- names(as.list(reducedDims(sce_obj))) + for(obsm_key in obsm_keys) { + # If there are column names, then the obsm element will be stored as a data.frame, + # but Vitessce can only handle array obsm, so we need to remove any column names. + # Reference: https://github.com/theislab/zellkonverter/blob/e1e95b1/R/SCE2AnnData.R#L159 + colnames(reducedDims(sce_obj)[[obsm_key]]) <- NULL + } + + store <- pizzarr::DirectoryStore$new(out_path) + anndataR::from_SingleCellExperiment(sce_obj, "ZarrAnnData", store = store, to_dense = to_dense) + return(TRUE) +} + +#' Save a SpatialExperiment to an AnnData-Zarr store. +#' +#' @param spe_obj The object to save. +#' @param out_path A path to the output Zarr store. +#' @return TRUE if the conversion succeeds. +#' +#' @export +#' @importFrom SummarizedExperiment colData +#' @importFrom SingleCellExperiment int_colData +#' @importFrom SpatialExperiment colData<- +spe_to_anndata_zarr <- function(spe_obj, out_path) { + internal_col_data <- int_colData(spe_obj) + + colData(spe_obj) <- cbind( + colData(spe_obj), + internal_col_data$spatialCoords, + # spatialData deprecated in 1.5.2 + # internal_col_data$spatialData, + internal_col_data$reducedDims + ) + + success <- sce_to_anndata_zarr(spe_obj, out_path) + return(success) +} + +#' Save an image in a SpatialExperiment to an OME-Zarr store +#' +#' @param spe_obj The object containing the image. +#' @param sample_id The sample_id in the imgData data frame. +#' @param image_id The image_id in the imgData data frame. +#' @param out_path A path to the output Zarr store. +#' @return TRUE if the conversion succeeds. +#' +#' @export +#' @examples +#' obj <- get_spe_obj() +#' spe_to_ome_zarr(obj, "sample1", "image1", "data/spe_image.zarr") +#' @importFrom SpatialExperiment getImg +#' @importFrom grDevices as.raster col2rgb +spe_to_ome_zarr <- function(spe_obj, sample_id, image_id, out_path) { + img_arr <- apply(as.matrix(as.raster(getImg(spe_obj, image_id = image_id, sample_id = sample_id))), c(1, 2), col2rgb) + + # Use basilisk + proc <- basilisk::basiliskStart(py_env) + on.exit(basilisk::basiliskStop(proc)) + + success <- basilisk::basiliskRun(proc, function(img_arr, sample_id, image_id, out_path) { + zarr <- reticulate::import("zarr") + ome_zarr <- reticulate::import("ome_zarr") + + z_root <- zarr$open_group(out_path, mode = "w") + + # Need to copy this here since can't refer to functions in the outside environment. + obj_list <- function(...) { + retval <- stats::setNames(list(), character(0)) + param_list <- list(...) + for(key in names(param_list)) { + retval[[key]] = param_list[[key]] + } + retval + } + + default_window <- obj_list( + start = 0, + min = 0, + max = 255, + end = 255 + ) + + ome_zarr$writer$write_image( + image = img_arr, + group = z_root, + axes = "cyx", + omero = obj_list( + name = image_id, + version = "0.3", + rdefs = obj_list( + + ), + channels = list( + obj_list( + label = "r", + color = "FF0000", + window = default_window + ), + obj_list( + label = "g", + color = "00FF00", + window = default_window + ), + obj_list( + label = "b", + color = "0000FF", + window = default_window + ) + ) + ) + ) + return(TRUE) + }, img_arr = img_arr, sample_id = sample_id, image_id = image_id, out_path = out_path) + return(success) +} diff --git a/R/data_to_zarr.R b/R/data_to_zarr.R deleted file mode 100644 index 8fa9a3c..0000000 --- a/R/data_to_zarr.R +++ /dev/null @@ -1,269 +0,0 @@ - -#' Save a Seurat object to an AnnData-Zarr store. -#' -#' @param seurat_obj The object to save. -#' @param out_path A path to the output Zarr store. -#' @param assay The name of the assay to save. -#' @return TRUE if the conversion succeeds. -#' -#' @export -#' @examples -#' obj <- get_seurat_obj() -#' seurat_to_anndata_zarr(obj, out_path = "data/seurat.zarr", assay = "RNA") -seurat_to_anndata_zarr <- function(seurat_obj, out_path, assay) { - if(!requireNamespace("SeuratDisk", quietly = TRUE)) { - stop("Install 'SeuratDisk' to enable conversion of Seurat objects to AnnData objects.") - } - - h5seurat_path <- paste0(out_path, ".h5Seurat") - h5ad_path <- paste0(out_path, ".h5ad") - - # Convert factor columns to string/numeric. - seurat_obj@meta.data <- varhandle::unfactor(seurat_obj@meta.data) - - SeuratDisk::SaveH5Seurat(seurat_obj, filename = h5seurat_path, overwrite = TRUE) - SeuratDisk::Convert(h5seurat_path, dest = "h5ad", overwrite = TRUE, assay = assay) - - # Use basilisk - proc <- basilisk::basiliskStart(py_env) - on.exit(basilisk::basiliskStop(proc)) - - success <- basilisk::basiliskRun(proc, function(h5ad_path, out_path) { - anndata <- reticulate::import("anndata") - zarr <- reticulate::import("zarr") - - adata <- anndata$read_h5ad(h5ad_path) - - cleanup_colnames <- function(df) { - # Reference: https://github.com/theislab/scvelo/issues/255#issuecomment-739995301 - new_colnames <- colnames(df) - new_colnames[new_colnames == "_index"] <- "features" - return(new_colnames) - } - - noop <- function(cond) { } - - tryCatch({ - colnames(adata$var) <- cleanup_colnames(adata$var) - }, error = noop) - - # Reconstruct, omitting raw and uns. - adata <- anndata$AnnData( - X = adata$X, - obs = as.data.frame(adata$obs), - var = as.data.frame(adata$var), - obsm = adata$obsm, - varm = adata$varm - ) - - adata$write_zarr(out_path) - - return(TRUE) - }, h5ad_path = h5ad_path, out_path = out_path) - return(success) -} - -#' Save a SingleCellExperiment to an AnnData-Zarr store. -#' -#' @param sce_obj The object to save. -#' @param out_path A path to the output Zarr store. -#' @return TRUE if the conversion succeeds. -#' -#' @export -#' @examples -#' obj <- get_sce_obj() -#' sce_to_anndata_zarr(obj, out_path = "data/sce.zarr") -#' @importFrom SingleCellExperiment reducedDims reducedDims<- -sce_to_anndata_zarr <- function(sce_obj, out_path) { - obsm_keys <- names(as.list(reducedDims(sce_obj))) - for(obsm_key in obsm_keys) { - # If there are column names, then the obsm element will be stored as a data.frame, - # but Vitessce can only handle array obsm, so we need to remove any column names. - # Reference: https://github.com/theislab/zellkonverter/blob/e1e95b1/R/SCE2AnnData.R#L159 - colnames(reducedDims(sce_obj)[[obsm_key]]) <- NULL - } - - # Use basilisk - proc <- basilisk::basiliskStart(py_env) - on.exit(basilisk::basiliskStop(proc)) - - success <- basilisk::basiliskRun(proc, function(sce_obj, out_path) { - anndata <- reticulate::import("anndata") - zarr <- reticulate::import("zarr") - - adata <- zellkonverter::SCE2AnnData(sce_obj) - adata$write_zarr(out_path) - return(TRUE) - }, sce_obj = sce_obj, out_path = out_path) - return(success) -} - -#' Save a SpatialExperiment to an AnnData-Zarr store. -#' -#' @param spe_obj The object to save. -#' @param out_path A path to the output Zarr store. -#' @return TRUE if the conversion succeeds. -#' -#' @export -#' @importFrom SummarizedExperiment colData -#' @importFrom SingleCellExperiment int_colData -#' @importFrom SpatialExperiment colData<- -spe_to_anndata_zarr <- function(spe_obj, out_path) { - internal_col_data <- int_colData(spe_obj) - - colData(spe_obj) <- cbind( - colData(spe_obj), - internal_col_data$spatialCoords, - # spatialData deprecated in 1.5.2 - # internal_col_data$spatialData, - internal_col_data$reducedDims - ) - - success <- sce_to_anndata_zarr(spe_obj, out_path) - return(success) -} - -#' Save an image in a SpatialExperiment to an OME-Zarr store -#' -#' @param spe_obj The object containing the image. -#' @param sample_id The sample_id in the imgData data frame. -#' @param image_id The image_id in the imgData data frame. -#' @param out_path A path to the output Zarr store. -#' @return TRUE if the conversion succeeds. -#' -#' @export -#' @examples -#' obj <- get_spe_obj() -#' spe_to_ome_zarr(obj, "sample1", "image1", "data/spe_image.zarr") -#' @importFrom SpatialExperiment getImg -#' @importFrom grDevices as.raster col2rgb -spe_to_ome_zarr <- function(spe_obj, sample_id, image_id, out_path) { - img_arr <- apply(as.matrix(as.raster(getImg(spe_obj, image_id = image_id, sample_id = sample_id))), c(1, 2), col2rgb) - - # Use basilisk - proc <- basilisk::basiliskStart(py_env) - on.exit(basilisk::basiliskStop(proc)) - - success <- basilisk::basiliskRun(proc, function(img_arr, sample_id, image_id, out_path) { - zarr <- reticulate::import("zarr") - ome_zarr <- reticulate::import("ome_zarr") - - z_root <- zarr$open_group(out_path, mode = "w") - - # Need to copy this here since can't refer to functions in the outside environment. - obj_list <- function(...) { - retval <- stats::setNames(list(), character(0)) - param_list <- list(...) - for(key in names(param_list)) { - retval[[key]] = param_list[[key]] - } - retval - } - - default_window <- obj_list( - start = 0, - min = 0, - max = 255, - end = 255 - ) - - ome_zarr$writer$write_image( - image = img_arr, - group = z_root, - axes = "cyx", - omero = obj_list( - name = image_id, - version = "0.3", - rdefs = obj_list( - - ), - channels = list( - obj_list( - label = "r", - color = "FF0000", - window = default_window - ), - obj_list( - label = "g", - color = "00FF00", - window = default_window - ), - obj_list( - label = "b", - color = "0000FF", - window = default_window - ) - ) - ) - ) - return(TRUE) - }, img_arr = img_arr, sample_id = sample_id, image_id = image_id, out_path = out_path) - return(success) -} - -#' Save a Giotto object to an AnnData-Zarr store -#' -#' @param giotto_obj The object to save. -#' @param out_path A path to the output Zarr store. -#' @param X_slot The name of the slot in the Giotto object to use for adata.X -#' @return TRUE if the conversion succeeds. -#' -#' @export -#' @examples -#' obj <- get_giotto_obj() -#' giotto_to_anndata_zarr(obj, "data/giotto.zarr") -#' @importFrom methods slot -giotto_to_anndata_zarr <- function(giotto_obj, out_path, X_slot = "raw_exprs") { - - # Use basilisk - proc <- basilisk::basiliskStart(py_env) - on.exit(basilisk::basiliskStop(proc)) - - success <- basilisk::basiliskRun(proc, function(giotto_obj, out_path, X_slot) { - anndata <- reticulate::import("anndata") - zarr <- reticulate::import("zarr") - - # Reference: https://github.com/theislab/zellkonverter/blob/master/R/SCE2AnnData.R#L237 - make_numpy_friendly <- function(x, transpose = TRUE) { - if (transpose) { - x <- Matrix::t(x) - } - if (DelayedArray::is_sparse(x)) { - methods::as(x, "dgCMatrix") - } else { - as.matrix(x) - } - } - - X <- make_numpy_friendly(slot(giotto_obj, X_slot)) - obs <- slot(giotto_obj, "cell_metadata") - var <- slot(giotto_obj, "gene_metadata") - - adata <- anndata$AnnData(X = X, obs = obs, var = var) - - obsm <- list() - - if(!is.null(slot(giotto_obj, "spatial_locs"))) { - spatial_locs <- slot(giotto_obj, "spatial_locs") - obsm[['spatial']] <- t(as.matrix(spatial_locs[, c("sdimx", "sdimy")])) - } - - if(!is.null(slot(giotto_obj, "dimension_reduction"))) { - dim_reducs <- slot(giotto_obj, "dimension_reduction")$cells - for(dim_reduc_name in names(dim_reducs)) { - dim_reduc_coords <- dim_reducs[[dim_reduc_name]][[dim_reduc_name]]$coordinates - obsm[[dim_reduc_name]] <- t(as.matrix(dim_reduc_coords)) - } - } - - if(length(obsm) > 0) { - # TODO make_numpy_friendly is outside scope - obsm <- lapply(obsm, make_numpy_friendly) - adata$obsm <- obsm - } - - adata$write_zarr(out_path) - return(TRUE) - }, giotto_obj = giotto_obj, out_path = out_path, X_slot = X_slot) - return(success) -} diff --git a/R/giotto_to_bioc.R b/R/giotto_to_bioc.R new file mode 100644 index 0000000..9be701a --- /dev/null +++ b/R/giotto_to_bioc.R @@ -0,0 +1,4 @@ +#' @export +giotto_to_spe <- function(gio) { + return(GiottoClass::giottoToSpatialExperiment(gio)) +} \ No newline at end of file diff --git a/R/mock_objects.R b/R/mock_objects.R index 866435c..7d33b7a 100644 --- a/R/mock_objects.R +++ b/R/mock_objects.R @@ -1,3 +1,17 @@ +#' Get a Seurat v5 object for tests and examples. +#' @return The object. +#' @keywords internal +#' @export +#' @examples +#' obj <- get_seurat_v5_obj() +#' @import Seurat +#' @import SeuratObject +get_seurat_v5_obj <- function() { + SeuratData::InstallData("pbmcsca") + obj <- SeuratData::LoadData("pbmcsca") + return(obj) +} + #' Create a mock Seurat object for tests and examples. #' @return The object. #' @keywords internal diff --git a/R/seurat_to_bioc.R b/R/seurat_to_bioc.R new file mode 100644 index 0000000..27e034c --- /dev/null +++ b/R/seurat_to_bioc.R @@ -0,0 +1,64 @@ +#' @export +seurat_to_sce <- function(seu) { + sce <- Seurat::as.SingleCellExperiment(seu) + return(sce) +} + +#' @export +seurat_to_spe <- function(seu, sample_id = NA, img_id = NA) { + # Reference: https://github.com/drighelli/SpatialExperiment/issues/115 + sce <- seurat_to_sce(seu) + + if(is.na(sample_id)) { + sample_id <- "sample0" + } + + if(length(seu@images) >= 1) { + + if(is.na(img_id)) { + img_id <- names(seu@images)[1] + } + + ## Extract spatial coordinates + if("VisiumV2" %in% class(seu@images[[img_id]])) { + spatialCoords <- as.matrix( + seu@images[[img_id]]@boundaries$centroids@coords + ) + } else { + spatialCoords <- as.matrix( + seu@images[[img_id]]@coordinates[, c("imagecol", "imagerow")] + ) + } + + + ## Extract and process image data + img <- SpatialExperiment::SpatialImage( + x = as.raster(seu@images[[img_id]]@image) + ) + + imgData <- S4Vectors::DataFrame( + sample_id = sample_id, + image_id = img_id, + data = I(list(img)), + scaleFactor = seu@images[[img_id]]@scale.factors$lowres + ) + } + + # Convert to SpatialExperiment + spe <- SpatialExperiment::SpatialExperiment( + assays = SummarizedExperiment::assays(sce), + rowData = SingleCellExperiment::rowData(sce), + colData = SingleCellExperiment::colData(sce), + metadata = S4Vectors::metadata(sce), + reducedDims = SingleCellExperiment::reducedDims(sce), + altExps = SingleCellExperiment::altExps(sce), + sample_id = sample_id, + spatialCoords = spatialCoords, + imgData = imgData + ) + # indicate all spots are on the tissue + spe$in_tissue <- 1 + spe$sample_id <- sample_id + # Return Spatial Experiment object + return(spe) +} \ No newline at end of file diff --git a/README.md b/README.md index a8d0986..5e4b6ef 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ Installation requires R 4.0.0 or greater. ```r install.packages("devtools") +devtools::install_github("keller-mark/pizzarr") +devtools::install_github("keller-mark/anndataR", ref="keller-mark/zarr-and-rarr") + devtools::install_github("vitessce/vitessceAnalysisR") ``` diff --git a/man/get_seurat_v5_obj.Rd b/man/get_seurat_v5_obj.Rd new file mode 100644 index 0000000..9b1e337 --- /dev/null +++ b/man/get_seurat_v5_obj.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mock_objects.R +\name{get_seurat_v5_obj} +\alias{get_seurat_v5_obj} +\title{Get a Seurat v5 object for tests and examples.} +\usage{ +get_seurat_v5_obj() +} +\value{ +The object. +} +\description{ +Get a Seurat v5 object for tests and examples. +} +\examples{ +obj <- get_seurat_v5_obj() +} +\keyword{internal} diff --git a/man/giotto_to_anndata_zarr.Rd b/man/giotto_to_anndata_zarr.Rd deleted file mode 100644 index 9f4a786..0000000 --- a/man/giotto_to_anndata_zarr.Rd +++ /dev/null @@ -1,25 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data_to_zarr.R -\name{giotto_to_anndata_zarr} -\alias{giotto_to_anndata_zarr} -\title{Save a Giotto object to an AnnData-Zarr store} -\usage{ -giotto_to_anndata_zarr(giotto_obj, out_path, X_slot = "raw_exprs") -} -\arguments{ -\item{giotto_obj}{The object to save.} - -\item{out_path}{A path to the output Zarr store.} - -\item{X_slot}{The name of the slot in the Giotto object to use for adata.X} -} -\value{ -TRUE if the conversion succeeds. -} -\description{ -Save a Giotto object to an AnnData-Zarr store -} -\examples{ -obj <- get_giotto_obj() -giotto_to_anndata_zarr(obj, "data/giotto.zarr") -} diff --git a/man/sce_to_anndata_zarr.Rd b/man/sce_to_anndata_zarr.Rd index 372681b..efe07b1 100644 --- a/man/sce_to_anndata_zarr.Rd +++ b/man/sce_to_anndata_zarr.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data_to_zarr.R +% Please edit documentation in R/bioc_to_zarr.R \name{sce_to_anndata_zarr} \alias{sce_to_anndata_zarr} \title{Save a SingleCellExperiment to an AnnData-Zarr store.} diff --git a/man/seurat_to_anndata_zarr.Rd b/man/seurat_to_anndata_zarr.Rd deleted file mode 100644 index 76633ff..0000000 --- a/man/seurat_to_anndata_zarr.Rd +++ /dev/null @@ -1,25 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data_to_zarr.R -\name{seurat_to_anndata_zarr} -\alias{seurat_to_anndata_zarr} -\title{Save a Seurat object to an AnnData-Zarr store.} -\usage{ -seurat_to_anndata_zarr(seurat_obj, out_path, assay) -} -\arguments{ -\item{seurat_obj}{The object to save.} - -\item{out_path}{A path to the output Zarr store.} - -\item{assay}{The name of the assay to save.} -} -\value{ -TRUE if the conversion succeeds. -} -\description{ -Save a Seurat object to an AnnData-Zarr store. -} -\examples{ -obj <- get_seurat_obj() -seurat_to_anndata_zarr(obj, out_path = "data/seurat.zarr", assay = "RNA") -} diff --git a/man/spe_to_anndata_zarr.Rd b/man/spe_to_anndata_zarr.Rd index 59596d8..0d340ef 100644 --- a/man/spe_to_anndata_zarr.Rd +++ b/man/spe_to_anndata_zarr.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data_to_zarr.R +% Please edit documentation in R/bioc_to_zarr.R \name{spe_to_anndata_zarr} \alias{spe_to_anndata_zarr} \title{Save a SpatialExperiment to an AnnData-Zarr store.} diff --git a/man/spe_to_ome_zarr.Rd b/man/spe_to_ome_zarr.Rd index bdbe5cb..d8cde20 100644 --- a/man/spe_to_ome_zarr.Rd +++ b/man/spe_to_ome_zarr.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data_to_zarr.R +% Please edit documentation in R/bioc_to_zarr.R \name{spe_to_ome_zarr} \alias{spe_to_ome_zarr} \title{Save an image in a SpatialExperiment to an OME-Zarr store}