From 8966e08596bb22451a3639cb7cf0764cc5febe1d Mon Sep 17 00:00:00 2001 From: AustinHartman Date: Mon, 5 Dec 2022 17:41:52 -0500 Subject: [PATCH 01/53] update readme package version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9fa45122e..427ab3a20 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![CRAN Version](https://www.r-pkg.org/badges/version/Seurat)](https://cran.r-project.org/package=Seurat) [![CRAN Downloads](https://cranlogs.r-pkg.org/badges/Seurat)](https://cran.r-project.org/package=Seurat) -# Seurat v4.2.1 +# Seurat v4.3.0 Seurat is an R toolkit for single cell genomics, developed and maintained by the Satija Lab at NYGC. From 723b0666e2e1398653738f8567c7d955d0acbec7 Mon Sep 17 00:00:00 2001 From: timoast <4591688+timoast@users.noreply.github.com> Date: Wed, 7 Dec 2022 17:34:36 -0500 Subject: [PATCH 02/53] Fix sce object conversion; #6692 --- R/objects.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/objects.R b/R/objects.R index 39dd56499..f1dc39d56 100644 --- a/R/objects.R +++ b/R/objects.R @@ -1138,6 +1138,8 @@ as.Seurat.SingleCellExperiment <- function( embeddings <- as.matrix(x = SingleCellExperiment::reducedDim(x = x, type = dr)) if (is.null(x = rownames(x = embeddings))) { rownames(x = embeddings) <- cell.names + } else { + rownames(x = embeddings) <- make.unique(names = rownames(x = embeddings)) } if (isTRUE(x = !grepl('_$', gsub(pattern = "[[:digit:]]", From 04fdc7038ef7f8dd4369b0df1bb08eb40a9dd707 Mon Sep 17 00:00:00 2001 From: Paul Hoffman Date: Thu, 8 Dec 2022 10:20:07 -0500 Subject: [PATCH 03/53] Update changelog Bump develop version --- DESCRIPTION | 4 ++-- NEWS.md | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 16535fd76..188b93b4b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: Seurat -Version: 4.3.0 -Date: 2022-11-18 +Version: 4.3.0.9001 +Date: 2022-12-08 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. Authors@R: c( diff --git a/NEWS.md b/NEWS.md index a2d554b69..75ed99993 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# Unreleased +## Changes +- Fix bug in `as.Seurat.SingleCellExperiment()` ([#6692](https://github.com/satijalab/seurat/issues/6692)) + # Seurat 4.3.0 (2022-11-18) ## Added From d97a5f6f1288026e01afa9e6d1858e6aec61c827 Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Fri, 16 Dec 2022 14:36:12 -0500 Subject: [PATCH 04/53] Fix FoldChange.Seurat to handle appropriate normalization method; Add checks to PrepSCTFindmarkers for median UMI calculation --- R/differential_expression.R | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/R/differential_expression.R b/R/differential_expression.R index c96e3fea4..6b66b07c3 100644 --- a/R/differential_expression.R +++ b/R/differential_expression.R @@ -1219,6 +1219,24 @@ FoldChange.Seurat <- function( ident.2 = ident.2, cellnames.use = cellnames.use ) + # check normalization method + norm.command <- paste0("NormalizeData.", assay) + norm.method <- if (norm.command %in% Command(object = object) && is.null(x = reduction)) { + Command( + object = object, + command = norm.command, + value = "normalization.method" + ) + } else if (length(x = intersect(x = c("FindIntegrationAnchors", "FindTransferAnchors"), y = Command(object = object)))) { + command <- intersect(x = c("FindIntegrationAnchors", "FindTransferAnchors"), y = Command(object = object))[1] + Command( + object = object, + command = command, + value = "normalization.method" + ) + } else { + NULL + } fc.results <- FoldChange( object = data.use, cells.1 = cells$cells.1, @@ -1228,7 +1246,8 @@ FoldChange.Seurat <- function( pseudocount.use = pseudocount.use, mean.fxn = mean.fxn, base = base, - fc.name = fc.name + fc.name = fc.name, + norm.method = norm.method ) return(fc.results) } @@ -2071,8 +2090,8 @@ PrepSCTFindMarkers <- function(object, assay = "SCT", verbose = TRUE) { } model_median_umis <- SCTResults(object = object[[assay]], slot = "median_umi") - min_median_umi <- min(unlist(x = observed_median_umis)) - if (all(unlist(x = model_median_umis) == min_median_umi)){ + min_median_umi <- min(unlist(x = observed_median_umis), na.rm = TRUE) + if (all(unlist(x = model_median_umis) > min_median_umi)){ if (verbose){ message("Minimum UMI unchanged. Skipping re-correction.") } From a89b6358491c88a9bbb57f638df6963c1d4408ca Mon Sep 17 00:00:00 2001 From: samuel-marsh Date: Mon, 30 Jan 2023 09:35:06 -0500 Subject: [PATCH 05/53] add keep.scale SpatialPlot --- R/visualization.R | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/R/visualization.R b/R/visualization.R index 9af3a6e02..ccf664cfc 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -2948,6 +2948,12 @@ ISpatialFeaturePlot <- function( #' entire background image. #' @param slot If plotting a feature, which data slot to pull from (counts, #' data, or scale.data) +#' @param keep.scale How to handle the color scale across multiple plots. Options are: +#' \itemize{ +#' \item{"feature" (default; by row/feature scaling):}{ The plots for each individual feature are scaled to the maximum expression of the feature across the conditions provided to 'split.by'.} +#' \item{"all" (universal scaling):}{ The plots for all features and conditions are scaled to the maximum expression value for the feature with the highest overall expression.} +#' \item{NULL (no scaling):}{ Each individual plot is scaled to the maximum expression value of the feature in the condition provided to 'split.by'. Be aware setting NULL will result in color scales that are not comparable between plots.} +#' } #' @param min.cutoff,max.cutoff Vector of minimum and maximum cutoff #' values for each feature, may specify quantile in the form of 'q##' where '##' #' is the quantile (eg, 'q1', 'q10') @@ -3011,6 +3017,7 @@ SpatialPlot <- function( image.alpha = 1, crop = TRUE, slot = 'data', + keep.scale = "feature", min.cutoff = NA, max.cutoff = NA, cells.highlight = NULL, @@ -3050,6 +3057,12 @@ SpatialPlot <- function( if (length(x = images) < 1) { stop("Could not find any spatial image information") } + + # Check keep.scale param for valid entries + if (!(is.null(x = keep.scale)) && !(keep.scale %in% c("feature", "all"))) { + stop("`keep.scale` must be set to either `feature`, `all`, or NULL") + } + if (is.null(x = features)) { if (interactive) { return(ISpatialDimPlot( @@ -3182,6 +3195,13 @@ SpatialPlot <- function( mode = "list", length = length(x = features) * ncols ) + + # Get max across all features + if (!(is.null(x = keep.scale)) && keep.scale == "all") { + max.feature.value <- max(apply(data, 2, function(x) max(x, na.rm = TRUE))) + } + + for (i in 1:ncols) { plot.idx <- i image.idx <- ifelse(test = facet.highlight, yes = 1, no = i) @@ -3198,6 +3218,12 @@ SpatialPlot <- function( cols <- hue_pal()(n = length(x = levels(x = data[, features[j]]))) names(x = cols) <- levels(x = data[, features[j]]) } + + # Get feature max for individual feature + if (!(is.null(x = keep.scale)) && keep.scale == "feature") { + max.feature.value <- max(data[, features[j]]) + } + plot <- SingleSpatialPlot( data = cbind( coordinates, @@ -3268,6 +3294,12 @@ SpatialPlot <- function( theme(plot.title = element_text(hjust = 0.5)) + NoLegend() } + + # Plot multiple images depending on keep.scale + if (!(is.null(x = keep.scale))) { + plot <- suppressMessages(plot & scale_fill_gradientn(colors = SpatialColors(n = 100), limits = c(NA, max.feature.value))) + } + plots[[plot.idx]] <- plot plot.idx <- plot.idx + ncols if (cols.unset) { From 3589ce9ea2f579a4c14d15daa8f39b2bcbc51f90 Mon Sep 17 00:00:00 2001 From: samuel-marsh Date: Mon, 30 Jan 2023 09:36:12 -0500 Subject: [PATCH 06/53] update helper function SpatialFeaturePlot --- R/convenience.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/convenience.R b/R/convenience.R index aee4b254a..94d397d78 100644 --- a/R/convenience.R +++ b/R/convenience.R @@ -94,6 +94,7 @@ SpatialFeaturePlot <- function( images = NULL, crop = TRUE, slot = 'data', + keep.scale = "feature", min.cutoff = NA, max.cutoff = NA, ncol = NULL, @@ -111,6 +112,7 @@ SpatialFeaturePlot <- function( images = images, crop = crop, slot = slot, + keep.scale = keep.scale, min.cutoff = min.cutoff, max.cutoff = max.cutoff, ncol = ncol, From e1620ee9a4788f19bd8547b86a8c6845325fe520 Mon Sep 17 00:00:00 2001 From: Jordan Sicherman Date: Mon, 27 Feb 2023 12:07:14 -0800 Subject: [PATCH 07/53] Allow loading renamed feature --- R/convenience.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/convenience.R b/R/convenience.R index e2848908b..7700a064d 100644 --- a/R/convenience.R +++ b/R/convenience.R @@ -201,7 +201,10 @@ LoadXenium <- function(data.dir, fov = 'fov', assay = 'Xenium') { ) xenium.obj <- CreateSeuratObject(counts = data$matrix[["Gene Expression"]], assay = assay) - xenium.obj[["BlankCodeword"]] <- CreateAssayObject(counts = data$matrix[["Blank Codeword"]]) + if("Blank Codeword" %in% names(data$matrix)) + xenium.obj[["BlankCodeword"]] <- CreateAssayObject(counts = data$matrix[["Blank Codeword"]]) + else + xenium.obj[["BlankCodeword"]] <- CreateAssayObject(counts = data$matrix[["Unassigned Codeword"]]) xenium.obj[["ControlCodeword"]] <- CreateAssayObject(counts = data$matrix[["Negative Control Codeword"]]) xenium.obj[["ControlProbe"]] <- CreateAssayObject(counts = data$matrix[["Negative Control Probe"]]) From 9f6cc3facf0161b8da7255c826650e149be6c1f3 Mon Sep 17 00:00:00 2001 From: AustinHartman Date: Tue, 28 Feb 2023 09:58:09 -0500 Subject: [PATCH 08/53] add Xenium section to vignette --- DESCRIPTION | 4 +- vignettes/spatial_vignette_2.Rmd | 87 +++++++++++++++++++++++++++++++- vignettes/vignettes.yaml | 4 +- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 188b93b4b..d8317cf63 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: Seurat -Version: 4.3.0.9001 -Date: 2022-12-08 +Version: 4.3.0.9002 +Date: 2023-02-01 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. Authors@R: c( diff --git a/vignettes/spatial_vignette_2.Rmd b/vignettes/spatial_vignette_2.Rmd index 2e315bccf..90976ede8 100644 --- a/vignettes/spatial_vignette_2.Rmd +++ b/vignettes/spatial_vignette_2.Rmd @@ -37,14 +37,13 @@ In this vignette, we introduce a Seurat extension to analyze new types of spatia We update the Seurat infrastructure to enable the analysis, visualization, and exploration of these exciting datasets. In this vignette, we focus on three datasets produced by different multiplexed imaging technologies, each of which is publicly available. We will be adding support for additional imaging-based technologies in the coming months. * Vizgen MERSCOPE (Mouse Brain) +* 10x Genomics Xenium In Situ (Mouse Brain) * Nanostring CosMx Spatial Molecular Imager (FFPE Human Lung) * Akoya CODEX (Human Lymph Node) First, we install the updated versions of Seurat and SeuratObject that support this infrastructure, as well as other packages necessary for this vignette. ```{r init, message=FALSE, warning=FALSE} -library(remotes) -remotes::install_github("satijalab/seurat", "feat/imaging") library(Seurat) library(future) plan("multisession", workers = 10) @@ -199,6 +198,90 @@ We can visualize individual molecules plotted at higher resolution after zooming ImageDimPlot(vizgen.obj, fov = "hippo", molecules = rownames(markers.14)[1:4], cols = "polychrome", mols.size = 1, alpha = 0.5, mols.cols = c("red", "blue", "yellow", "green")) ``` +# Mouse Brain: 10x Genomics Xenium In Situ + +In this section we'll analyze data produced by the Xenium platform. The vignette demonstrates how to load the per-transcript location data, cell x gene matrix, cell segmentation, and cell centroid information available in the Xenium outputs. The resulting Seurat object will contain the gene expression profile of each cell, the centroid and boundary of each cell, and the location of each individual detected transcript. The per-cell gene expression profiles are similar to standard single-cell RNA-seq and can be analyzed using the same tools. + +This uses the `Tiny subset` dataset from 10x Genomics 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) which can be downloaded as described below. These analysis steps are also compatible with the larger `Full coronal section`, but will take longer to execute. + +```{bash, eval=FALSE} +wget https://cf.10xgenomics.com/samples/xenium/1.0.2/Xenium_V1_FF_Mouse_Brain_Coronal_Subset_CTX_HP/Xenium_V1_FF_Mouse_Brain_Coronal_Subset_CTX_HP_outs.zip +unzip Xenium_V1_FF_Mouse_Brain_Coronal_Subset_CTX_HP_outs.zip +``` + +First we read in the dataset and create a Seurat object. Provide the path to the data folder for a Xenium run as the input path. The RNA data is stored in the `Xenium` assay of the Seurat object. This step should take about a minute. + +```{r load.xenium, results='hide'} +path <- "/brahms/hartmana/spatial_sensitivity_comparison/10x_xenium/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) +``` + +Spatial information is loaded into slots of the Seurat object, labelled by the name of "field of view" (FOV) being loaded. Initially all the data is loaded into the FOV named `fov`. Later, we will make a cropped FOV that zooms into a region of interest. + +Standard QC plots provided by Seurat are available via the `Xenium` assay. Here are violin plots of genes per cell (`nFeature_Xenium`) and transcript counts per cell (`nCount_Xenium`) +```{r vlnplot.xenium} +VlnPlot(xenium.obj, features = c("nFeature_Xenium", "nCount_Xenium"), ncol = 2, pt.size = 0) +``` + +Next, we plot the positions of the pan-inhibitory neuron marker Gad1, inhibitory neuron sub-type markers Pvalb, and Sst, and astrocyte marker Gfap on the tissue using `ImageDimPlot()`. +```{r p2.xenium, fig.width=10, fig.height=8} +ImageDimPlot(xenium.obj, fov = "fov", molecules = c("Gad1", "Sst", "Pvalb", "Gfap"), nmols = 20000) +``` + +```{r save.img, include=FALSE} +library(ggplot2) +plot <- ImageDimPlot(xenium.obj, fov = "fov", molecules = c("Gad1", "Gfap"), nmols = 40000, alpha=0.01, dark.background = F, mols.alpha = 0.6) + coord_flip() + scale_x_reverse() + NoLegend() +ggsave(filename = "../output/images/spatial_vignette_2.jpg", height = 7, width = 12, plot = plot, quality = 50) +``` + +Here we visualize the expression level of some key layer marker genes at the per-cell level using `ImageFeaturePlot()` which is analogous to the `FeaturePlot()` function for visualizing expression on a 2D embedding. We manually adjust the `max.cutoff` for each gene to roughly the 90th percentile (which can be specified with `max.cutoff='q90'`) of it's count distribution to improve contrast. +```{r mat.xenium, message=FALSE, warning=FALSE, fig.width=12, fig.height=12} +ImageFeaturePlot(xenium.obj, features = c("Cux2", "Rorb", "Bcl11b", "Foxp2"), max.cutoff = c(25, 35, 12, 10), size = 0.75, cols = c("white", "red")) +``` + +We can zoom in on a chosen area with the `Crop()` function. Once zoomed-in, we can visualize cell segmentation boundaries along with individual molecules. +```{r cropping.xenium, message=FALSE, warning=FALSE, fig.width=10, fig.height=8} +cropped.coords <- Crop(xenium.obj[["fov"]], x = c(1200, 2900), y = c(3750, 4550), coords = "plot") + +xenium.obj[["zoom"]] <- cropped.coords + +# visualize cropped area with cell segmentations & selected molecules +DefaultBoundary(xenium.obj[["zoom"]]) <- "segmentation" +ImageDimPlot(xenium.obj, fov = "zoom", + axes = TRUE, border.color = "white", border.size = 0.1, + cols = "polychrome", coord.fixed = FALSE, + molecules = c("Gad1", "Sst", "Npy2r", "Pvalb", "Nrn1"), nmols = 10000) +``` + +Next, we use SCTransform for normalization followed by standard dimensionality reduction and clustering. This step takes about 5 minutes from start to finish. +```{r unsupervised.xenium, results='hide'} +xenium.obj <- SCTransform(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) +``` + +We can then visualize the results of the clustering by coloring each cell according to its cluster either in UMAP space with `DimPlot()` or overlaid on the image with `ImageDimPlot()`. +```{r umap.xenium, fig.width=10, fig.height=7} +DimPlot(xenium.obj) +``` + +We can visualize the expression level of the markers we looked at earlier on the UMAP coordinates. +```{r features.xenium, fig.width=8, fig.height=10} +FeaturePlot(xenium.obj, features = c("Cux2", "Bcl11b", "Foxp2", "Gad1", "Sst", "Gfap")) +``` + +We can now use `ImageDimPlot()` to color the cell positions colored by the cluster labels determined in the previous step. +```{r clusters.xenium, fig.width=13, fig.height=13} +ImageDimPlot(xenium.obj, cols = "polychrome", size = 0.75) +``` + # Human Lung: Nanostring CosMx Spatial Molecular Imager This dataset was produced using Nanostring CosMx Spatial Molecular Imager (SMI). The CosMX SMI performs multiplexed single molecule profiling, can profile both RNA and protein targets, and can be applied directly to FFPE tissues. The dataset represents 8 FFPE samples taken from 5 non-small-cell lung cancer (NSCLC) tissues, and is available for [public download](https://www.nanostring.com/products/cosmx-spatial-molecular-imager/ffpe-dataset/). The gene panel consists of 960 transcripts. diff --git a/vignettes/vignettes.yaml b/vignettes/vignettes.yaml index 6316d70bf..a45dfde70 100644 --- a/vignettes/vignettes.yaml +++ b/vignettes/vignettes.yaml @@ -21,8 +21,8 @@ - title: Analysis of spatial datasets (Imaging-based) name: spatial_vignette_2 summary: | - Learn to explore spatially-resolved data from multiplexed imaging technologies, including MERFISH, Nanostring SMI, and CODEX. - image: spatial_vignette_2.png + Learn to explore spatially-resolved data from multiplexed imaging technologies, including MERFISH, Xenium, CosMx SMI, and CODEX. + image: spatial_vignette_2.jpg - category: Data Integration vignettes: From 070e6550baae1aea38d62e124c7d3fba5152bc48 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Mon, 10 Apr 2023 10:21:22 -0700 Subject: [PATCH 09/53] spaceranger 2.1 update --- R/preprocessing.R | 48 ++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/R/preprocessing.R b/R/preprocessing.R index 7ef77dcf5..33130738f 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -494,30 +494,43 @@ GetResidual <- function( #' } #' Load10X_Spatial <- function( - data.dir, - filename = 'filtered_feature_bc_matrix.h5', - assay = 'Spatial', - slice = 'slice1', - filter.matrix = TRUE, - to.upper = FALSE, - image = NULL, - ... + data.dir, + filename = 'filtered_feature_bc_matrix.h5', + assay = 'Spatial', + slice = 'slice1', + filter.matrix = TRUE, + to.upper = FALSE, + image = NULL, + ... ) { if (length(x = data.dir) > 1) { - warning("'Load10X_Spatial' accepts only one 'data.dir'", immediate. = TRUE) + warning("'Load10X_Spatial' accepts only one 'data.dir'", + immediate. = TRUE) data.dir <- data.dir[1] } - data <- Read10X_h5(filename = file.path(data.dir, filename), ...) + data <- Read10X_h5(filename = file.path(data.dir, filename), + ...) if (to.upper) { - rownames(x = data) <- toupper(x = rownames(x = data)) + data <- imap(data, ~{ + rownames(.x) <- toupper(rownames(.x)) + .x + }) + } + if (is.list(data) & "Antibody Capture" %in% names(data)) { + matrix_gex <- data$`Gene Expression` + matrix_protein <- data$`Antibody Capture` + object <- CreateSeuratObject(counts = matrix_gex, assay = assay) + object_protein <- CreateAssayObject(counts = matrix_protein) + object[["Protein"]] <- object_protein + } + else { + object <- CreateSeuratObject(counts = data, assay = assay) } - object <- CreateSeuratObject(counts = data, assay = assay) if (is.null(x = image)) { - image <- Read10X_Image( - image.dir = file.path(data.dir, 'spatial'), - filter.matrix = filter.matrix - ) - } else { + image <- Read10X_Image(image.dir = file.path(data.dir, + "spatial"), filter.matrix = filter.matrix) + } + else { if (!inherits(x = image, what = "VisiumV1")) stop("Image must be an object of class 'VisiumV1'.") } @@ -527,6 +540,7 @@ Load10X_Spatial <- function( return(object) } + #' Load STARmap data #' #' @param data.dir location of data directory that contains the counts matrix, From 0a1a76474e0cf719945e39efc1a494013bf1f1a8 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Mon, 10 Apr 2023 10:37:30 -0700 Subject: [PATCH 10/53] formatting --- R/preprocessing.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/preprocessing.R b/R/preprocessing.R index 33130738f..97ce550da 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -527,8 +527,8 @@ Load10X_Spatial <- function( object <- CreateSeuratObject(counts = data, assay = assay) } if (is.null(x = image)) { - image <- Read10X_Image(image.dir = file.path(data.dir, - "spatial"), filter.matrix = filter.matrix) + image <- Read10X_Image(image.dir = file.path(data.dir,"spatial"), + filter.matrix = filter.matrix) } else { if (!inherits(x = image, what = "VisiumV1")) From eaa786cd870550b12ccb7492a8f757372aaa22a1 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Mon, 10 Apr 2023 10:50:55 -0700 Subject: [PATCH 11/53] merge devlop in --- R/preprocessing.R | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/R/preprocessing.R b/R/preprocessing.R index 45baa4254..f7524278d 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -500,30 +500,43 @@ GetResidual <- function( #' } #' Load10X_Spatial <- function( - data.dir, - filename = 'filtered_feature_bc_matrix.h5', - assay = 'Spatial', - slice = 'slice1', - filter.matrix = TRUE, - to.upper = FALSE, - image = NULL, - ... + data.dir, + filename = 'filtered_feature_bc_matrix.h5', + assay = 'Spatial', + slice = 'slice1', + filter.matrix = TRUE, + to.upper = FALSE, + image = NULL, + ... ) { if (length(x = data.dir) > 1) { - warning("'Load10X_Spatial' accepts only one 'data.dir'", immediate. = TRUE) + warning("'Load10X_Spatial' accepts only one 'data.dir'", + immediate. = TRUE) data.dir <- data.dir[1] } - data <- Read10X_h5(filename = file.path(data.dir, filename), ...) + data <- Read10X_h5(filename = file.path(data.dir, filename), + ...) if (to.upper) { - rownames(x = data) <- toupper(x = rownames(x = data)) + data <- imap(data, ~{ + rownames(.x) <- toupper(rownames(.x)) + .x + }) + } + if (is.list(data) & "Antibody Capture" %in% names(data)) { + matrix_gex <- data$`Gene Expression` + matrix_protein <- data$`Antibody Capture` + object <- CreateSeuratObject(counts = matrix_gex, assay = assay) + object_protein <- CreateAssayObject(counts = matrix_protein) + object[["Protein"]] <- object_protein + } + else { + object <- CreateSeuratObject(counts = data, assay = assay) } - object <- CreateSeuratObject(counts = data, assay = assay) if (is.null(x = image)) { - image <- Read10X_Image( - image.dir = file.path(data.dir, 'spatial'), - filter.matrix = filter.matrix - ) - } else { + image <- Read10X_Image(image.dir = file.path(data.dir,"spatial"), + filter.matrix = filter.matrix) + } + else { if (!inherits(x = image, what = "VisiumV1")) stop("Image must be an object of class 'VisiumV1'.") } From fe3a5effda825e901cb6ee7816ab46689cff0344 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Mon, 10 Apr 2023 11:43:04 -0700 Subject: [PATCH 12/53] add ability to read raw_probe_bc_matrix.h5 --- NAMESPACE | 3 +++ R/preprocessing.R | 46 +++++++++++++++++++++++++++++++++++ man/Read10x_probe_metadata.Rd | 26 ++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 man/Read10x_probe_metadata.Rd diff --git a/NAMESPACE b/NAMESPACE index 4984dc2be..5206e445f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -282,6 +282,7 @@ export(Radius) export(Read10X) export(Read10X_Image) export(Read10X_h5) +export(Read10x_probe_metadata) export(ReadAkoya) export(ReadMtx) export(ReadNanostring) @@ -615,6 +616,8 @@ importFrom(grid,pointsGrob) importFrom(grid,rasterGrob) importFrom(grid,unit) importFrom(grid,viewport) +importFrom(hdf5r,H5File) +importFrom(hdf5r,list.objects) importFrom(httr,GET) importFrom(httr,accept_json) importFrom(httr,build_url) diff --git a/R/preprocessing.R b/R/preprocessing.R index 82c482c25..47db60421 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -516,6 +516,7 @@ Load10X_Spatial <- function( } data <- Read10X_h5(filename = file.path(data.dir, filename), ...) + if (to.upper) { data <- imap(data, ~{ rownames(.x) <- toupper(rownames(.x)) @@ -543,10 +544,55 @@ Load10X_Spatial <- function( image <- image[Cells(x = object)] DefaultAssay(object = image) <- assay object[[slice]] <- image + + # if using the raw_probe_bc_matrix.h5 add probe meta-data to @misc slot + if(filename == "raw_probe_bc_matrix.h5"){ + probe_metadata <- Read10x_probe_metadata(data.dir) + Misc(object = object[['Spatial']], slot = "probe_metadata") <- probe_metadata + } return(object) } +#' Read10x Probe Metadata +#' +#' This function reads the probe metadata from a 10x Genomics probe barcode matrix file in HDF5 format. +#' +#' @param data.dir The directory where the file is located. +#' @param filename The name of the file containing the raw probe barcode matrix in HDF5 format. The default filename is 'raw_probe_bc_matrix.h5'. +#' +#' @return Returns a vector containing the probe metadata. +#' @export +#' +#' @examples +#' data_dir <- "~/data" +#' filename <- "raw_probe_bc_matrix.h5" +#' metadata <- Read10x_probe_metadata(data_dir, filename) +#' print(metadata) +#' +#' @importFrom hdf5r H5File list.objects +#' +#' +#' @export +Read10x_probe_metadata <- function(data.dir, + filename = 'raw_probe_bc_matrix.h5') { + + if (!requireNamespace('hdf5r', quietly = TRUE)) { + stop("Please install hdf5r to read HDF5 files") + } + file_path = paste0(data.dir,"/", filename) + if (!file.exists(file_path)) { + stop("File not found") + } + infile <- hdf5r::H5File$new(filename = file_path, mode = 'r') + if("matrix/features/probe_region" %in% hdf5r::list.objects(infile)) { + probe_name <- infile[['matrix/features/name']][] + probe_region<- infile[['matrix/features/probe_region']][] + meta_data <- data.frame(probe_name, probe_region) + return(meta_data) + } +} + #' Load STARmap data #' #' @param data.dir location of data directory that contains the counts matrix, diff --git a/man/Read10x_probe_metadata.Rd b/man/Read10x_probe_metadata.Rd new file mode 100644 index 000000000..631c3a3e7 --- /dev/null +++ b/man/Read10x_probe_metadata.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/preprocessing.R +\name{Read10x_probe_metadata} +\alias{Read10x_probe_metadata} +\title{Read10x Probe Metadata} +\usage{ +Read10x_probe_metadata(data.dir, filename = "raw_probe_bc_matrix.h5") +} +\arguments{ +\item{data.dir}{The directory where the file is located.} + +\item{filename}{The name of the file containing the raw probe barcode matrix in HDF5 format. The default filename is 'raw_probe_bc_matrix.h5'.} +} +\value{ +Returns a vector containing the probe metadata. +} +\description{ +This function reads the probe metadata from a 10x Genomics probe barcode matrix file in HDF5 format. +} +\examples{ +data_dir <- "~/data" +filename <- "raw_probe_bc_matrix.h5" +metadata <- Read10x_probe_metadata(data_dir, filename) +print(metadata) + +} From 5fbc793e647c811045ab3c9f8cb35fe3ed77a042 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Mon, 10 Apr 2023 11:54:11 -0700 Subject: [PATCH 13/53] update docs --- NAMESPACE | 2 -- R/preprocessing.R | 12 +----------- man/Read10x_probe_metadata.Rd | 9 +-------- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 5206e445f..f2a3568cd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -616,8 +616,6 @@ importFrom(grid,pointsGrob) importFrom(grid,rasterGrob) importFrom(grid,unit) importFrom(grid,viewport) -importFrom(hdf5r,H5File) -importFrom(hdf5r,list.objects) importFrom(httr,GET) importFrom(httr,accept_json) importFrom(httr,build_url) diff --git a/R/preprocessing.R b/R/preprocessing.R index 47db60421..7fd02693e 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -561,17 +561,7 @@ Load10X_Spatial <- function( #' @param data.dir The directory where the file is located. #' @param filename The name of the file containing the raw probe barcode matrix in HDF5 format. The default filename is 'raw_probe_bc_matrix.h5'. #' -#' @return Returns a vector containing the probe metadata. -#' @export -#' -#' @examples -#' data_dir <- "~/data" -#' filename <- "raw_probe_bc_matrix.h5" -#' metadata <- Read10x_probe_metadata(data_dir, filename) -#' print(metadata) -#' -#' @importFrom hdf5r H5File list.objects -#' +#' @return Returns a data.frame containing the probe metadata. #' #' @export Read10x_probe_metadata <- function(data.dir, diff --git a/man/Read10x_probe_metadata.Rd b/man/Read10x_probe_metadata.Rd index 631c3a3e7..a0f2d3e98 100644 --- a/man/Read10x_probe_metadata.Rd +++ b/man/Read10x_probe_metadata.Rd @@ -12,15 +12,8 @@ Read10x_probe_metadata(data.dir, filename = "raw_probe_bc_matrix.h5") \item{filename}{The name of the file containing the raw probe barcode matrix in HDF5 format. The default filename is 'raw_probe_bc_matrix.h5'.} } \value{ -Returns a vector containing the probe metadata. +Returns a data.frame containing the probe metadata. } \description{ This function reads the probe metadata from a 10x Genomics probe barcode matrix file in HDF5 format. } -\examples{ -data_dir <- "~/data" -filename <- "raw_probe_bc_matrix.h5" -metadata <- Read10x_probe_metadata(data_dir, filename) -print(metadata) - -} From b9132c00f1998b38a897ae1aa01b8f516ec96c28 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Mon, 10 Apr 2023 18:31:01 -0700 Subject: [PATCH 14/53] be more flexible with probe metadata --- R/preprocessing.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R/preprocessing.R b/R/preprocessing.R index 7fd02693e..dff51263c 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -545,8 +545,10 @@ Load10X_Spatial <- function( DefaultAssay(object = image) <- assay object[[slice]] <- image - # if using the raw_probe_bc_matrix.h5 add probe meta-data to @misc slot - if(filename == "raw_probe_bc_matrix.h5"){ + # if using the meta-data available for probes add to @misc slot + file_path <- file.path(data.dir, filename) + infile <- hdf5r::H5File$new(filename = file_path, mode = 'r') + if("matrix/features/probe_region" %in% hdf5r::list.objects(infile)) { probe_metadata <- Read10x_probe_metadata(data.dir) Misc(object = object[['Spatial']], slot = "probe_metadata") <- probe_metadata } From 30f21e2452d203fc4858fbe3df6b945086fbc356 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Wed, 12 Apr 2023 14:44:47 -0400 Subject: [PATCH 15/53] Add files via upload --- tests/testdata/visium/raw_probe_bc_matrix.h5 | Bin 0 -> 127196 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/testdata/visium/raw_probe_bc_matrix.h5 diff --git a/tests/testdata/visium/raw_probe_bc_matrix.h5 b/tests/testdata/visium/raw_probe_bc_matrix.h5 new file mode 100644 index 0000000000000000000000000000000000000000..7b424ed6e9864abbec3749d029aaf343a947b391 GIT binary patch literal 127196 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�Ygf}om1OF)Gm81 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 literal 0 HcmV?d00001 From 3662341e87895caf91a63ea9f55bc69d26afbc8a Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Fri, 14 Apr 2023 11:42:56 -0400 Subject: [PATCH 16/53] Fix fold change calculation for assays --- R/differential_expression.R | 54 ++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/R/differential_expression.R b/R/differential_expression.R index 6b66b07c3..0a62f1a98 100644 --- a/R/differential_expression.R +++ b/R/differential_expression.R @@ -59,6 +59,7 @@ FindAllMarkers <- function( latent.vars = NULL, min.cells.feature = 3, min.cells.group = 3, + pseudocount.use = 1, mean.fxn = NULL, fc.name = NULL, base = 2, @@ -101,6 +102,18 @@ FindAllMarkers <- function( new.nodes <- unique(x = tree$edge[, 1, drop = TRUE]) idents.all <- (tree$Nnode + 2):max(tree$edge) } + # Default mean function assumes data has been log transformed (such as post NormalizeData or SCT data slot) + default.mean.fxn <- function(x) { + return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) + } + mean.fxn <- mean.fxn %||% switch( + EXPR = slot, + 'counts' = function(x) { + return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) + }, + 'scale.data' = rowMeans, + default.mean.fxn + ) genes.de <- list() messages <- list() for (i in 1:length(x = idents.all)) { @@ -752,12 +765,18 @@ FindMarkers.SCTAssay <- function( 'scale.data' = GetAssayData(object = object, slot = "counts"), numeric() ) - if (is.null(x = mean.fxn)){ - mean.fxn <- function(x) { - return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) - - } + # Default assumes FindMarkers was invoked with log2(corrected counts) - SCT data slot + default.mean.fxn <- function(x) { + return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) } + mean.fxn <- mean.fxn %||% switch( + EXPR = slot, + 'counts' = function(x) { + return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) + }, + 'scale.data' = rowMeans, + default.mean.fxn + ) fc.results <- FoldChange( object = object, slot = data.slot, @@ -1089,21 +1108,30 @@ FoldChange.Assay <- function( ) { pseudocount.use <- pseudocount.use %||% 1 data <- GetAssayData(object = object, slot = slot) + # Default assumes FoldChange was invoked with log2(corrected counts) - SCT data slot default.mean.fxn <- function(x) { - return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) + return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) } mean.fxn <- mean.fxn %||% switch( EXPR = slot, - 'data' = switch( - EXPR = norm.method %||% '', - 'LogNormalize' = function(x) { - return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) - }, - default.mean.fxn - ), + 'counts' = function(x) { + return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) + }, 'scale.data' = rowMeans, default.mean.fxn ) + # mean.fxn <- mean.fxn %||% switch( + # EXPR = slot, + # 'data' = switch( + # EXPR = norm.method %||% '', + # 'LogNormalize' = function(x) { + # return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) + # }, + # default.mean.fxn + # ), + # 'scale.data' = rowMeans, + # default.mean.fxn + # ) # Omit the decimal value of e from the column name if base == exp(1) base.text <- ifelse( test = base == exp(1), From a2be3b4aaac7664f4432994a00bf5ccac086d156 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Fri, 21 Apr 2023 11:44:48 -0400 Subject: [PATCH 17/53] Update R/preprocessing.R Co-authored-by: Shaun Jackman --- R/preprocessing.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/preprocessing.R b/R/preprocessing.R index dff51263c..323db7c64 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -523,7 +523,7 @@ Load10X_Spatial <- function( .x }) } - if (is.list(data) & "Antibody Capture" %in% names(data)) { + if (is.list(data) && "Antibody Capture" %in% names(data)) { matrix_gex <- data$`Gene Expression` matrix_protein <- data$`Antibody Capture` object <- CreateSeuratObject(counts = matrix_gex, assay = assay) From 11f888e50ce5cfe5b9d5ec11465a36a03261aa32 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Thu, 25 May 2023 08:30:26 -0700 Subject: [PATCH 18/53] updat Read10x_probe_metadata.Rd to use filename --- NAMESPACE | 2 - R/preprocessing.R | 165 +++++++--------------------------- man/Read10x_probe_metadata.Rd | 6 ++ man/Seurat-package.Rd | 2 + man/SpatialPlot.Rd | 9 ++ 5 files changed, 47 insertions(+), 137 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index f2a3568cd..e9b6d6a9f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -200,7 +200,6 @@ export(GetAssay) export(GetAssayData) export(GetImage) export(GetIntegrationData) -export(GetResidual) export(GetTissueCoordinates) export(GetTransferPredictions) export(GroupCorrelation) @@ -636,7 +635,6 @@ importFrom(jsonlite,fromJSON) importFrom(jsonlite,read_json) importFrom(leiden,leiden) importFrom(lmtest,lrtest) -importFrom(matrixStats,rowAnyNAs) importFrom(matrixStats,rowMeans2) importFrom(matrixStats,rowSds) importFrom(matrixStats,rowSums2) diff --git a/R/preprocessing.R b/R/preprocessing.R index 323db7c64..278c70e4c 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -333,139 +333,6 @@ HTODemux <- function( return(object) } -#' Calculate pearson residuals of features not in the scale.data -#' -#' This function calls sctransform::get_residuals. -#' -#' @param object A seurat object -#' @param features Name of features to add into the scale.data -#' @param assay Name of the assay of the seurat object generated by SCTransform -#' @param umi.assay Name of the assay of the seurat object containing UMI matrix -#' and the default is RNA -#' @param clip.range Numeric of length two specifying the min and max values the -#' Pearson residual will be clipped to -#' @param replace.value Recalculate residuals for all features, even if they are -#' already present. Useful if you want to change the clip.range. -#' @param na.rm For features where there is no feature model stored, return NA -#' for residual value in scale.data when na.rm = FALSE. When na.rm is TRUE, only -#' return residuals for features with a model stored for all cells. -#' @param verbose Whether to print messages and progress bars -#' -#' @return Returns a Seurat object containing Pearson residuals of added -#' features in its scale.data -#' -#' @importFrom sctransform get_residuals -#' @importFrom matrixStats rowAnyNAs -#' -#' @export -#' @concept preprocessing -#' -#' @seealso \code{\link[sctransform]{get_residuals}} -#' -#' @examples -#' data("pbmc_small") -#' pbmc_small <- SCTransform(object = pbmc_small, variable.features.n = 20) -#' pbmc_small <- GetResidual(object = pbmc_small, features = c('MS4A1', 'TCL1A')) -#' -GetResidual <- function( - object, - features, - assay = NULL, - umi.assay = NULL, - clip.range = NULL, - replace.value = FALSE, - na.rm = TRUE, - verbose = TRUE -) { - assay <- assay %||% DefaultAssay(object = object) - if (IsSCT(assay = object[[assay]])) { - object[[assay]] <- as(object[[assay]], 'SCTAssay') - } - if (!inherits(x = object[[assay]], what = "SCTAssay")) { - stop(assay, " assay was not generated by SCTransform") - } - sct.models <- levels(x = object[[assay]]) - if (length(x = sct.models) == 0) { - warning("SCT model not present in assay", call. = FALSE, immediate. = TRUE) - return(object) - } - possible.features <- unique(x = unlist(x = lapply(X = sct.models, FUN = function(x) { - rownames(x = SCTResults(object = object[[assay]], slot = "feature.attributes", model = x)) - } - ))) - bad.features <- setdiff(x = features, y = possible.features) - if (length(x = bad.features) > 0) { - warning("The following requested features are not present in any models: ", - paste(bad.features, collapse = ", "), call. = FALSE) - features <- intersect(x = features, y = possible.features) - } - features.orig <- features - if (na.rm) { - # only compute residuals when feature model info is present in all - features <- names(x = which(x = table(unlist(x = lapply( - X = sct.models, - FUN = function(x) { - rownames(x = SCTResults(object = object[[assay]], slot = "feature.attributes", model = x)) - } - ))) == length(x = sct.models))) - if (length(x = features) == 0) { - return(object) - } - } - features <- intersect(x = features.orig, y = features) - if (length(x = sct.models) > 1 && verbose) { - message( - "This SCTAssay contains multiple SCT models. Computing residuals for cells using different models" - ) - } - new.residuals <- lapply( - X = sct.models, - FUN = function(x) { - GetResidualSCTModel( - object = object, - assay = assay, - SCTModel = x, - new_features = features, - replace.value = replace.value, - clip.range = clip.range, - verbose = verbose - ) - } - ) - existing.data <- GetAssayData(object = object, slot = 'scale.data', assay = assay) - all.features <- union(x = rownames(x = existing.data), y = features) - new.scale <- matrix( - data = NA, - nrow = length(x = all.features), - ncol = ncol(x = object), - dimnames = list(all.features, Cells(x = object)) - ) - if (nrow(x = existing.data) > 0){ - new.scale[1:nrow(x = existing.data), ] <- existing.data - } - if (length(x = new.residuals) == 1 & is.list(x = new.residuals)) { - new.residuals <- new.residuals[[1]] - } else { - new.residuals <- Reduce(cbind, new.residuals) - } - new.scale[rownames(x = new.residuals), colnames(x = new.residuals)] <- new.residuals - if (na.rm) { - new.scale <- new.scale[!rowAnyNAs(x = new.scale), ] - } - object <- SetAssayData( - object = object, - assay = assay, - slot = "scale.data", - new.data = new.scale - ) - if (any(!features.orig %in% rownames(x = new.scale))) { - bad.features <- features.orig[which(!features.orig %in% rownames(x = new.scale))] - warning("Residuals not computed for the following requested features: ", - paste(bad.features, collapse = ", "), call. = FALSE) - } - return(object) -} - #' Load a 10x Genomics Visium Spatial Experiment into a \code{Seurat} object #' #' @inheritParams Read10X @@ -523,7 +390,7 @@ Load10X_Spatial <- function( .x }) } - if (is.list(data) && "Antibody Capture" %in% names(data)) { + if (is.list(data) & "Antibody Capture" %in% names(data)) { matrix_gex <- data$`Gene Expression` matrix_protein <- data$`Antibody Capture` object <- CreateSeuratObject(counts = matrix_gex, assay = assay) @@ -549,13 +416,41 @@ Load10X_Spatial <- function( file_path <- file.path(data.dir, filename) infile <- hdf5r::H5File$new(filename = file_path, mode = 'r') if("matrix/features/probe_region" %in% hdf5r::list.objects(infile)) { - probe_metadata <- Read10x_probe_metadata(data.dir) + probe_metadata <- Read10x_probe_metadata(data.dir, filename) Misc(object = object[['Spatial']], slot = "probe_metadata") <- probe_metadata } return(object) } +#' Read10x Probe Metadata +#' +#' This function reads the probe metadata from a 10x Genomics probe barcode matrix file in HDF5 format. +#' +#' @param data.dir The directory where the file is located. +#' @param filename The name of the file containing the raw probe barcode matrix in HDF5 format. Currently the only file that contains meta-data is 'raw_probe_bc_matrix.h5'. +#' +#' @return Returns a data.frame containing the probe metadata. +#' +#' @export +Read10x_probe_metadata <- function(data.dir, filename) { + + if (!requireNamespace('hdf5r', quietly = TRUE)) { + stop("Please install hdf5r to read HDF5 files") + } + file_path = paste0(data.dir,"/", filename) + if (!file.exists(file_path)) { + stop("File not found") + } + infile <- hdf5r::H5File$new(filename = file_path, mode = 'r') + if("matrix/features/probe_region" %in% hdf5r::list.objects(infile)) { + probe_name <- infile[['matrix/features/name']][] + probe_region<- infile[['matrix/features/probe_region']][] + meta_data <- data.frame(probe_name, probe_region) + return(meta_data) + } +} + #' Read10x Probe Metadata #' #' This function reads the probe metadata from a 10x Genomics probe barcode matrix file in HDF5 format. diff --git a/man/Read10x_probe_metadata.Rd b/man/Read10x_probe_metadata.Rd index a0f2d3e98..d3dd53e61 100644 --- a/man/Read10x_probe_metadata.Rd +++ b/man/Read10x_probe_metadata.Rd @@ -4,6 +4,8 @@ \alias{Read10x_probe_metadata} \title{Read10x Probe Metadata} \usage{ +Read10x_probe_metadata(data.dir, filename = "raw_probe_bc_matrix.h5") + Read10x_probe_metadata(data.dir, filename = "raw_probe_bc_matrix.h5") } \arguments{ @@ -12,8 +14,12 @@ Read10x_probe_metadata(data.dir, filename = "raw_probe_bc_matrix.h5") \item{filename}{The name of the file containing the raw probe barcode matrix in HDF5 format. The default filename is 'raw_probe_bc_matrix.h5'.} } \value{ +Returns a data.frame containing the probe metadata. + Returns a data.frame containing the probe metadata. } \description{ +This function reads the probe metadata from a 10x Genomics probe barcode matrix file in HDF5 format. + This function reads the probe metadata from a 10x Genomics probe barcode matrix file in HDF5 format. } diff --git a/man/Seurat-package.Rd b/man/Seurat-package.Rd index 351af75c9..69a18bf33 100644 --- a/man/Seurat-package.Rd +++ b/man/Seurat-package.Rd @@ -53,7 +53,9 @@ Other contributors: \item Jeff Farrell \email{jfarrell@g.harvard.edu} [contributor] \item Christoph Hafemeister \email{chafemeister@nygenome.org} (\href{https://orcid.org/0000-0001-6365-8254}{ORCID}) [contributor] \item Yuhan Hao \email{yhao@nygenome.org} (\href{https://orcid.org/0000-0002-1810-0822}{ORCID}) [contributor] + \item Austin Hartman \email{ahartman@nygenome.org} (\href{https://orcid.org/0000-0001-7278-1852}{ORCID}) [contributor] \item Jaison Jain \email{jjain@nygenome.org} (\href{https://orcid.org/0000-0002-9478-5018}{ORCID}) [contributor] + \item Madeline Kowalski \email{mkowalski@nygenome.org} (\href{https://orcid.org/0000-0002-5655-7620}{ORCID}) [contributor] \item Efthymia Papalexi \email{epapalexi@nygenome.org} (\href{https://orcid.org/0000-0001-5898-694X}{ORCID}) [contributor] \item Patrick Roelli \email{proelli@nygenome.org} [contributor] \item Rahul Satija \email{rsatija@nygenome.org} (\href{https://orcid.org/0000-0001-9448-8833}{ORCID}) [contributor] diff --git a/man/SpatialPlot.Rd b/man/SpatialPlot.Rd index 133501e2a..5bd9f8162 100644 --- a/man/SpatialPlot.Rd +++ b/man/SpatialPlot.Rd @@ -15,6 +15,7 @@ SpatialPlot( image.alpha = 1, crop = TRUE, slot = "data", + keep.scale = "feature", min.cutoff = NA, max.cutoff = NA, cells.highlight = NULL, @@ -67,6 +68,7 @@ SpatialFeaturePlot( images = NULL, crop = TRUE, slot = "data", + keep.scale = "feature", min.cutoff = NA, max.cutoff = NA, ncol = NULL, @@ -103,6 +105,13 @@ entire background image.} \item{slot}{If plotting a feature, which data slot to pull from (counts, data, or scale.data)} +\item{keep.scale}{How to handle the color scale across multiple plots. Options are: +\itemize{ + \item{"feature" (default; by row/feature scaling):}{ The plots for each individual feature are scaled to the maximum expression of the feature across the conditions provided to 'split.by'.} + \item{"all" (universal scaling):}{ The plots for all features and conditions are scaled to the maximum expression value for the feature with the highest overall expression.} + \item{NULL (no scaling):}{ Each individual plot is scaled to the maximum expression value of the feature in the condition provided to 'split.by'. Be aware setting NULL will result in color scales that are not comparable between plots.} +}} + \item{min.cutoff, max.cutoff}{Vector of minimum and maximum cutoff values for each feature, may specify quantile in the form of 'q##' where '##' is the quantile (eg, 'q1', 'q10')} From 61e3c929c308b7a934af96be8a8409dd536d0175 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Thu, 25 May 2023 08:39:49 -0700 Subject: [PATCH 19/53] accidental remove GetResidual --- NAMESPACE | 2 + R/preprocessing.R | 133 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index e9b6d6a9f..f2a3568cd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -200,6 +200,7 @@ export(GetAssay) export(GetAssayData) export(GetImage) export(GetIntegrationData) +export(GetResidual) export(GetTissueCoordinates) export(GetTransferPredictions) export(GroupCorrelation) @@ -635,6 +636,7 @@ importFrom(jsonlite,fromJSON) importFrom(jsonlite,read_json) importFrom(leiden,leiden) importFrom(lmtest,lrtest) +importFrom(matrixStats,rowAnyNAs) importFrom(matrixStats,rowMeans2) importFrom(matrixStats,rowSds) importFrom(matrixStats,rowSums2) diff --git a/R/preprocessing.R b/R/preprocessing.R index 278c70e4c..e819d73f8 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -333,6 +333,139 @@ HTODemux <- function( return(object) } +#' Calculate pearson residuals of features not in the scale.data +#' +#' This function calls sctransform::get_residuals. +#' +#' @param object A seurat object +#' @param features Name of features to add into the scale.data +#' @param assay Name of the assay of the seurat object generated by SCTransform +#' @param umi.assay Name of the assay of the seurat object containing UMI matrix +#' and the default is RNA +#' @param clip.range Numeric of length two specifying the min and max values the +#' Pearson residual will be clipped to +#' @param replace.value Recalculate residuals for all features, even if they are +#' already present. Useful if you want to change the clip.range. +#' @param na.rm For features where there is no feature model stored, return NA +#' for residual value in scale.data when na.rm = FALSE. When na.rm is TRUE, only +#' return residuals for features with a model stored for all cells. +#' @param verbose Whether to print messages and progress bars +#' +#' @return Returns a Seurat object containing Pearson residuals of added +#' features in its scale.data +#' +#' @importFrom sctransform get_residuals +#' @importFrom matrixStats rowAnyNAs +#' +#' @export +#' @concept preprocessing +#' +#' @seealso \code{\link[sctransform]{get_residuals}} +#' +#' @examples +#' data("pbmc_small") +#' pbmc_small <- SCTransform(object = pbmc_small, variable.features.n = 20) +#' pbmc_small <- GetResidual(object = pbmc_small, features = c('MS4A1', 'TCL1A')) +#' +GetResidual <- function( + object, + features, + assay = NULL, + umi.assay = NULL, + clip.range = NULL, + replace.value = FALSE, + na.rm = TRUE, + verbose = TRUE +) { + assay <- assay %||% DefaultAssay(object = object) + if (IsSCT(assay = object[[assay]])) { + object[[assay]] <- as(object[[assay]], 'SCTAssay') + } + if (!inherits(x = object[[assay]], what = "SCTAssay")) { + stop(assay, " assay was not generated by SCTransform") + } + sct.models <- levels(x = object[[assay]]) + if (length(x = sct.models) == 0) { + warning("SCT model not present in assay", call. = FALSE, immediate. = TRUE) + return(object) + } + possible.features <- unique(x = unlist(x = lapply(X = sct.models, FUN = function(x) { + rownames(x = SCTResults(object = object[[assay]], slot = "feature.attributes", model = x)) + } + ))) + bad.features <- setdiff(x = features, y = possible.features) + if (length(x = bad.features) > 0) { + warning("The following requested features are not present in any models: ", + paste(bad.features, collapse = ", "), call. = FALSE) + features <- intersect(x = features, y = possible.features) + } + features.orig <- features + if (na.rm) { + # only compute residuals when feature model info is present in all + features <- names(x = which(x = table(unlist(x = lapply( + X = sct.models, + FUN = function(x) { + rownames(x = SCTResults(object = object[[assay]], slot = "feature.attributes", model = x)) + } + ))) == length(x = sct.models))) + if (length(x = features) == 0) { + return(object) + } + } + features <- intersect(x = features.orig, y = features) + if (length(x = sct.models) > 1 && verbose) { + message( + "This SCTAssay contains multiple SCT models. Computing residuals for cells using different models" + ) + } + new.residuals <- lapply( + X = sct.models, + FUN = function(x) { + GetResidualSCTModel( + object = object, + assay = assay, + SCTModel = x, + new_features = features, + replace.value = replace.value, + clip.range = clip.range, + verbose = verbose + ) + } + ) + existing.data <- GetAssayData(object = object, slot = 'scale.data', assay = assay) + all.features <- union(x = rownames(x = existing.data), y = features) + new.scale <- matrix( + data = NA, + nrow = length(x = all.features), + ncol = ncol(x = object), + dimnames = list(all.features, Cells(x = object)) + ) + if (nrow(x = existing.data) > 0){ + new.scale[1:nrow(x = existing.data), ] <- existing.data + } + if (length(x = new.residuals) == 1 & is.list(x = new.residuals)) { + new.residuals <- new.residuals[[1]] + } else { + new.residuals <- Reduce(cbind, new.residuals) + } + new.scale[rownames(x = new.residuals), colnames(x = new.residuals)] <- new.residuals + if (na.rm) { + new.scale <- new.scale[!rowAnyNAs(x = new.scale), ] + } + object <- SetAssayData( + object = object, + assay = assay, + slot = "scale.data", + new.data = new.scale + ) + if (any(!features.orig %in% rownames(x = new.scale))) { + bad.features <- features.orig[which(!features.orig %in% rownames(x = new.scale))] + warning("Residuals not computed for the following requested features: ", + paste(bad.features, collapse = ", "), call. = FALSE) + } + return(object) +} + #' Load a 10x Genomics Visium Spatial Experiment into a \code{Seurat} object #' #' @inheritParams Read10X From bc567ef5bedd708f1da1c9b97e12960a8b948c11 Mon Sep 17 00:00:00 2001 From: AustinHartman Date: Thu, 25 May 2023 14:19:01 -0400 Subject: [PATCH 20/53] update formatting & news; bump version --- DESCRIPTION | 6 +- NAMESPACE | 2 +- NEWS.md | 1 + R/preprocessing.R | 86 +++++++------------ ..._metadata.Rd => Read10X_probe_metadata.Rd} | 12 +-- 5 files changed, 37 insertions(+), 70 deletions(-) rename man/{Read10x_probe_metadata.Rd => Read10X_probe_metadata.Rd} (60%) diff --git a/DESCRIPTION b/DESCRIPTION index d8317cf63..4a01c0d97 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: Seurat -Version: 4.3.0.9002 -Date: 2023-02-01 +Version: 4.3.0.9003 +Date: 2023-05-25 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. Authors@R: c( @@ -98,7 +98,7 @@ Collate: 'tree.R' 'utilities.R' 'zzz.R' -RoxygenNote: 7.2.2 +RoxygenNote: 7.2.3 Encoding: UTF-8 Suggests: ape, diff --git a/NAMESPACE b/NAMESPACE index f2a3568cd..435ea0551 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -282,7 +282,7 @@ export(Radius) export(Read10X) export(Read10X_Image) export(Read10X_h5) -export(Read10x_probe_metadata) +export(Read10X_probe_metadata) export(ReadAkoya) export(ReadMtx) export(ReadNanostring) diff --git a/NEWS.md b/NEWS.md index 75ed99993..d94e47ae5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,7 @@ # Unreleased ## Changes - Fix bug in `as.Seurat.SingleCellExperiment()` ([#6692](https://github.com/satijalab/seurat/issues/6692)) +- Support for Visium probe information introduced in Spaceranger 2.1 ([#7141](https://github.com/satijalab/seurat/pull/7141)) # Seurat 4.3.0 (2022-11-18) diff --git a/R/preprocessing.R b/R/preprocessing.R index e819d73f8..401df0189 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -368,14 +368,14 @@ HTODemux <- function( #' pbmc_small <- GetResidual(object = pbmc_small, features = c('MS4A1', 'TCL1A')) #' GetResidual <- function( - object, - features, - assay = NULL, - umi.assay = NULL, - clip.range = NULL, - replace.value = FALSE, - na.rm = TRUE, - verbose = TRUE + object, + features, + assay = NULL, + umi.assay = NULL, + clip.range = NULL, + replace.value = FALSE, + na.rm = TRUE, + verbose = TRUE ) { assay <- assay %||% DefaultAssay(object = object) if (IsSCT(assay = object[[assay]])) { @@ -500,14 +500,14 @@ GetResidual <- function( #' } #' Load10X_Spatial <- function( - data.dir, - filename = 'filtered_feature_bc_matrix.h5', - assay = 'Spatial', - slice = 'slice1', - filter.matrix = TRUE, - to.upper = FALSE, - image = NULL, - ... + data.dir, + filename = 'filtered_feature_bc_matrix.h5', + assay = 'Spatial', + slice = 'slice1', + filter.matrix = TRUE, + to.upper = FALSE, + image = NULL, + ... ) { if (length(x = data.dir) > 1) { warning("'Load10X_Spatial' accepts only one 'data.dir'", @@ -549,41 +549,12 @@ Load10X_Spatial <- function( file_path <- file.path(data.dir, filename) infile <- hdf5r::H5File$new(filename = file_path, mode = 'r') if("matrix/features/probe_region" %in% hdf5r::list.objects(infile)) { - probe_metadata <- Read10x_probe_metadata(data.dir, filename) - Misc(object = object[['Spatial']], slot = "probe_metadata") <- probe_metadata + probe.metadata <- Read10X_probe_metadata(data.dir, filename) + Misc(object = object[['Spatial']], slot = "probe_metadata") <- probe.metadata } return(object) } - -#' Read10x Probe Metadata -#' -#' This function reads the probe metadata from a 10x Genomics probe barcode matrix file in HDF5 format. -#' -#' @param data.dir The directory where the file is located. -#' @param filename The name of the file containing the raw probe barcode matrix in HDF5 format. Currently the only file that contains meta-data is 'raw_probe_bc_matrix.h5'. -#' -#' @return Returns a data.frame containing the probe metadata. -#' -#' @export -Read10x_probe_metadata <- function(data.dir, filename) { - - if (!requireNamespace('hdf5r', quietly = TRUE)) { - stop("Please install hdf5r to read HDF5 files") - } - file_path = paste0(data.dir,"/", filename) - if (!file.exists(file_path)) { - stop("File not found") - } - infile <- hdf5r::H5File$new(filename = file_path, mode = 'r') - if("matrix/features/probe_region" %in% hdf5r::list.objects(infile)) { - probe_name <- infile[['matrix/features/name']][] - probe_region<- infile[['matrix/features/probe_region']][] - meta_data <- data.frame(probe_name, probe_region) - return(meta_data) - } -} - #' Read10x Probe Metadata #' #' This function reads the probe metadata from a 10x Genomics probe barcode matrix file in HDF5 format. @@ -594,22 +565,23 @@ Read10x_probe_metadata <- function(data.dir, filename) { #' @return Returns a data.frame containing the probe metadata. #' #' @export -Read10x_probe_metadata <- function(data.dir, - filename = 'raw_probe_bc_matrix.h5') { - +Read10X_probe_metadata <- function( + data.dir, + filename = 'raw_probe_bc_matrix.h5' +) { if (!requireNamespace('hdf5r', quietly = TRUE)) { stop("Please install hdf5r to read HDF5 files") } - file_path = paste0(data.dir,"/", filename) - if (!file.exists(file_path)) { + file.path = paste0(data.dir,"/", filename) + if (!file.exists(file.path)) { stop("File not found") } - infile <- hdf5r::H5File$new(filename = file_path, mode = 'r') + infile <- hdf5r::H5File$new(filename = file.path, mode = 'r') if("matrix/features/probe_region" %in% hdf5r::list.objects(infile)) { - probe_name <- infile[['matrix/features/name']][] - probe_region<- infile[['matrix/features/probe_region']][] - meta_data <- data.frame(probe_name, probe_region) - return(meta_data) + probe.name <- infile[['matrix/features/name']][] + probe.region<- infile[['matrix/features/probe_region']][] + meta.data <- data.frame(probe.name, probe.region) + return(meta.data) } } diff --git a/man/Read10x_probe_metadata.Rd b/man/Read10X_probe_metadata.Rd similarity index 60% rename from man/Read10x_probe_metadata.Rd rename to man/Read10X_probe_metadata.Rd index d3dd53e61..2aa207a4b 100644 --- a/man/Read10x_probe_metadata.Rd +++ b/man/Read10X_probe_metadata.Rd @@ -1,12 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/preprocessing.R -\name{Read10x_probe_metadata} -\alias{Read10x_probe_metadata} +\name{Read10X_probe_metadata} +\alias{Read10X_probe_metadata} \title{Read10x Probe Metadata} \usage{ -Read10x_probe_metadata(data.dir, filename = "raw_probe_bc_matrix.h5") - -Read10x_probe_metadata(data.dir, filename = "raw_probe_bc_matrix.h5") +Read10X_probe_metadata(data.dir, filename = "raw_probe_bc_matrix.h5") } \arguments{ \item{data.dir}{The directory where the file is located.} @@ -14,12 +12,8 @@ Read10x_probe_metadata(data.dir, filename = "raw_probe_bc_matrix.h5") \item{filename}{The name of the file containing the raw probe barcode matrix in HDF5 format. The default filename is 'raw_probe_bc_matrix.h5'.} } \value{ -Returns a data.frame containing the probe metadata. - Returns a data.frame containing the probe metadata. } \description{ -This function reads the probe metadata from a 10x Genomics probe barcode matrix file in HDF5 format. - This function reads the probe metadata from a 10x Genomics probe barcode matrix file in HDF5 format. } From 1577c1147cf1a80d331450b9256e64ec21250a8e Mon Sep 17 00:00:00 2001 From: AustinHartman Date: Thu, 25 May 2023 15:50:54 -0400 Subject: [PATCH 21/53] add Curio Seeker load function; bump verion --- DESCRIPTION | 2 +- NAMESPACE | 1 + NEWS.md | 1 + R/preprocessing.R | 85 ++++++++++++++++++++++++++++++++++++++++++ man/LoadCurioSeeker.Rd | 21 +++++++++++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 man/LoadCurioSeeker.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 4a01c0d97..b3a24f298 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: Seurat -Version: 4.3.0.9003 +Version: 4.3.0.9004 Date: 2023-05-25 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. diff --git a/NAMESPACE b/NAMESPACE index 435ea0551..d74684414 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -235,6 +235,7 @@ export(LinkedFeaturePlot) export(Load10X_Spatial) export(LoadAkoya) export(LoadAnnoyIndex) +export(LoadCurioSeeker) export(LoadHuBMAPCODEX) export(LoadNanostring) export(LoadSTARmap) diff --git a/NEWS.md b/NEWS.md index d94e47ae5..afdcac4e9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ ## Changes - Fix bug in `as.Seurat.SingleCellExperiment()` ([#6692](https://github.com/satijalab/seurat/issues/6692)) - Support for Visium probe information introduced in Spaceranger 2.1 ([#7141](https://github.com/satijalab/seurat/pull/7141)) +- Add `LoadCurioSeeker` to load sequencing-based spatial datasets generated using the Curio Seeker # Seurat 4.3.0 (2022-11-18) diff --git a/R/preprocessing.R b/R/preprocessing.R index 401df0189..8719625a5 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -656,6 +656,91 @@ LoadSTARmap <- function( return(starmap) } +#' Load Curio Seeker data +#' +#' @param data.dir location of data directory that contains the counts matrix, +#' gene names, barcodes/beads, and barcodes/bead location files. +#' @param assay Name of assay to associate spatial data to +#' +#' @return A \code{\link{Seurat}} object +#' +#' @importFrom Matrix readMM +#' +#' @export +#' @concept preprocessing +#' +LoadCurioSeeker <- function(data.dir, assay = "Spatial") { + # check and find input files + if (length(x = data.dir) > 1) { + warning("'LoadCurioSeeker' accepts only one 'data.dir'", + immediate. = TRUE) + data.dir <- data.dir[1] + } + mtx.file <- list.files( + data.dir, + pattern = "*MoleculesPerMatchedBead.mtx", + full.names = TRUE) + if (length(x = mtx.file) > 1) { + warning("Multiple files matched the pattern '*MoleculesPerMatchedBead.mtx'", + immediate. = TRUE) + } else if (length(x = mtx.file) == 0) { + stop("No file matched the pattern '*MoleculesPerMatchedBead.mtx'", call. = FALSE) + } + mtx.file <- mtx.file[1] + barcodes.file <- list.files( + data.dir, + pattern = "*barcodes.tsv", + full.names = TRUE) + if (length(x = barcodes.file) > 1) { + warning("Multiple files matched the pattern '*barcodes.tsv'", + immediate. = TRUE) + } else if (length(x = barcodes.file) == 0) { + stop("No file matched the pattern '*barcodes.tsv'", call. = FALSE) + } + barcodes.file <- barcodes.file[1] + genes.file <- list.files( + data.dir, + pattern = "*genes.tsv", + full.names = TRUE) + if (length(x = genes.file) > 1) { + warning("Multiple files matched the pattern '*genes.tsv'", + immediate. = TRUE) + } else if (length(x = genes.file) == 0) { + stop("No file matched the pattern '*genes.tsv'", call. = FALSE) + } + genes.file <- genes.file[1] + coordinates.file <- list.files( + data.dir, + pattern = "*MatchedBeadLocation.csv", + full.names = TRUE) + if (length(x = coordinates.file) > 1) { + warning("Multiple files matched the pattern '*MatchedBeadLocation.csv'", + immediate. = TRUE) + } else if (length(x = coordinates.file) == 0) { + stop("No file matched the pattern '*MatchedBeadLocation.csv'", call. = FALSE) + } + coordinates.file <- coordinates.file[1] + + # load counts matrix and create seurat object + mtx <- readMM(mtx.file) + mtx <- as.sparse(mtx) + barcodes <- read.csv(barcodes.file, header = FALSE) + genes <- read.csv(genes.file, header = FALSE) + colnames(mtx) <- barcodes$V1 + rownames(mtx) <- genes$V1 + object <- CreateSeuratObject(counts = mtx, assay = assay) + + # load positions of each bead and store in a SlideSeq object in images slot + coords <- read.csv(coordinates.file) + colnames(coords) <- c("cell", "x", "y") + coords$y <- -coords$y + rownames(coords) <- coords$cell + coords$cell <- NULL + image <- new(Class = 'SlideSeq', assay = assay, coordinates = coords) + object[["Slice"]] <- image + return(object) +} + #' Normalize raw data #' #' Normalize count data per cell and transform to log scale diff --git a/man/LoadCurioSeeker.Rd b/man/LoadCurioSeeker.Rd new file mode 100644 index 000000000..5b86ef2c7 --- /dev/null +++ b/man/LoadCurioSeeker.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/preprocessing.R +\name{LoadCurioSeeker} +\alias{LoadCurioSeeker} +\title{Load Curio Seeker data} +\usage{ +LoadCurioSeeker(data.dir, assay = "Spatial") +} +\arguments{ +\item{data.dir}{location of data directory that contains the counts matrix, +gene names, barcodes/beads, and barcodes/bead location files.} + +\item{assay}{Name of assay to associate spatial data to} +} +\value{ +A \code{\link{Seurat}} object +} +\description{ +Load Curio Seeker data +} +\concept{preprocessing} From 68d0378a37234e515d0d583297c2b1247bfab396 Mon Sep 17 00:00:00 2001 From: AustinHartman Date: Thu, 25 May 2023 16:04:27 -0400 Subject: [PATCH 22/53] update NEWS --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index afdcac4e9..97fd014a8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,7 +2,8 @@ ## Changes - Fix bug in `as.Seurat.SingleCellExperiment()` ([#6692](https://github.com/satijalab/seurat/issues/6692)) - Support for Visium probe information introduced in Spaceranger 2.1 ([#7141](https://github.com/satijalab/seurat/pull/7141)) -- Add `LoadCurioSeeker` to load sequencing-based spatial datasets generated using the Curio Seeker +- Add `LoadCurioSeeker` to load sequencing-based spatial datasets generated using the Curio Seeker ([#744](https://github.com/satijalab/seurat-private/pull/744)) +- Fix fold change calculation for assays ([#739](https://github.com/satijalab/seurat-private/pull/739)) # Seurat 4.3.0 (2022-11-18) From b97e42da44278a17edc5a1d34654805abf8b23fc Mon Sep 17 00:00:00 2001 From: AustinHartman Date: Fri, 2 Jun 2023 14:23:27 -0400 Subject: [PATCH 23/53] dont check feature max in dim plots --- R/visualization.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/visualization.R b/R/visualization.R index b46347fd0..d3d2deff3 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -4009,7 +4009,7 @@ SpatialPlot <- function( } # Get feature max for individual feature - if (!(is.null(x = keep.scale)) && keep.scale == "feature") { + if (!(is.null(x = keep.scale)) && keep.scale == "feature" && typeof(x = data[, features[j]]) != "factor") { max.feature.value <- max(data[, features[j]]) } From 48afb8b978afca66e2696233b830578cbf019c3f Mon Sep 17 00:00:00 2001 From: AustinHartman Date: Fri, 2 Jun 2023 14:54:46 -0400 Subject: [PATCH 24/53] typeof->class --- R/visualization.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/visualization.R b/R/visualization.R index d3d2deff3..76720081f 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -4009,7 +4009,7 @@ SpatialPlot <- function( } # Get feature max for individual feature - if (!(is.null(x = keep.scale)) && keep.scale == "feature" && typeof(x = data[, features[j]]) != "factor") { + if (!(is.null(x = keep.scale)) && keep.scale == "feature" && class(x = data[, features[j]]) != "factor") { max.feature.value <- max(data[, features[j]]) } From 0ee9d27312e33c39732e13d239d131e895ac1777 Mon Sep 17 00:00:00 2001 From: AustinHartman Date: Fri, 2 Jun 2023 14:59:17 -0400 Subject: [PATCH 25/53] add condition at scale_fill_gradientn step --- R/visualization.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/visualization.R b/R/visualization.R index 76720081f..2a09a98fb 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -4085,7 +4085,7 @@ SpatialPlot <- function( } # Plot multiple images depending on keep.scale - if (!(is.null(x = keep.scale))) { + if (!(is.null(x = keep.scale)) && class(x = data[, features[j]]) != "factor") { plot <- suppressMessages(plot & scale_fill_gradientn(colors = SpatialColors(n = 100), limits = c(NA, max.feature.value))) } From ef133f2a9dc8d252a22c56a0f962c86d999a00dd Mon Sep 17 00:00:00 2001 From: AustinHartman Date: Fri, 2 Jun 2023 16:09:42 -0400 Subject: [PATCH 26/53] bump version --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index b3a24f298..4542df82c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: Seurat -Version: 4.3.0.9004 -Date: 2023-05-25 +Version: 4.3.0.9005 +Date: 2023-06-02 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. Authors@R: c( From b98aad116e104bb6bed60227269634543380f74a Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Fri, 9 Jun 2023 15:50:59 -0400 Subject: [PATCH 27/53] Default pt size to 1 when rasterizing --- R/visualization.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/visualization.R b/R/visualization.R index 2a09a98fb..c2e61c88c 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -8051,12 +8051,12 @@ SingleDimPlot <- function( raster = NULL, raster.dpi = NULL ) { - pt.size <- pt.size %||% AutoPointSize(data = data, raster = raster) if ((nrow(x = data) > 1e5) & !isFALSE(raster)){ message("Rasterizing points since number of points exceeds 100,000.", "\nTo disable this behavior set `raster=FALSE`") } raster <- raster %||% (nrow(x = data) > 1e5) + pt.size <- pt.size %||% AutoPointSize(data = data, raster = raster) if (!is.null(x = raster.dpi)) { if (!is.numeric(x = raster.dpi) || length(x = raster.dpi) != 2) stop("'raster.dpi' must be a two-length numeric vector") From 45f447f120a263b3e2521106c7c4b39ff3f5fb01 Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Fri, 9 Jun 2023 16:07:15 -0400 Subject: [PATCH 28/53] Revert "Fix fold change calculation for assays" --- R/differential_expression.R | 54 +++++++++---------------------------- 1 file changed, 13 insertions(+), 41 deletions(-) diff --git a/R/differential_expression.R b/R/differential_expression.R index 0a62f1a98..6b66b07c3 100644 --- a/R/differential_expression.R +++ b/R/differential_expression.R @@ -59,7 +59,6 @@ FindAllMarkers <- function( latent.vars = NULL, min.cells.feature = 3, min.cells.group = 3, - pseudocount.use = 1, mean.fxn = NULL, fc.name = NULL, base = 2, @@ -102,18 +101,6 @@ FindAllMarkers <- function( new.nodes <- unique(x = tree$edge[, 1, drop = TRUE]) idents.all <- (tree$Nnode + 2):max(tree$edge) } - # Default mean function assumes data has been log transformed (such as post NormalizeData or SCT data slot) - default.mean.fxn <- function(x) { - return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) - } - mean.fxn <- mean.fxn %||% switch( - EXPR = slot, - 'counts' = function(x) { - return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) - }, - 'scale.data' = rowMeans, - default.mean.fxn - ) genes.de <- list() messages <- list() for (i in 1:length(x = idents.all)) { @@ -765,18 +752,12 @@ FindMarkers.SCTAssay <- function( 'scale.data' = GetAssayData(object = object, slot = "counts"), numeric() ) - # Default assumes FindMarkers was invoked with log2(corrected counts) - SCT data slot - default.mean.fxn <- function(x) { - return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) + if (is.null(x = mean.fxn)){ + mean.fxn <- function(x) { + return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) + + } } - mean.fxn <- mean.fxn %||% switch( - EXPR = slot, - 'counts' = function(x) { - return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) - }, - 'scale.data' = rowMeans, - default.mean.fxn - ) fc.results <- FoldChange( object = object, slot = data.slot, @@ -1108,30 +1089,21 @@ FoldChange.Assay <- function( ) { pseudocount.use <- pseudocount.use %||% 1 data <- GetAssayData(object = object, slot = slot) - # Default assumes FoldChange was invoked with log2(corrected counts) - SCT data slot default.mean.fxn <- function(x) { - return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) + return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) } mean.fxn <- mean.fxn %||% switch( EXPR = slot, - 'counts' = function(x) { - return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) - }, + 'data' = switch( + EXPR = norm.method %||% '', + 'LogNormalize' = function(x) { + return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) + }, + default.mean.fxn + ), 'scale.data' = rowMeans, default.mean.fxn ) - # mean.fxn <- mean.fxn %||% switch( - # EXPR = slot, - # 'data' = switch( - # EXPR = norm.method %||% '', - # 'LogNormalize' = function(x) { - # return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) - # }, - # default.mean.fxn - # ), - # 'scale.data' = rowMeans, - # default.mean.fxn - # ) # Omit the decimal value of e from the column name if base == exp(1) base.text <- ifelse( test = base == exp(1), From 688f60cbedb894d809afb940817edaad767d4fcc Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Thu, 6 Jul 2023 09:35:49 -0400 Subject: [PATCH 29/53] Update news, bump version --- DESCRIPTION | 2 +- NEWS.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4542df82c..b1a466ec9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: Seurat -Version: 4.3.0.9005 +Version: 4.3.0.9006 Date: 2023-06-02 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. diff --git a/NEWS.md b/NEWS.md index 97fd014a8..e31cd93d0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,7 @@ - Support for Visium probe information introduced in Spaceranger 2.1 ([#7141](https://github.com/satijalab/seurat/pull/7141)) - Add `LoadCurioSeeker` to load sequencing-based spatial datasets generated using the Curio Seeker ([#744](https://github.com/satijalab/seurat-private/pull/744)) - Fix fold change calculation for assays ([#739](https://github.com/satijalab/seurat-private/pull/739)) +- Fix `pt.size` bug when rasterization is set to true ([#7379](https://github.com/satijalab/seurat/issues/7379)) # Seurat 4.3.0 (2022-11-18) From e08e1084049ee9387591e442e00799645d833591 Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Thu, 6 Jul 2023 12:42:14 -0400 Subject: [PATCH 30/53] Fix fold change calculation for SCT assay --- R/differential_expression.R | 72 +++++++++++++++++-- tests/testthat/test_differential_expression.R | 1 + 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/R/differential_expression.R b/R/differential_expression.R index 6b66b07c3..6d014cde0 100644 --- a/R/differential_expression.R +++ b/R/differential_expression.R @@ -752,12 +752,18 @@ FindMarkers.SCTAssay <- function( 'scale.data' = GetAssayData(object = object, slot = "counts"), numeric() ) - if (is.null(x = mean.fxn)){ - mean.fxn <- function(x) { - return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) - - } + # Default assumes the input is log1p(corrected counts) + default.mean.fxn <- function(x) { + return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) } + mean.fxn <- mean.fxn %||% switch( + EXPR = slot, + 'counts' = function(x) { + return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) + }, + 'scale.data' = rowMeans, + default.mean.fxn + ) fc.results <- FoldChange( object = object, slot = data.slot, @@ -1102,6 +1108,9 @@ FoldChange.Assay <- function( default.mean.fxn ), 'scale.data' = rowMeans, + 'counts' = function(x) { + return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) + }, default.mean.fxn ) # Omit the decimal value of e from the column name if base == exp(1) @@ -1125,6 +1134,59 @@ FoldChange.Assay <- function( ) } +#' @importFrom Matrix rowMeans +#' @rdname FoldChange +#' @concept differential_expression +#' @export +#' @method FoldChange SCTAssay +FoldChange.SCTAssay <- function( + object, + cells.1, + cells.2, + features = NULL, + slot = "data", + pseudocount.use = 1, + fc.name = NULL, + mean.fxn = NULL, + base = 2, + ... +) { + pseudocount.use <- pseudocount.use %||% 1 + data <- GetAssayData(object = object, slot = slot) + default.mean.fxn <- function(x) { + return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) + } + mean.fxn <- mean.fxn %||% switch( + EXPR = slot, + 'data' = default.mean.fxn, + 'scale.data' = rowMeans, + 'counts' = function(x) { + return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) + }, + default.mean.fxn + ) + # Omit the decimal value of e from the column name if base == exp(1) + base.text <- ifelse( + test = base == exp(1), + yes = "", + no = base + ) + fc.name <- fc.name %||% ifelse( + test = slot == "scale.data", + yes = "avg_diff", + no = paste0("avg_log", base.text, "FC") + ) + FoldChange( + object = data, + cells.1 = cells.1, + cells.2 = cells.2, + features = features, + mean.fxn = mean.fxn, + fc.name = fc.name + ) +} + + #' @importFrom Matrix rowMeans #' @rdname FoldChange #' @concept differential_expression diff --git a/tests/testthat/test_differential_expression.R b/tests/testthat/test_differential_expression.R index 373a6b6d9..1f2f6e8ed 100644 --- a/tests/testthat/test_differential_expression.R +++ b/tests/testthat/test_differential_expression.R @@ -300,6 +300,7 @@ test_that("FindAllMarkers works as expected", { # CLR normalization expect_equal(results.clr[1, "p_val"], 1.209462e-11) + expect_equal(results.clr[1, "avg_log2FC"], -1.079924, tolerance = 1e-6) expect_equal(results.clr[1, "pct.1"], 0.083) expect_equal(results.clr[1, "pct.2"], 0.909) From 992c1a96693b1467160969625edc8a5c80ab0867 Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Thu, 6 Jul 2023 15:32:30 -0400 Subject: [PATCH 31/53] Fix foldchange calculation for all normalization approaches --- R/differential_expression.R | 45 ++++++++++++------- tests/testthat/test_differential_expression.R | 1 - 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/R/differential_expression.R b/R/differential_expression.R index 6d014cde0..a67860abb 100644 --- a/R/differential_expression.R +++ b/R/differential_expression.R @@ -1095,24 +1095,37 @@ FoldChange.Assay <- function( ) { pseudocount.use <- pseudocount.use %||% 1 data <- GetAssayData(object = object, slot = slot) - default.mean.fxn <- function(x) { + # By default run as if LogNormalize is done + log1pdata.mean.fxn <- function(x) { + return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) + } + scaledata.mean.fxn <- rowMeans + counts.mean.fxn <- function(x) { return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) } - mean.fxn <- mean.fxn %||% switch( - EXPR = slot, - 'data' = switch( - EXPR = norm.method %||% '', - 'LogNormalize' = function(x) { - return(log(x = rowMeans(x = expm1(x = x)) + pseudocount.use, base = base)) - }, - default.mean.fxn - ), - 'scale.data' = rowMeans, - 'counts' = function(x) { - return(log(x = rowMeans(x = x) + pseudocount.use, base = base)) - }, - default.mean.fxn - ) + if (!is.null(x = norm.method)) { + # For anything apart from log normalization set to rowMeans + if (norm.method!="LogNormalize") { + new.mean.fxn <- counts.mean.fxn + } else { + new.mean.fxn <- counts.mean.fxn + if (slot == "data") { + new.mean.fxn <- log1pdata.mean.fxn + } else if (slot == "scale.data") { + new.mean.fxn <- scaledata.mean.fxn + } + } + } else { + # If no normalization method is passed use slots to decide mean function + new.mean.fxn <- switch( + EXPR = slot, + 'data' = log1pdata.mean.fxn, + 'scale.data' = scaledata.mean.fxn, + 'counts' = counts.mean.fxn, + log1pdata.mean.fxn + ) + } + mean.fxn <- mean.fxn %||% new.mean.fxn # Omit the decimal value of e from the column name if base == exp(1) base.text <- ifelse( test = base == exp(1), diff --git a/tests/testthat/test_differential_expression.R b/tests/testthat/test_differential_expression.R index 1f2f6e8ed..373a6b6d9 100644 --- a/tests/testthat/test_differential_expression.R +++ b/tests/testthat/test_differential_expression.R @@ -300,7 +300,6 @@ test_that("FindAllMarkers works as expected", { # CLR normalization expect_equal(results.clr[1, "p_val"], 1.209462e-11) - expect_equal(results.clr[1, "avg_log2FC"], -1.079924, tolerance = 1e-6) expect_equal(results.clr[1, "pct.1"], 0.083) expect_equal(results.clr[1, "pct.2"], 0.909) From d342294380cd05c0eae193ddcc6ee87d62c73df6 Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Thu, 6 Jul 2023 15:55:13 -0400 Subject: [PATCH 32/53] Update news; bump version --- DESCRIPTION | 2 +- NEWS.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index b1a466ec9..c8b3cc136 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: Seurat -Version: 4.3.0.9006 +Version: 4.3.0.9007 Date: 2023-06-02 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. diff --git a/NEWS.md b/NEWS.md index e31cd93d0..a2c2d9147 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,7 @@ - Add `LoadCurioSeeker` to load sequencing-based spatial datasets generated using the Curio Seeker ([#744](https://github.com/satijalab/seurat-private/pull/744)) - Fix fold change calculation for assays ([#739](https://github.com/satijalab/seurat-private/pull/739)) - Fix `pt.size` bug when rasterization is set to true ([#7379](https://github.com/satijalab/seurat/issues/7379)) +- Fix `FoldChange` and `FindMarkers` to support all normalization approaches ([#7115](https://github.com/satijalab/seurat/pull/7115),[#7110](https://github.com/satijalab/seurat/issues/7110),[#7095](https://github.com/satijalab/seurat/issues/7095),[#6976](https://github.com/satijalab/seurat/issues/6976),[#6654](https://github.com/satijalab/seurat/issues/6654),[#6701](https://github.com/satijalab/seurat/issues/6701),[#6773](https://github.com/satijalab/seurat/issues/6773), [#7107](https://github.com/satijalab/seurat/issues/7107)) # Seurat 4.3.0 (2022-11-18) From 66e655a2c16e3719d855d60b12a1fac6c4ea1dea Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Thu, 6 Jul 2023 17:05:42 -0400 Subject: [PATCH 33/53] Fix for using counts based test on SCT assay --- R/differential_expression.R | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/R/differential_expression.R b/R/differential_expression.R index a67860abb..625f64f18 100644 --- a/R/differential_expression.R +++ b/R/differential_expression.R @@ -723,6 +723,13 @@ FindMarkers.SCTAssay <- function( yes = 'counts', no = slot ) + if (test.use %in% DEmethods_counts()){ + # set slot to counts + if (slot !="counts") { + message(paste0("Setting slot to counts for ", test.use, " (counts based test: ")) + slot <- "counts" + } + } if (recorrect_umi && length(x = levels(x = object)) > 1) { cell_attributes <- SCTResults(object = object, slot = "cell.attributes") observed_median_umis <- lapply( From c5d37bec13644cc6e69d25b2584ea7786e1b7de3 Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Thu, 6 Jul 2023 17:18:57 -0400 Subject: [PATCH 34/53] Update documentation; bump version; update news --- DESCRIPTION | 4 ++-- NAMESPACE | 1 + R/preprocessing.R | 2 +- man/FoldChange.Rd | 14 ++++++++++++++ man/SCTransform.Rd | 2 +- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index c8b3cc136..808c66074 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: Seurat -Version: 4.3.0.9007 -Date: 2023-06-02 +Version: 4.3.0.9008 +Date: 2023-07-06 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. Authors@R: c( diff --git a/NAMESPACE b/NAMESPACE index d74684414..d181345f8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -32,6 +32,7 @@ S3method(FindVariableFeatures,Seurat) S3method(FindVariableFeatures,default) S3method(FoldChange,Assay) S3method(FoldChange,DimReduc) +S3method(FoldChange,SCTAssay) S3method(FoldChange,Seurat) S3method(FoldChange,default) S3method(GetAssay,Seurat) diff --git a/R/preprocessing.R b/R/preprocessing.R index 8719625a5..b831c8555 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -3130,7 +3130,7 @@ SampleUMI <- function( #' Use regularized negative binomial regression to normalize UMI count data #' #' This function calls sctransform::vst. The sctransform package is available at -#' https://github.com/ChristophH/sctransform. +#' https://github.com/satijalab/sctransform. #' Use this function as an alternative to the NormalizeData, #' FindVariableFeatures, ScaleData workflow. Results are saved in a new assay #' (named SCT by default) with counts being (corrected) counts, data being log1p(counts), diff --git a/man/FoldChange.Rd b/man/FoldChange.Rd index edda396b5..e04def58b 100644 --- a/man/FoldChange.Rd +++ b/man/FoldChange.Rd @@ -4,6 +4,7 @@ \alias{FoldChange} \alias{FoldChange.default} \alias{FoldChange.Assay} +\alias{FoldChange.SCTAssay} \alias{FoldChange.DimReduc} \alias{FoldChange.Seurat} \title{Fold Change} @@ -26,6 +27,19 @@ FoldChange(object, ...) ... ) +\method{FoldChange}{SCTAssay}( + object, + cells.1, + cells.2, + features = NULL, + slot = "data", + pseudocount.use = 1, + fc.name = NULL, + mean.fxn = NULL, + base = 2, + ... +) + \method{FoldChange}{DimReduc}( object, cells.1, diff --git a/man/SCTransform.Rd b/man/SCTransform.Rd index 54d4c2e8a..abc8dac81 100644 --- a/man/SCTransform.Rd +++ b/man/SCTransform.Rd @@ -86,7 +86,7 @@ slot of the new assay. } \description{ This function calls sctransform::vst. The sctransform package is available at -https://github.com/ChristophH/sctransform. +https://github.com/satijalab/sctransform. Use this function as an alternative to the NormalizeData, FindVariableFeatures, ScaleData workflow. Results are saved in a new assay (named SCT by default) with counts being (corrected) counts, data being log1p(counts), From e5171f7a6b2b1145753b0b1606d16e30d6b0984d Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Fri, 7 Jul 2023 13:09:43 -0400 Subject: [PATCH 35/53] Add parallelization support for PrepSCTFindMarkers --- NAMESPACE | 2 ++ R/differential_expression.R | 71 +++++++++++++++++++++++++++---------- R/utilities.R | 37 +++++++++++++++++++ src/RcppExports.cpp | 2 +- 4 files changed, 93 insertions(+), 19 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index d181345f8..8528f5cc5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -405,6 +405,7 @@ importFrom(Matrix,rowMeans) importFrom(Matrix,rowSums) importFrom(Matrix,sparse.model.matrix) importFrom(Matrix,sparseMatrix) +importFrom(Matrix,summary) importFrom(Matrix,t) importFrom(RANN,nn2) importFrom(RColorBrewer,brewer.pal) @@ -673,6 +674,7 @@ importFrom(plotly,plot_ly) importFrom(plotly,raster2uri) importFrom(png,readPNG) importFrom(progressr,progressor) +importFrom(progressr,with_progress) importFrom(reticulate,import) importFrom(reticulate,py_module_available) importFrom(reticulate,py_set_seed) diff --git a/R/differential_expression.R b/R/differential_expression.R index 625f64f18..a8a38346c 100644 --- a/R/differential_expression.R +++ b/R/differential_expression.R @@ -2120,7 +2120,11 @@ PerformDE <- function( #' @param assay Assay name where for SCT objects are stored; Default is 'SCT' #' @param verbose Print messages and progress #' @importFrom Matrix Matrix +#' @importFrom pbapply pblapply +#' @importFrom future.apply future_lapply +#' @importFrom future nbrOfWorkers #' @importFrom sctransform correct_counts +#' @importFrom progressr progressor with_progress #' #' @return Returns a Seurat object with recorrected counts and data in the SCT assay. #' @export @@ -2148,6 +2152,16 @@ PerformDE <- function( #' ) #' PrepSCTFindMarkers <- function(object, assay = "SCT", verbose = TRUE) { + show_progressr <- FALSE + if (verbose && nbrOfWorkers() == 1) { + my.lapply <- pblapply + } else { + my.lapply <- future_lapply + } + if (verbose && nbrOfWorkers() > 1) { + show_progressr <- TRUE + } + if (length(x = levels(x = object[[assay]])) == 1) { if (verbose) { message("Only one SCT model is stored - skipping recalculating corrected counts") @@ -2214,27 +2228,49 @@ PrepSCTFindMarkers <- function(object, assay = "SCT", verbose = TRUE) { set_median_umi <- rep(min_median_umi, length(levels(x = object[[assay]]))) names(set_median_umi) <- levels(x = object[[assay]]) set_median_umi <- as.list(set_median_umi) + # correct counts - for (model_name in levels(x = object[[assay]])) { + my.correct_counts <- function(model_name, p=NULL){ model_genes <- rownames(x = model_pars_fit[[model_name]]) - x <- list( - model_str = model_str[[model_name]], - arguments = arguments[[model_name]], - model_pars_fit = as.matrix(x = model_pars_fit[[model_name]]), - cell_attr = cell_attr[[model_name]] - ) - cells <- rownames(x = cell_attr[[model_name]]) - umi <- raw_umi[model_genes, cells] + x <- list( + model_str = model_str[[model_name]], + arguments = arguments[[model_name]], + model_pars_fit = as.matrix(x = model_pars_fit[[model_name]]), + cell_attr = cell_attr[[model_name]] + ) + cells <- rownames(x = cell_attr[[model_name]]) + umi <- raw_umi[model_genes, cells] - umi_corrected <- correct_counts( - x = x, - umi = umi, - verbosity = 0, - scale_factor = min_median_umi - ) - corrected_counts[rownames(umi_corrected), colnames(umi_corrected)] <- umi_corrected + umi_corrected <- correct_counts( + x = x, + umi = umi, + verbosity = 0, + scale_factor = min_median_umi + ) + if (!is.null(x = p)){ + p(sprintf("model=%s", model_name)) + } + return(umi_corrected) } - corrected_data <- log1p(corrected_counts) + + if (show_progressr){ + with_progress({ + p <- progressor(steps = length(x = levels(x = object[[assay]]))) + corrected_counts.list <- my.lapply(X = levels(x = object[[assay]]), + FUN = my.correct_counts, + p = p) + names(x = corrected_counts.list) <- levels(x = object[[assay]]) + }) + } else { + corrected_counts.list <- my.lapply(X = levels(x = object[[assay]]), + FUN = my.correct_counts) + names(x = corrected_counts.list) <- levels(x = object[[assay]]) + } + + corrected_counts <- do.call(what = MergeSparseMatrices, args = corrected_counts.list) + corrected_counts.list <- NULL + + corrected_data <- log1p(x = corrected_counts) suppressWarnings({object <- SetAssayData(object = object, assay = assay, slot = "counts", @@ -2244,7 +2280,6 @@ PrepSCTFindMarkers <- function(object, assay = "SCT", verbose = TRUE) { slot = "data", new.data = corrected_data)}) SCTResults(object = object[[assay]], slot = "median_umi") <- set_median_umi - return(object) } diff --git a/R/utilities.R b/R/utilities.R index b546bc5d0..97065973d 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -2488,3 +2488,40 @@ ToNumeric <- function(x){ } return(x) } + + +# Merge a list of sparse matrixes +#' @importFrom Matrix summary sparseMatrix +MergeSparseMatrices <- function(...) { + + colname.new <- character() + rowname.new <- character() + x <- vector() + i <- numeric() + j <- numeric() + + for (mat in list(...)) { + colname.old <- colnames(x = mat) + rowname.old <- rownames(x = mat) + + # does not check if there are overlapping cells + colname.new <- union(x = colname.new, y = colname.old) + rowname.new <- union(x = rowname.new, y = rowname.old) + + colindex.new <- match(x = colname.old, table = colname.new) + rowindex.new <- match(x = rowname.old, table = rowname.new) + + ind <- summary(object = mat) + # Expand the list of indices and x + i <- c(i, rowindex.new[ind[,1]]) + j <- c(j, colindex.new[ind[,2]]) + x <- c(x, ind[,3]) + } + + merged.mat <- sparseMatrix(i=i, + j=j, + x=x, + dims=c(length(rowname.new), length(colname.new)), + dimnames=list(rowname.new, colname.new)) + return (merged.mat) +} diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 7a3302c6b..540e5c2d8 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -402,7 +402,7 @@ BEGIN_RCPP END_RCPP } -RcppExport SEXP isnull(SEXP); +RcppExport SEXP isnull(void *); static const R_CallMethodDef CallEntries[] = { {"_Seurat_RunModularityClusteringCpp", (DL_FUNC) &_Seurat_RunModularityClusteringCpp, 9}, From 9cd11672c6464358a0c4d29d00d73ff355e7819a Mon Sep 17 00:00:00 2001 From: Efi Papalexi Date: Wed, 12 Jul 2023 14:48:01 -0700 Subject: [PATCH 36/53] update function to be compatible with splitpipe v1.1.0 --- R/convenience.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/convenience.R b/R/convenience.R index ff0e91900..5c9367eca 100644 --- a/R/convenience.R +++ b/R/convenience.R @@ -391,7 +391,8 @@ SpecificDimPlot <- function(object, ...) { #' @export #' ReadParseBio <- function(data.dir, ...) { - mtx <- file.path(data.dir, "DGE.mtx") + file.dir <- list.files(path = data.dir, pattern = ".mtx") + mtx <- file.path(data.dir, file.dir) cells <- file.path(data.dir, "cell_metadata.csv") features <- file.path(data.dir, "all_genes.csv") return(ReadMtx( From 2d7269ef64eb147b5c23ed1862f1aef3d40449dd Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Wed, 12 Jul 2023 17:01:37 -0600 Subject: [PATCH 37/53] Correct dropped expression when loading CosMx data The LoadNanostring() function drops the first gene from the assay expression matrix. This patch corrects this and no longer drops this gene from the expression matrix. --- R/preprocessing.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/preprocessing.R b/R/preprocessing.R index 45baa4254..506f65322 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -1870,7 +1870,7 @@ ReadNanostring <- function( tx <- subset(tx, select = -c(fov, cell_ID)) } - tx <- as.data.frame(t(x = as.matrix(x = tx[, -1, drop = FALSE]))) + tx <- as.data.frame(t(x = as.matrix(x = tx))) if (!is.na(x = genes.filter)) { ptx( message = paste("Filtering genes with pattern", genes.filter), From 18840fd637b007d343dc0de5bd15ba38775ff44c Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Wed, 12 Jul 2023 20:17:47 -0400 Subject: [PATCH 38/53] Update news; bump version --- DESCRIPTION | 4 ++-- NEWS.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 808c66074..69d647cd8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: Seurat -Version: 4.3.0.9008 -Date: 2023-07-06 +Version: 4.3.0.9009 +Date: 2023-07-12 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. Authors@R: c( diff --git a/NEWS.md b/NEWS.md index a2c2d9147..19ed1c84f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,7 @@ - Fix fold change calculation for assays ([#739](https://github.com/satijalab/seurat-private/pull/739)) - Fix `pt.size` bug when rasterization is set to true ([#7379](https://github.com/satijalab/seurat/issues/7379)) - Fix `FoldChange` and `FindMarkers` to support all normalization approaches ([#7115](https://github.com/satijalab/seurat/pull/7115),[#7110](https://github.com/satijalab/seurat/issues/7110),[#7095](https://github.com/satijalab/seurat/issues/7095),[#6976](https://github.com/satijalab/seurat/issues/6976),[#6654](https://github.com/satijalab/seurat/issues/6654),[#6701](https://github.com/satijalab/seurat/issues/6701),[#6773](https://github.com/satijalab/seurat/issues/6773), [#7107](https://github.com/satijalab/seurat/issues/7107)) +- Fix for handling newer ParseBio formats in `ReadParseBio` ([#7565](https://github.com/satijalab/seurat/pull/7565)) # Seurat 4.3.0 (2022-11-18) From 8830ed9b71d5a03792d7e22440134ddc3310edcf Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Thu, 13 Jul 2023 16:09:42 -0400 Subject: [PATCH 39/53] Update SCT v2 vignette --- man/reexports.Rd | 2 +- vignettes/sctransform_v2_vignette.Rmd | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/man/reexports.Rd b/man/reexports.Rd index e94d58954..ed66b98a1 100644 --- a/man/reexports.Rd +++ b/man/reexports.Rd @@ -70,6 +70,6 @@ These objects are imported from other packages. Follow the links below to see their documentation. \describe{ - \item{SeuratObject}{\code{\link[SeuratObject:set-if-null]{\%||\%}}, \code{\link[SeuratObject:set-if-null]{\%iff\%}}, \code{\link[SeuratObject]{AddMetaData}}, \code{\link[SeuratObject]{as.Graph}}, \code{\link[SeuratObject]{as.Neighbor}}, \code{\link[SeuratObject]{as.Seurat}}, \code{\link[SeuratObject]{as.sparse}}, \code{\link[SeuratObject:ObjectAccess]{Assays}}, \code{\link[SeuratObject]{Cells}}, \code{\link[SeuratObject]{CellsByIdentities}}, \code{\link[SeuratObject]{Command}}, \code{\link[SeuratObject]{CreateAssayObject}}, \code{\link[SeuratObject]{CreateDimReducObject}}, \code{\link[SeuratObject]{CreateSeuratObject}}, \code{\link[SeuratObject]{DefaultAssay}}, \code{\link[SeuratObject:DefaultAssay]{DefaultAssay<-}}, \code{\link[SeuratObject]{Distances}}, \code{\link[SeuratObject]{Embeddings}}, \code{\link[SeuratObject]{FetchData}}, \code{\link[SeuratObject:AssayData]{GetAssayData}}, \code{\link[SeuratObject]{GetImage}}, \code{\link[SeuratObject]{GetTissueCoordinates}}, \code{\link[SeuratObject:VariableFeatures]{HVFInfo}}, \code{\link[SeuratObject]{Idents}}, \code{\link[SeuratObject:Idents]{Idents<-}}, \code{\link[SeuratObject]{Images}}, \code{\link[SeuratObject]{Index}}, \code{\link[SeuratObject:Index]{Index<-}}, \code{\link[SeuratObject]{Indices}}, \code{\link[SeuratObject]{IsGlobal}}, \code{\link[SeuratObject]{JS}}, \code{\link[SeuratObject:JS]{JS<-}}, \code{\link[SeuratObject]{Key}}, \code{\link[SeuratObject:Key]{Key<-}}, \code{\link[SeuratObject]{Loadings}}, \code{\link[SeuratObject:Loadings]{Loadings<-}}, \code{\link[SeuratObject]{LogSeuratCommand}}, \code{\link[SeuratObject]{Misc}}, \code{\link[SeuratObject:Misc]{Misc<-}}, \code{\link[SeuratObject:ObjectAccess]{Neighbors}}, \code{\link[SeuratObject]{Project}}, \code{\link[SeuratObject:Project]{Project<-}}, \code{\link[SeuratObject]{Radius}}, \code{\link[SeuratObject:ObjectAccess]{Reductions}}, \code{\link[SeuratObject]{RenameCells}}, \code{\link[SeuratObject:Idents]{RenameIdents}}, \code{\link[SeuratObject:Idents]{ReorderIdent}}, \code{\link[SeuratObject]{RowMergeSparseMatrices}}, \code{\link[SeuratObject:AssayData]{SetAssayData}}, \code{\link[SeuratObject:Idents]{SetIdent}}, \code{\link[SeuratObject:VariableFeatures]{SpatiallyVariableFeatures}}, \code{\link[SeuratObject:Idents]{StashIdent}}, \code{\link[SeuratObject]{Stdev}}, \code{\link[SeuratObject:VariableFeatures]{SVFInfo}}, \code{\link[SeuratObject]{Tool}}, \code{\link[SeuratObject:Tool]{Tool<-}}, \code{\link[SeuratObject]{UpdateSeuratObject}}, \code{\link[SeuratObject]{VariableFeatures}}, \code{\link[SeuratObject:VariableFeatures]{VariableFeatures<-}}, \code{\link[SeuratObject]{WhichCells}}} + \item{SeuratObject}{\code{\link[SeuratObject:set-if-null]{\%iff\%}}, \code{\link[SeuratObject:set-if-null]{\%||\%}}, \code{\link[SeuratObject]{AddMetaData}}, \code{\link[SeuratObject:ObjectAccess]{Assays}}, \code{\link[SeuratObject]{Cells}}, \code{\link[SeuratObject]{CellsByIdentities}}, \code{\link[SeuratObject]{Command}}, \code{\link[SeuratObject]{CreateAssayObject}}, \code{\link[SeuratObject]{CreateDimReducObject}}, \code{\link[SeuratObject]{CreateSeuratObject}}, \code{\link[SeuratObject]{DefaultAssay}}, \code{\link[SeuratObject:DefaultAssay]{DefaultAssay<-}}, \code{\link[SeuratObject]{Distances}}, \code{\link[SeuratObject]{Embeddings}}, \code{\link[SeuratObject]{FetchData}}, \code{\link[SeuratObject:AssayData]{GetAssayData}}, \code{\link[SeuratObject]{GetImage}}, \code{\link[SeuratObject]{GetTissueCoordinates}}, \code{\link[SeuratObject:VariableFeatures]{HVFInfo}}, \code{\link[SeuratObject]{Idents}}, \code{\link[SeuratObject:Idents]{Idents<-}}, \code{\link[SeuratObject]{Images}}, \code{\link[SeuratObject]{Index}}, \code{\link[SeuratObject:Index]{Index<-}}, \code{\link[SeuratObject]{Indices}}, \code{\link[SeuratObject]{IsGlobal}}, \code{\link[SeuratObject]{JS}}, \code{\link[SeuratObject:JS]{JS<-}}, \code{\link[SeuratObject]{Key}}, \code{\link[SeuratObject:Key]{Key<-}}, \code{\link[SeuratObject]{Loadings}}, \code{\link[SeuratObject:Loadings]{Loadings<-}}, \code{\link[SeuratObject]{LogSeuratCommand}}, \code{\link[SeuratObject]{Misc}}, \code{\link[SeuratObject:Misc]{Misc<-}}, \code{\link[SeuratObject:ObjectAccess]{Neighbors}}, \code{\link[SeuratObject]{Project}}, \code{\link[SeuratObject:Project]{Project<-}}, \code{\link[SeuratObject]{Radius}}, \code{\link[SeuratObject:ObjectAccess]{Reductions}}, \code{\link[SeuratObject]{RenameCells}}, \code{\link[SeuratObject:Idents]{RenameIdents}}, \code{\link[SeuratObject:Idents]{ReorderIdent}}, \code{\link[SeuratObject]{RowMergeSparseMatrices}}, \code{\link[SeuratObject:VariableFeatures]{SVFInfo}}, \code{\link[SeuratObject:AssayData]{SetAssayData}}, \code{\link[SeuratObject:Idents]{SetIdent}}, \code{\link[SeuratObject:VariableFeatures]{SpatiallyVariableFeatures}}, \code{\link[SeuratObject:Idents]{StashIdent}}, \code{\link[SeuratObject]{Stdev}}, \code{\link[SeuratObject]{Tool}}, \code{\link[SeuratObject:Tool]{Tool<-}}, \code{\link[SeuratObject]{UpdateSeuratObject}}, \code{\link[SeuratObject]{VariableFeatures}}, \code{\link[SeuratObject:VariableFeatures]{VariableFeatures<-}}, \code{\link[SeuratObject]{WhichCells}}, \code{\link[SeuratObject]{as.Graph}}, \code{\link[SeuratObject]{as.Neighbor}}, \code{\link[SeuratObject]{as.Seurat}}, \code{\link[SeuratObject]{as.sparse}}} }} diff --git a/vignettes/sctransform_v2_vignette.Rmd b/vignettes/sctransform_v2_vignette.Rmd index 42ecd064d..c6bd485e2 100644 --- a/vignettes/sctransform_v2_vignette.Rmd +++ b/vignettes/sctransform_v2_vignette.Rmd @@ -33,9 +33,9 @@ knitr::opts_chunk$set( ## TL;DR -We recently introduced [sctransform](https://genomebiology.biomedcentral.com/articles/10.1186/s13059-019-1874-1) to perform normalization and variance stabilization of scRNA-seq datasets. We now release an updated version ('v2'), based on [our broad analysis](https://www.biorxiv.org/content/10.1101/2021.07.07.451498v1) of 59 scRNA-seq datasets spanning a range of technologies, systems, and sequencing depths. This update improves speed and memory consumption, the stability of parameter estimates, the identification of variable features, and the the ability to perform downstream differential expression analyses. +We recently introduced [sctransform](https://genomebiology.biomedcentral.com/articles/10.1186/s13059-019-1874-1) to perform normalization and variance stabilization of scRNA-seq datasets. We now release an updated version ('v2'), based on [our broad analysis](https://genomebiology.biomedcentral.com/articles/10.1186/s13059-021-02584-9) of 59 scRNA-seq datasets spanning a range of technologies, systems, and sequencing depths. This update improves speed and memory consumption, the stability of parameter estimates, the identification of variable features, and the the ability to perform downstream differential expression analyses. -Users can install sctransform v2 from CRAN (sctransform v0.3.3) and invoke the use of the updated method via the `vst.flavor` argument. +Users can install sctransform v2 from CRAN (sctransform v0.3.3+) and invoke the use of the updated method via the `vst.flavor` argument. ```{r tldr, eval=FALSE} # install sctransform >= 0.3.3 @@ -56,7 +56,7 @@ In this vignette, we use [sctransform v2](https://github.com/satijalab/sctransfo ## Install sctransform -We will install sctransform v2 from CRAN (v0.3.3). We will also install the [glmGamPoi](https://bioconductor.org/packages/release/bioc/html/glmGamPoi.html) package which substantially improves the speed of the learning procedure. +We will install sctransform v2 from CRAN. We will also install the [glmGamPoi](https://bioconductor.org/packages/release/bioc/html/glmGamPoi.html) package which substantially improves the speed of the learning procedure. ```{r results='hide', message=FALSE, warning=FALSE} # install glmGamPoi From 4f0eaaa5fbe73691746fc09219d1670bb9a6e00e Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Thu, 13 Jul 2023 16:54:45 -0400 Subject: [PATCH 40/53] Bump version; update NEWS --- DESCRIPTION | 4 ++-- NEWS.md | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 69d647cd8..da86fa750 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: Seurat -Version: 4.3.0.9009 -Date: 2023-07-12 +Version: 4.3.0.9010 +Date: 2023-07-13 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. Authors@R: c( diff --git a/NEWS.md b/NEWS.md index 19ed1c84f..91a06a125 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,8 @@ # Unreleased + +## Added +- Added parallelization support with speed improvements for `PrepSCTFindMarkers` + ## Changes - Fix bug in `as.Seurat.SingleCellExperiment()` ([#6692](https://github.com/satijalab/seurat/issues/6692)) - Support for Visium probe information introduced in Spaceranger 2.1 ([#7141](https://github.com/satijalab/seurat/pull/7141)) From 7cc0972aad6e982f1e01ea0a1ee2e6213c9c430c Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Thu, 13 Jul 2023 17:50:01 -0400 Subject: [PATCH 41/53] Default to no progressr updates in PrepSCTFindMarkers --- DESCRIPTION | 2 +- NAMESPACE | 1 - R/differential_expression.R | 26 +++++--------------------- man/PrepSCTFindMarkers.Rd | 26 ++++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index da86fa750..50aea79ec 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: Seurat -Version: 4.3.0.9010 +Version: 4.3.0.9011 Date: 2023-07-13 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. diff --git a/NAMESPACE b/NAMESPACE index 8528f5cc5..5792a4bf3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -674,7 +674,6 @@ importFrom(plotly,plot_ly) importFrom(plotly,raster2uri) importFrom(png,readPNG) importFrom(progressr,progressor) -importFrom(progressr,with_progress) importFrom(reticulate,import) importFrom(reticulate,py_module_available) importFrom(reticulate,py_set_seed) diff --git a/R/differential_expression.R b/R/differential_expression.R index a8a38346c..f8e4b5ecc 100644 --- a/R/differential_expression.R +++ b/R/differential_expression.R @@ -2124,12 +2124,13 @@ PerformDE <- function( #' @importFrom future.apply future_lapply #' @importFrom future nbrOfWorkers #' @importFrom sctransform correct_counts -#' @importFrom progressr progressor with_progress #' #' @return Returns a Seurat object with recorrected counts and data in the SCT assay. #' @export #' #' @concept differential_expression +#' @template section-progressr +#' @template section-future #' @examples #' data("pbmc_small") #' pbmc_small1 <- SCTransform(object = pbmc_small, variable.features.n = 20) @@ -2152,16 +2153,11 @@ PerformDE <- function( #' ) #' PrepSCTFindMarkers <- function(object, assay = "SCT", verbose = TRUE) { - show_progressr <- FALSE if (verbose && nbrOfWorkers() == 1) { my.lapply <- pblapply } else { my.lapply <- future_lapply } - if (verbose && nbrOfWorkers() > 1) { - show_progressr <- TRUE - } - if (length(x = levels(x = object[[assay]])) == 1) { if (verbose) { message("Only one SCT model is stored - skipping recalculating corrected counts") @@ -2252,21 +2248,9 @@ PrepSCTFindMarkers <- function(object, assay = "SCT", verbose = TRUE) { } return(umi_corrected) } - - if (show_progressr){ - with_progress({ - p <- progressor(steps = length(x = levels(x = object[[assay]]))) - corrected_counts.list <- my.lapply(X = levels(x = object[[assay]]), - FUN = my.correct_counts, - p = p) - names(x = corrected_counts.list) <- levels(x = object[[assay]]) - }) - } else { - corrected_counts.list <- my.lapply(X = levels(x = object[[assay]]), - FUN = my.correct_counts) - names(x = corrected_counts.list) <- levels(x = object[[assay]]) - } - + corrected_counts.list <- my.lapply(X = levels(x = object[[assay]]), + FUN = my.correct_counts) + names(x = corrected_counts.list) <- levels(x = object[[assay]]) corrected_counts <- do.call(what = MergeSparseMatrices, args = corrected_counts.list) corrected_counts.list <- NULL diff --git a/man/PrepSCTFindMarkers.Rd b/man/PrepSCTFindMarkers.Rd index efec41676..c24af42ce 100644 --- a/man/PrepSCTFindMarkers.Rd +++ b/man/PrepSCTFindMarkers.Rd @@ -24,6 +24,31 @@ as the sequencing depth covariate. The counts slot of the SCT assay is replaced with recorrected counts and the data slot is replaced with log1p of recorrected counts. } +\section{Progress Updates with \pkg{progressr}}{ + +This function uses +\href{https://cran.r-project.org/package=progressr}{\pkg{progressr}} to +render status updates and progress bars. To enable progress updates, wrap +the function call in \code{\link[progressr]{with_progress}} or run +\code{\link[progressr:handlers]{handlers(global = TRUE)}} before running +this function. For more details about \pkg{progressr}, please read +\href{https://progressr.futureverse.org/articles/progressr-intro.html}{\code{vignette("progressr-intro")}} +} + +\section{Parallelization with \pkg{future}}{ + +This function uses +\href{https://cran.r-project.org/package=future}{\pkg{future}} to enable +parallelization. Parallelization strategies can be set using +\code{\link[future]{plan}}. Common plans include \dQuote{\code{sequential}} +for non-parallelized processing or \dQuote{\code{multisession}} for parallel +evaluation using multiple \R sessions; for other plans, see the +\dQuote{Implemented evaluation strategies} section of +\code{\link[future:plan]{?future::plan}}. For a more thorough introduction +to \pkg{future}, see +\href{https://future.futureverse.org/articles/future-1-overview.html}{\code{vignette("future-1-overview")}} +} + \examples{ data("pbmc_small") pbmc_small1 <- SCTransform(object = pbmc_small, variable.features.n = 20) @@ -47,3 +72,4 @@ markers_subset <- FindMarkers( } \concept{differential_expression} +\concept{future} From 47740adadc5d1e0772db5d3fbb46331c623378ef Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Thu, 13 Jul 2023 18:23:22 -0400 Subject: [PATCH 42/53] Simplify PrepSCTFindmarkers --- R/differential_expression.R | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/R/differential_expression.R b/R/differential_expression.R index f8e4b5ecc..220172f44 100644 --- a/R/differential_expression.R +++ b/R/differential_expression.R @@ -2226,7 +2226,7 @@ PrepSCTFindMarkers <- function(object, assay = "SCT", verbose = TRUE) { set_median_umi <- as.list(set_median_umi) # correct counts - my.correct_counts <- function(model_name, p=NULL){ + my.correct_counts <- function(model_name){ model_genes <- rownames(x = model_pars_fit[[model_name]]) x <- list( model_str = model_str[[model_name]], @@ -2243,9 +2243,6 @@ PrepSCTFindMarkers <- function(object, assay = "SCT", verbose = TRUE) { verbosity = 0, scale_factor = min_median_umi ) - if (!is.null(x = p)){ - p(sprintf("model=%s", model_name)) - } return(umi_corrected) } corrected_counts.list <- my.lapply(X = levels(x = object[[assay]]), From 47bf3c27e1c3818911e3c3c43feb191db070cb03 Mon Sep 17 00:00:00 2001 From: AustinHartman Date: Fri, 21 Jul 2023 15:52:51 -0400 Subject: [PATCH 43/53] bump version; update NEWS --- DESCRIPTION | 4 ++-- NEWS.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 50aea79ec..4c1678765 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: Seurat -Version: 4.3.0.9011 -Date: 2023-07-13 +Version: 4.3.0.9012 +Date: 2023-07-21 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. Authors@R: c( diff --git a/NEWS.md b/NEWS.md index 91a06a125..6a28c5159 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ ## Added - Added parallelization support with speed improvements for `PrepSCTFindMarkers` +- Fix bug in `LoadNanostring`([#7566](https://github.com/satijalab/seurat/pull/7566)) ## Changes - Fix bug in `as.Seurat.SingleCellExperiment()` ([#6692](https://github.com/satijalab/seurat/issues/6692)) From 4616cc67a461933c86d25f4df70ab1399b45148b Mon Sep 17 00:00:00 2001 From: Paul Hoffman Date: Fri, 15 Sep 2023 19:07:14 -0400 Subject: [PATCH 44/53] Update `Read10X_Image()` Fix reading scale factors in `Read10X_Image()` resolves satijalab/seurat#7706 --- R/preprocessing.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/preprocessing.R b/R/preprocessing.R index a0b2bbd6b..b17506ec3 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -1191,7 +1191,7 @@ Read10X_Image <- function(image.dir, image.name = "tissue_lowres_image.png", fil Class = 'VisiumV1', image = image, scale.factors = scalefactors( - spot = scale.factors$tissue_hires_scalef, + spot = scale.factors$spot_diameter_fullres, fiducial = scale.factors$fiducial_diameter_fullres, hires = scale.factors$tissue_hires_scalef, scale.factors$tissue_lowres_scalef From c6c0b306d9008883b89bd95da3e7c2715f23101b Mon Sep 17 00:00:00 2001 From: Paul Hoffman Date: Fri, 15 Sep 2023 19:07:20 -0400 Subject: [PATCH 45/53] Bump develop version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4c1678765..12b6172af 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: Seurat -Version: 4.3.0.9012 +Version: 4.3.0.9013 Date: 2023-07-21 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. From a55eb2fb1329f6ca6b3a2ff62b086dac27ed8f70 Mon Sep 17 00:00:00 2001 From: AustinHartman Date: Fri, 15 Sep 2023 20:27:29 -0400 Subject: [PATCH 46/53] update news, readme, desc --- DESCRIPTION | 4 ++-- NEWS.md | 2 +- README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 12b6172af..f56d32af1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: Seurat -Version: 4.3.0.9013 -Date: 2023-07-21 +Version: 4.4.0 +Date: 2023-09-15 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. Authors@R: c( diff --git a/NEWS.md b/NEWS.md index 6a28c5159..94b32876f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# Unreleased +# Seurat 4.4.0 (2023-09-15) ## Added - Added parallelization support with speed improvements for `PrepSCTFindMarkers` diff --git a/README.md b/README.md index 427ab3a20..f9dc4e4d7 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![CRAN Version](https://www.r-pkg.org/badges/version/Seurat)](https://cran.r-project.org/package=Seurat) [![CRAN Downloads](https://cranlogs.r-pkg.org/badges/Seurat)](https://cran.r-project.org/package=Seurat) -# Seurat v4.3.0 +# Seurat v4.4.0 Seurat is an R toolkit for single cell genomics, developed and maintained by the Satija Lab at NYGC. From cfd7eca179b314938ab83376a3d13f4ca96ab409 Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Wed, 27 Sep 2023 16:29:39 -0400 Subject: [PATCH 47/53] Import purrr for imap --- R/preprocessing.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/preprocessing.R b/R/preprocessing.R index b17506ec3..d19876978 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -488,6 +488,7 @@ GetResidual <- function( #' @importFrom png readPNG #' @importFrom grid rasterGrob #' @importFrom jsonlite fromJSON +#' @importFrom purrr imap #' #' @export #' @concept preprocessing @@ -519,7 +520,7 @@ Load10X_Spatial <- function( if (to.upper) { data <- imap(data, ~{ - rownames(.x) <- toupper(rownames(.x)) + rownames(.x) <- toupper(x = rownames(.x)) .x }) } From 643a57d2e10eda962b7b8ae4d0e35b5f8a590b21 Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Wed, 27 Sep 2023 16:30:12 -0400 Subject: [PATCH 48/53] Fix checks for rasterization messages Fixes https://github.com/satijalab/seurat/pull/7842 --- R/visualization.R | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/R/visualization.R b/R/visualization.R index c2e61c88c..d3edd0f4a 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -4009,7 +4009,7 @@ SpatialPlot <- function( } # Get feature max for individual feature - if (!(is.null(x = keep.scale)) && keep.scale == "feature" && class(x = data[, features[j]]) != "factor") { + if (!(is.null(x = keep.scale)) && keep.scale == "feature" && !inherits(x = data[, features[j]], what = "factor") ) { max.feature.value <- max(data[, features[j]]) } @@ -4085,7 +4085,7 @@ SpatialPlot <- function( } # Plot multiple images depending on keep.scale - if (!(is.null(x = keep.scale)) && class(x = data[, features[j]]) != "factor") { + if (!(is.null(x = keep.scale)) && !inherits(x = data[, features[j]], "factor")) { plot <- suppressMessages(plot & scale_fill_gradientn(colors = SpatialColors(n = 100), limits = c(NA, max.feature.value))) } @@ -4885,7 +4885,7 @@ AutoPointSize <- function(data, raster = NULL) { #' hexadecimal codes #' @param threshold Intensity threshold for light/dark cutoff; intensities #' greater than \code{theshold} yield \code{dark}, others yield \code{light} -#' @param w3c Use \href{http://www.w3.org/TR/WCAG20/}{W3C} formula for calculating +#' @param w3c Use \href{https://www.w3.org/TR/WCAG20/}{W3C} formula for calculating #' background text color; ignores \code{threshold} #' @param dark Color for dark text #' @param light Color for light text @@ -7802,7 +7802,7 @@ globalVariables(names = '..density..', package = 'Seurat') #' @param cols An optional vector of colors to use #' @param pt.size Point size for the plot #' @param smooth Make a smoothed scatter plot -#' @param rows.highight A vector of rows to highlight (like cells.highlight in +#' @param rows.highlight A vector of rows to highlight (like cells.highlight in #' \code{\link{SingleDimPlot}}) #' @param legend.title Optional legend title #' @param raster Convert points to raster format, default is \code{NULL} @@ -7842,7 +7842,7 @@ SingleCorPlot <- function( jitter = TRUE ) { pt.size <- pt.size %||% AutoPointSize(data = data, raster = raster) - if ((nrow(x = data) > 1e5) & !isFALSE(raster)){ + if ((nrow(x = data) > 1e5) & !is.null(x = raster)){ message("Rasterizing points since number of points exceeds 100,000.", "\nTo disable this behavior set `raster=FALSE`") } @@ -8051,7 +8051,7 @@ SingleDimPlot <- function( raster = NULL, raster.dpi = NULL ) { - if ((nrow(x = data) > 1e5) & !isFALSE(raster)){ + if ((nrow(x = data) > 1e5) & is.null(x = raster)){ message("Rasterizing points since number of points exceeds 100,000.", "\nTo disable this behavior set `raster=FALSE`") } @@ -8245,7 +8245,7 @@ SingleExIPlot <- function( if (PackageCheck('ggrastr', error = FALSE)) { # Set rasterization to true if ggrastr is installed and # number of points exceeds 100,000 - if ((nrow(x = data) > 1e5) & !isFALSE(raster)){ + if ((nrow(x = data) > 1e5) & is.null(x = raster)){ message("Rasterizing points since number of points exceeds 100,000.", "\nTo disable this behavior set `raster=FALSE`") # change raster to TRUE From e577c8f737c903f20ee68c292e5ea0586e24b96b Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Wed, 27 Sep 2023 16:31:35 -0400 Subject: [PATCH 49/53] Updates for latest CRAN checks --- inst/CITATION | 106 ++++++++++++++++++++++++-------------------- src/Makevars | 2 +- src/integration.cpp | 34 +++++++++----- 3 files changed, 80 insertions(+), 62 deletions(-) diff --git a/inst/CITATION b/inst/CITATION index 4aaa4d01f..bcc1f5593 100644 --- a/inst/CITATION +++ b/inst/CITATION @@ -1,31 +1,33 @@ citHeader("To cite Seurat in publications, please use:") -citEntry(entry = "article", - author = personList(as.person("Yuhan Hao"), - as.person("Stephanie Hao"), - as.person("Erica Andersen-Nissen"), - as.person("William M. Mauck III"), - as.person("Shiwei Zheng"), - as.person("Andrew Butler"), - as.person("Maddie J. Lee"), - as.person("Aaron J. Wilk"), - as.person("Charlotte Darby"), - as.person("Michael Zagar"), - as.person("Paul Hoffman"), - as.person("Marlon Stoeckius"), - as.person("Efthymia Papalexi"), - as.person("Eleni P. Mimitou"), - as.person("Jaison Jain"), - as.person("Avi Srivastava"), - as.person("Tim Stuart"), - as.person("Lamar B. Fleming"), - as.person("Bertrand Yeung"), - as.person("Angela J. Rogers"), - as.person("Juliana M. McElrath"), - as.person("Catherine A. Blish"), - as.person("Raphael Gottardo"), - as.person("Peter Smibert"), - as.person("Rahul Satija")), +bibentry(bibtype = "article", + author = c( + as.person("Yuhan Hao"), + as.person("Stephanie Hao"), + as.person("Erica Andersen-Nissen"), + as.person("William M. Mauck III"), + as.person("Shiwei Zheng"), + as.person("Andrew Butler"), + as.person("Maddie J. Lee"), + as.person("Aaron J. Wilk"), + as.person("Charlotte Darby"), + as.person("Michael Zagar"), + as.person("Paul Hoffman"), + as.person("Marlon Stoeckius"), + as.person("Efthymia Papalexi"), + as.person("Eleni P. Mimitou"), + as.person("Jaison Jain"), + as.person("Avi Srivastava"), + as.person("Tim Stuart"), + as.person("Lamar B. Fleming"), + as.person("Bertrand Yeung"), + as.person("Angela J. Rogers"), + as.person("Juliana M. McElrath"), + as.person("Catherine A. Blish"), + as.person("Raphael Gottardo"), + as.person("Peter Smibert"), + as.person("Rahul Satija") + ), title = "Integrated analysis of multimodal single-cell data", journal = "Cell", year = "2021", @@ -34,17 +36,19 @@ citEntry(entry = "article", textVersion = "Hao and Hao et al. Integrated analysis of multimodal single-cell data. Cell (2021) [Seurat V4]" ) -citEntry(entry = "article", - author = personList(as.person("Tim Stuart"), - as.person("Andrew Butler"), - as.person("Paul Hoffman"), - as.person("Christoph Hafemeister"), - as.person("Efthymia Papalexi"), - as.person("William M Mauck III"), - as.person("Yuhan Hao"), - as.person("Marlon Stoeckius"), - as.person("Peter Smibert"), - as.person("Rahul Satija")), +bibentry(bibtype = "article", + author = c( + as.person("Tim Stuart"), + as.person("Andrew Butler"), + as.person("Paul Hoffman"), + as.person("Christoph Hafemeister"), + as.person("Efthymia Papalexi"), + as.person("William M Mauck III"), + as.person("Yuhan Hao"), + as.person("Marlon Stoeckius"), + as.person("Peter Smibert"), + as.person("Rahul Satija") + ), title = "Comprehensive Integration of Single-Cell Data", journal = "Cell", year = "2019", @@ -55,12 +59,14 @@ citEntry(entry = "article", textVersion = "Stuart and Butler et al. Comprehensive Integration of Single-Cell Data. Cell (2019) [Seurat V3]" ) -citEntry(entry = "article", - author = personList(as.person("Andrew Butler"), - as.person("Paul Hoffman"), - as.person("Peter Smibert"), - as.person("Efthymia Papalexi"), - as.person("Rahul Satija")), +bibentry(bibtype = "article", + author = c( + as.person("Andrew Butler"), + as.person("Paul Hoffman"), + as.person("Peter Smibert"), + as.person("Efthymia Papalexi"), + as.person("Rahul Satija") + ), title = "Integrating single-cell transcriptomic data across different conditions, technologies, and species", journal = "Nature Biotechnology", year = "2018", @@ -71,12 +77,14 @@ citEntry(entry = "article", textVersion = "Butler et al. Integrating single-cell transcriptomic data across different conditions, technologies, and species. Nat Biotechnol (2018) [Seurat V2]" ) -citEntry(entry = "article", - author = personList(as.person("Rahul Satija"), - as.person("Jeffrey A Farrell"), - as.person("David Gennert"), - as.person("Alexander F Schier"), - as.person("Aviv Regev")), +bibentry(bibtype = "article", + author = c( + as.person("Rahul Satija"), + as.person("Jeffrey A Farrell"), + as.person("David Gennert"), + as.person("Alexander F Schier"), + as.person("Aviv Regev") + ), title = "Spatial reconstruction of single-cell gene expression data", journal = "Nature Biotechnology", year = "2015", diff --git a/src/Makevars b/src/Makevars index a7f35101d..e9d976546 100644 --- a/src/Makevars +++ b/src/Makevars @@ -1 +1 @@ -CXX_STD = CXX11 +CXX_STD = CXX17 diff --git a/src/integration.cpp b/src/integration.cpp index 0b613b0be..0a2b59580 100644 --- a/src/integration.cpp +++ b/src/integration.cpp @@ -11,15 +11,15 @@ typedef Eigen::Triplet T; // [[Rcpp::export(rng = false)]] Eigen::SparseMatrix FindWeightsC( - NumericVector cells2, - Eigen::MatrixXd distances, - std::vector anchor_cells2, - std::vector integration_matrix_rownames, - Eigen::MatrixXd cell_index, - Eigen::VectorXd anchor_score, - double min_dist, - double sd, - bool display_progress + NumericVector cells2, + Eigen::MatrixXd distances, + std::vector anchor_cells2, + std::vector integration_matrix_rownames, + Eigen::MatrixXd cell_index, + Eigen::VectorXd anchor_score, + double min_dist, + double sd, + bool display_progress ) { std::vector tripletList; tripletList.reserve(anchor_cells2.size() * 10); @@ -87,14 +87,24 @@ Eigen::SparseMatrix FindWeightsC( // [[Rcpp::export(rng = false)]] Eigen::SparseMatrix IntegrateDataC( - Eigen::SparseMatrix integration_matrix, - Eigen::SparseMatrix weights, - Eigen::SparseMatrix expression_cells2 + Eigen::SparseMatrix integration_matrix, + Eigen::SparseMatrix weights, + Eigen::SparseMatrix expression_cells2 ) { Eigen::SparseMatrix corrected = expression_cells2 - weights.transpose() * integration_matrix; return(corrected); } +template +std::vector sort_indexes(const std::vector &v) { + // initialize original index locations + std::vector idx(v.size()); + std::iota(idx.begin(), idx.end(), 0); + std::stable_sort(idx.begin(), idx.end(), + [&v](size_t i1, size_t i2) {return v[i1] < v[i2];}); + return idx; +} + // [[Rcpp::export]] std::vector ScoreHelper( Eigen::SparseMatrix snn, From aab57dbe2264c0cd5db1aeece8b8a13233c96b94 Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Wed, 27 Sep 2023 16:31:49 -0400 Subject: [PATCH 50/53] Documentation updates --- man/BGTextColor.Rd | 2 +- man/CreateSCTAssayObject.Rd | 4 ++-- man/STARmap-class.Rd | 5 ++++- man/SingleCorPlot.Rd | 6 +++--- man/SlideSeq-class.Rd | 5 ++++- man/reexports.Rd | 2 +- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/man/BGTextColor.Rd b/man/BGTextColor.Rd index 99130d0dc..823f57dd1 100644 --- a/man/BGTextColor.Rd +++ b/man/BGTextColor.Rd @@ -22,7 +22,7 @@ hexadecimal codes} \item{threshold}{Intensity threshold for light/dark cutoff; intensities greater than \code{theshold} yield \code{dark}, others yield \code{light}} -\item{w3c}{Use \href{http://www.w3.org/TR/WCAG20/}{W3C} formula for calculating +\item{w3c}{Use \href{https://www.w3.org/TR/WCAG20/}{W3C} formula for calculating background text color; ignores \code{threshold}} \item{dark}{Color for dark text} diff --git a/man/CreateSCTAssayObject.Rd b/man/CreateSCTAssayObject.Rd index c3c1406c8..70f30f633 100644 --- a/man/CreateSCTAssayObject.Rd +++ b/man/CreateSCTAssayObject.Rd @@ -25,10 +25,10 @@ CreateSCTAssayObject( \item{min.cells}{Include features detected in at least this many cells. Will subset the counts matrix as well. To reintroduce excluded features, create a -new object with a lower cutoff.} +new object with a lower cutoff} \item{min.features}{Include cells where at least this many features are -detected.} +detected} \item{SCTModel.list}{list of SCTModels} } diff --git a/man/STARmap-class.Rd b/man/STARmap-class.Rd index 7984f21c3..30ab87d92 100644 --- a/man/STARmap-class.Rd +++ b/man/STARmap-class.Rd @@ -16,7 +16,10 @@ The STARmap class priority for visualization when the assay is set as the active/default assay in a \code{Seurat} object} -\item{\code{key}}{Key for the image} +\item{\code{key}}{A one-length character vector with the object's key; keys must +be one or more alphanumeric characters followed by an underscore +\dQuote{\code{_}} (regex pattern +\dQuote{\code{^[a-zA-Z][a-zA-Z0-9]*_$}})} } } diff --git a/man/SingleCorPlot.Rd b/man/SingleCorPlot.Rd index b250c32c6..7b45f430f 100644 --- a/man/SingleCorPlot.Rd +++ b/man/SingleCorPlot.Rd @@ -31,6 +31,9 @@ SingleCorPlot( \item{smooth}{Make a smoothed scatter plot} +\item{rows.highlight}{A vector of rows to highlight (like cells.highlight in +\code{\link{SingleDimPlot}})} + \item{legend.title}{Optional legend title} \item{raster}{Convert points to raster format, default is \code{NULL} @@ -43,9 +46,6 @@ Default is c(512, 512)} \item{plot.cor}{...} \item{jitter}{Jitter for easier visualization of crowded points} - -\item{rows.highight}{A vector of rows to highlight (like cells.highlight in -\code{\link{SingleDimPlot}})} } \value{ A ggplot2 object diff --git a/man/SlideSeq-class.Rd b/man/SlideSeq-class.Rd index a81a02e3c..60cdb125f 100644 --- a/man/SlideSeq-class.Rd +++ b/man/SlideSeq-class.Rd @@ -22,7 +22,10 @@ The SlideSeq class represents spatial information from the Slide-seq platform priority for visualization when the assay is set as the active/default assay in a \code{Seurat} object} -\item{\code{key}}{Key for the image} +\item{\code{key}}{A one-length character vector with the object's key; keys must +be one or more alphanumeric characters followed by an underscore +\dQuote{\code{_}} (regex pattern +\dQuote{\code{^[a-zA-Z][a-zA-Z0-9]*_$}})} } } diff --git a/man/reexports.Rd b/man/reexports.Rd index ed66b98a1..edb9ab16d 100644 --- a/man/reexports.Rd +++ b/man/reexports.Rd @@ -70,6 +70,6 @@ These objects are imported from other packages. Follow the links below to see their documentation. \describe{ - \item{SeuratObject}{\code{\link[SeuratObject:set-if-null]{\%iff\%}}, \code{\link[SeuratObject:set-if-null]{\%||\%}}, \code{\link[SeuratObject]{AddMetaData}}, \code{\link[SeuratObject:ObjectAccess]{Assays}}, \code{\link[SeuratObject]{Cells}}, \code{\link[SeuratObject]{CellsByIdentities}}, \code{\link[SeuratObject]{Command}}, \code{\link[SeuratObject]{CreateAssayObject}}, \code{\link[SeuratObject]{CreateDimReducObject}}, \code{\link[SeuratObject]{CreateSeuratObject}}, \code{\link[SeuratObject]{DefaultAssay}}, \code{\link[SeuratObject:DefaultAssay]{DefaultAssay<-}}, \code{\link[SeuratObject]{Distances}}, \code{\link[SeuratObject]{Embeddings}}, \code{\link[SeuratObject]{FetchData}}, \code{\link[SeuratObject:AssayData]{GetAssayData}}, \code{\link[SeuratObject]{GetImage}}, \code{\link[SeuratObject]{GetTissueCoordinates}}, \code{\link[SeuratObject:VariableFeatures]{HVFInfo}}, \code{\link[SeuratObject]{Idents}}, \code{\link[SeuratObject:Idents]{Idents<-}}, \code{\link[SeuratObject]{Images}}, \code{\link[SeuratObject]{Index}}, \code{\link[SeuratObject:Index]{Index<-}}, \code{\link[SeuratObject]{Indices}}, \code{\link[SeuratObject]{IsGlobal}}, \code{\link[SeuratObject]{JS}}, \code{\link[SeuratObject:JS]{JS<-}}, \code{\link[SeuratObject]{Key}}, \code{\link[SeuratObject:Key]{Key<-}}, \code{\link[SeuratObject]{Loadings}}, \code{\link[SeuratObject:Loadings]{Loadings<-}}, \code{\link[SeuratObject]{LogSeuratCommand}}, \code{\link[SeuratObject]{Misc}}, \code{\link[SeuratObject:Misc]{Misc<-}}, \code{\link[SeuratObject:ObjectAccess]{Neighbors}}, \code{\link[SeuratObject]{Project}}, \code{\link[SeuratObject:Project]{Project<-}}, \code{\link[SeuratObject]{Radius}}, \code{\link[SeuratObject:ObjectAccess]{Reductions}}, \code{\link[SeuratObject]{RenameCells}}, \code{\link[SeuratObject:Idents]{RenameIdents}}, \code{\link[SeuratObject:Idents]{ReorderIdent}}, \code{\link[SeuratObject]{RowMergeSparseMatrices}}, \code{\link[SeuratObject:VariableFeatures]{SVFInfo}}, \code{\link[SeuratObject:AssayData]{SetAssayData}}, \code{\link[SeuratObject:Idents]{SetIdent}}, \code{\link[SeuratObject:VariableFeatures]{SpatiallyVariableFeatures}}, \code{\link[SeuratObject:Idents]{StashIdent}}, \code{\link[SeuratObject]{Stdev}}, \code{\link[SeuratObject]{Tool}}, \code{\link[SeuratObject:Tool]{Tool<-}}, \code{\link[SeuratObject]{UpdateSeuratObject}}, \code{\link[SeuratObject]{VariableFeatures}}, \code{\link[SeuratObject:VariableFeatures]{VariableFeatures<-}}, \code{\link[SeuratObject]{WhichCells}}, \code{\link[SeuratObject]{as.Graph}}, \code{\link[SeuratObject]{as.Neighbor}}, \code{\link[SeuratObject]{as.Seurat}}, \code{\link[SeuratObject]{as.sparse}}} + \item{SeuratObject}{\code{\link[SeuratObject:set-if-null]{\%iff\%}}, \code{\link[SeuratObject:set-if-null]{\%||\%}}, \code{\link[SeuratObject]{AddMetaData}}, \code{\link[SeuratObject:ObjectAccess]{Assays}}, \code{\link[SeuratObject]{Cells}}, \code{\link[SeuratObject]{CellsByIdentities}}, \code{\link[SeuratObject]{Command}}, \code{\link[SeuratObject]{CreateAssayObject}}, \code{\link[SeuratObject]{CreateDimReducObject}}, \code{\link[SeuratObject]{CreateSeuratObject}}, \code{\link[SeuratObject]{DefaultAssay}}, \code{\link[SeuratObject:DefaultAssay]{DefaultAssay<-}}, \code{\link[SeuratObject]{Distances}}, \code{\link[SeuratObject]{Embeddings}}, \code{\link[SeuratObject]{FetchData}}, \code{\link[SeuratObject:AssayData]{GetAssayData}}, \code{\link[SeuratObject]{GetImage}}, \code{\link[SeuratObject]{GetTissueCoordinates}}, \code{\link[SeuratObject:VariableFeatures]{HVFInfo}}, \code{\link[SeuratObject]{Idents}}, \code{\link[SeuratObject:Idents]{Idents<-}}, \code{\link[SeuratObject]{Images}}, \code{\link[SeuratObject:NNIndex]{Index}}, \code{\link[SeuratObject:NNIndex]{Index<-}}, \code{\link[SeuratObject]{Indices}}, \code{\link[SeuratObject]{IsGlobal}}, \code{\link[SeuratObject]{JS}}, \code{\link[SeuratObject:JS]{JS<-}}, \code{\link[SeuratObject]{Key}}, \code{\link[SeuratObject:Key]{Key<-}}, \code{\link[SeuratObject]{Loadings}}, \code{\link[SeuratObject:Loadings]{Loadings<-}}, \code{\link[SeuratObject]{LogSeuratCommand}}, \code{\link[SeuratObject]{Misc}}, \code{\link[SeuratObject:Misc]{Misc<-}}, \code{\link[SeuratObject:ObjectAccess]{Neighbors}}, \code{\link[SeuratObject]{Project}}, \code{\link[SeuratObject:Project]{Project<-}}, \code{\link[SeuratObject]{Radius}}, \code{\link[SeuratObject:ObjectAccess]{Reductions}}, \code{\link[SeuratObject]{RenameCells}}, \code{\link[SeuratObject:Idents]{RenameIdents}}, \code{\link[SeuratObject:Idents]{ReorderIdent}}, \code{\link[SeuratObject]{RowMergeSparseMatrices}}, \code{\link[SeuratObject:VariableFeatures]{SVFInfo}}, \code{\link[SeuratObject:AssayData]{SetAssayData}}, \code{\link[SeuratObject:Idents]{SetIdent}}, \code{\link[SeuratObject:VariableFeatures]{SpatiallyVariableFeatures}}, \code{\link[SeuratObject:Idents]{StashIdent}}, \code{\link[SeuratObject]{Stdev}}, \code{\link[SeuratObject]{Tool}}, \code{\link[SeuratObject:Tool]{Tool<-}}, \code{\link[SeuratObject]{UpdateSeuratObject}}, \code{\link[SeuratObject]{VariableFeatures}}, \code{\link[SeuratObject:VariableFeatures]{VariableFeatures<-}}, \code{\link[SeuratObject]{WhichCells}}, \code{\link[SeuratObject]{as.Graph}}, \code{\link[SeuratObject]{as.Neighbor}}, \code{\link[SeuratObject]{as.Seurat}}, \code{\link[SeuratObject]{as.sparse}}} }} From ab5fc447f9070fa60338a4e4bc9d17c952fd63aa Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Wed, 27 Sep 2023 16:32:00 -0400 Subject: [PATCH 51/53] Documentation updates --- NAMESPACE | 1 + 1 file changed, 1 insertion(+) diff --git a/NAMESPACE b/NAMESPACE index 5792a4bf3..c89df3541 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -674,6 +674,7 @@ importFrom(plotly,plot_ly) importFrom(plotly,raster2uri) importFrom(png,readPNG) importFrom(progressr,progressor) +importFrom(purrr,imap) importFrom(reticulate,import) importFrom(reticulate,py_module_available) importFrom(reticulate,py_set_seed) From eab9e5a24925a744b5ad837fb67446bee2d1fe09 Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Wed, 27 Sep 2023 17:48:18 -0400 Subject: [PATCH 52/53] Documentation updates --- man/CreateSCTAssayObject.Rd | 4 ++-- man/STARmap-class.Rd | 5 +---- man/SlideSeq-class.Rd | 5 +---- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/man/CreateSCTAssayObject.Rd b/man/CreateSCTAssayObject.Rd index 70f30f633..c3c1406c8 100644 --- a/man/CreateSCTAssayObject.Rd +++ b/man/CreateSCTAssayObject.Rd @@ -25,10 +25,10 @@ CreateSCTAssayObject( \item{min.cells}{Include features detected in at least this many cells. Will subset the counts matrix as well. To reintroduce excluded features, create a -new object with a lower cutoff} +new object with a lower cutoff.} \item{min.features}{Include cells where at least this many features are -detected} +detected.} \item{SCTModel.list}{list of SCTModels} } diff --git a/man/STARmap-class.Rd b/man/STARmap-class.Rd index 30ab87d92..7984f21c3 100644 --- a/man/STARmap-class.Rd +++ b/man/STARmap-class.Rd @@ -16,10 +16,7 @@ The STARmap class priority for visualization when the assay is set as the active/default assay in a \code{Seurat} object} -\item{\code{key}}{A one-length character vector with the object's key; keys must -be one or more alphanumeric characters followed by an underscore -\dQuote{\code{_}} (regex pattern -\dQuote{\code{^[a-zA-Z][a-zA-Z0-9]*_$}})} +\item{\code{key}}{Key for the image} } } diff --git a/man/SlideSeq-class.Rd b/man/SlideSeq-class.Rd index 60cdb125f..a81a02e3c 100644 --- a/man/SlideSeq-class.Rd +++ b/man/SlideSeq-class.Rd @@ -22,10 +22,7 @@ The SlideSeq class represents spatial information from the Slide-seq platform priority for visualization when the assay is set as the active/default assay in a \code{Seurat} object} -\item{\code{key}}{A one-length character vector with the object's key; keys must -be one or more alphanumeric characters followed by an underscore -\dQuote{\code{_}} (regex pattern -\dQuote{\code{^[a-zA-Z][a-zA-Z0-9]*_$}})} +\item{\code{key}}{Key for the image} } } From 820f29d99d42f1c6a86d33979f173169ea5f9b10 Mon Sep 17 00:00:00 2001 From: Saket Choudhary Date: Wed, 27 Sep 2023 17:48:44 -0400 Subject: [PATCH 53/53] Update news, bump version --- DESCRIPTION | 9 +++++---- NEWS.md | 7 ++++--- cran-comments.md | 24 +++++------------------- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f56d32af1..01f67ab52 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: Seurat Version: 4.4.0 -Date: 2023-09-15 +Date: 2023-09-26 Title: Tools for Single Cell Genomics Description: A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , Stuart T, Butler A, et al (2019) , and Hao, Hao, et al (2020) for more details. Authors@R: c( @@ -58,6 +58,7 @@ Imports: plotly (>= 4.9.0), png, progressr, + purrr, RANN, RColorBrewer, Rcpp (>= 1.0.7), @@ -67,9 +68,9 @@ Imports: ROCR, Rtsne, scales, - scattermore (>= 0.7), - sctransform (>= 0.3.5), - SeuratObject (>= 4.1.3), + scattermore (>= 1.2), + sctransform (>= 0.4.0), + SeuratObject (>= 4.1.4), shiny, spatstat.explore, spatstat.geom, diff --git a/NEWS.md b/NEWS.md index 94b32876f..93bd03abd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# Seurat 4.4.0 (2023-09-15) +# Seurat 4.4.0 (2023-09-27) ## Added - Added parallelization support with speed improvements for `PrepSCTFindMarkers` @@ -7,11 +7,12 @@ ## Changes - Fix bug in `as.Seurat.SingleCellExperiment()` ([#6692](https://github.com/satijalab/seurat/issues/6692)) - Support for Visium probe information introduced in Spaceranger 2.1 ([#7141](https://github.com/satijalab/seurat/pull/7141)) -- Add `LoadCurioSeeker` to load sequencing-based spatial datasets generated using the Curio Seeker ([#744](https://github.com/satijalab/seurat-private/pull/744)) -- Fix fold change calculation for assays ([#739](https://github.com/satijalab/seurat-private/pull/739)) +- Add `LoadCurioSeeker` to load sequencing-based spatial datasets generated using the Curio Seeker +- Fix fold change calculation for assays ([#7095](https://github.com/satijalab/seurat/issues/7095)) - Fix `pt.size` bug when rasterization is set to true ([#7379](https://github.com/satijalab/seurat/issues/7379)) - Fix `FoldChange` and `FindMarkers` to support all normalization approaches ([#7115](https://github.com/satijalab/seurat/pull/7115),[#7110](https://github.com/satijalab/seurat/issues/7110),[#7095](https://github.com/satijalab/seurat/issues/7095),[#6976](https://github.com/satijalab/seurat/issues/6976),[#6654](https://github.com/satijalab/seurat/issues/6654),[#6701](https://github.com/satijalab/seurat/issues/6701),[#6773](https://github.com/satijalab/seurat/issues/6773), [#7107](https://github.com/satijalab/seurat/issues/7107)) - Fix for handling newer ParseBio formats in `ReadParseBio` ([#7565](https://github.com/satijalab/seurat/pull/7565)) +- Fix for handling rasterization by default ([#7842](https://github.com/satijalab/seurat/pull/7842)) # Seurat 4.3.0 (2022-11-18) diff --git a/cran-comments.md b/cran-comments.md index 4da97cd64..1bea36b47 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,30 +1,16 @@ -# Seurat v4.2.1 +# Seurat v4.4.0 ## Test environments -* local Ubuntu 20.04 install, R 4.1.3 +* local Ubuntu 20.04 install, R 4.3.1 * win-builder (release, devel) ## R CMD check results -There were no ERRORs or WARNINGs - -There was one NOTE: - -* checking CRAN incoming feasibility ... NOTE -Maintainer: 'Paul Hoffman ' - -Found the following (possibly) invalid URLs: - URL: https://www.science.org/doi/abs/10.1126/science.aad0501 - From: man/cc.genes.Rd - man/cc.genes.updated.2019.Rd - Status: 503 - Message: Service Unavailable - -This URL is valid and the service still exists. When navigating to the URL either via the documentation or directly, you are taken to the correct article +There were no ERRORs or WARNINGs. ## Downstream dependencies There no packages that depend on Seurat -There are sixteen packages that import Seurat: CAMML, CIDER, DR.SC, DUBStepR, maple, Platypus, rPanglaoDB, scDiffCom, scMappR, SCRIP, scRNAstat, Signac, SignacX, SoupX, spruce, and tidyseurat; this update does not impact their functionality +There are forty-five packages that import Seurat: AnanseSeurat, APL, bbknnR, benchdamic, CAMML, CIDER, COTAN, CSCDRNA, Dino, DR.SC, DWLS, ggsector, gsdensity, infercnv, IRISFGM, mixhvg, Nebulosa, pipeComp, PRECAST, ProFAST, rPanglaoDB, scAnnotate, scBFA, scBubbletree, scCB2, scDataviz, scDiffCom, scFeatures, scGate, scMappR, scperturbR, scpoisson, SCRIP, scRNAseqApp, scRNAstat, scTreeViz, SignacX, singleCellTK, SoupX, Spaniel, SPECK, speckle, SpotClean, stJoincount, and STREAK; this update does not impact their functionality -There are twelve packages that suggest Seurat: BisqueRNA, CIARA, ClustAssess, clustree, conos, DIscBIO, dyngen, harmony, rliger, Rmagic, treefit, and VAM; this update does not impact their functionality. +There are fifty-one packages that suggest Seurat: ASURAT, BayesSpace, BisqueRNA, Canek, cellpypes, CIARA, ClustAssess, clustifyr, clustifyrdatahub, clustree, combiroc, conos, countland, CRMetrics, decoupleR, DIscBIO, dittoSeq, dorothea, dyngen, EasyCellType, EpiMix, escape, fcoex, FEAST, fgsea, GeomxTools, grandR, harmony, M3Drop, MOFA2, monocle, muscData, progeny, RESET, rliger, SCORPIUS, SCpubr, scRepertoire, scTensor, Signac, SimBenchData, SimBu, spatialHeatmap, SPOTlight, TAPseq, TCGAbiolinks, tidybulk, treefit, tricycle, UCell, and VAM; this update does not impact their functionality.