Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multi-modal segmentation #8605

Merged
merged 20 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: Seurat
Version: 5.1.0.9005
Date: 2024-09-12
Version: 5.1.0.9006
Date: 2024-09-29
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) <doi:10.1038/nbt.3192>, Macosko E, Basu A, Satija R, et al (2015) <doi:10.1016/j.cell.2015.05.002>, Stuart T, Butler A, et al (2019) <doi:10.1016/j.cell.2019.05.031>, and Hao, Hao, et al (2020) <doi:10.1101/2020.10.12.335331> for more details.
Authors@R: c(
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ importFrom(SeuratObject,CreateAssayObject)
importFrom(SeuratObject,CreateCentroids)
importFrom(SeuratObject,CreateDimReducObject)
importFrom(SeuratObject,CreateFOV)
importFrom(SeuratObject,CreateMolecules)
importFrom(SeuratObject,CreateSegmentation)
importFrom(SeuratObject,CreateSeuratObject)
importFrom(SeuratObject,DefaultAssay)
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Unreleased

## Changes
- Surfaced more fine-grained control over what parts of a Xenium experiment are loaded in `LoadXenium`
- Added ability to load Xenium nucleus segmentation masks
- Updated `LoadXenium` to also read some run metadata (run start time, preservation method, panel used, organism, tissue type, instrument software version and stain kit used) into `misc` slot
- Updated `ReadXenium` to load cell_feature_matrix.h5 when present in favor of the MEX format files
- Added ability to read Xenium `segmentation_method` directly into `meta.data`
- Updated `ReadXenium` to load .parquet files using `arrow` instead of .csv.gz files to support XOA 3.0
jsicherman marked this conversation as resolved.
Show resolved Hide resolved
- Updated `RunSLSI` to support `BPCells` matrices
- Fixed `LoadXenium` to accommodate datasets without "Blank Codeword" or "Unassigned Codeword" matrices
- Fixed `ReadXenium` to properly parse multiple molecular outputs at once ([#8265](https://github.com/satijalab/seurat/issues/8265))
Expand Down
106 changes: 87 additions & 19 deletions R/convenience.R
Original file line number Diff line number Diff line change
Expand Up @@ -172,42 +172,110 @@ LoadVizgen <- function(data.dir, fov, assay = 'Vizgen', z = 3L) {

#' @return \code{LoadXenium}: A \code{\link[SeuratObject]{Seurat}} object
#'
#' @param data.dir Path to folder containing Nanostring SMI outputs
#' @param data.dir Path to folder containing Xenium outputs
#' @param fov FOV name
#' @param assay Assay name
#' @param mols.qv.threshold Remove transcript molecules with
#' a QV less than this threshold. QV >= 20 is the standard threshold
#' used to construct the cell x gene count matrix.
#' @param cell.centroids Whether or not to load cell centroids
#' @param molecule.coordinates Whether or not to load molecule pixel coordinates
#' @param segmentations One of "cell", "nucleus" or NULL (to load either cell
#' segmentations, nucleus segmentations or neither)
#' @param flip.xy Whether or not to flip the x/y coordinates of the Xenium outputs
#' to match what is displayed in Xenium Explorer, or fit on your screen better.
#'
#' @importFrom SeuratObject Cells CreateCentroids CreateFOV
#' CreateSegmentation CreateSeuratObject
#' CreateSegmentation CreateSeuratObject CreateMolecules
#'
#' @export
#'
#' @rdname ReadXenium
#'
LoadXenium <- function(data.dir, fov = 'fov', assay = 'Xenium') {
LoadXenium <- function(
data.dir,
fov = 'fov',
assay = 'Xenium',
mols.qv.threshold = 20,
cell.centroids = TRUE,
molecule.coordinates = TRUE,
segmentations = NULL,
flip.xy = FALSE
) {
if(!is.null(segmentations) && !(segmentations %in% c('nucleus', 'cell'))) {
stop('segmentations must be NULL or one of "nucleus", "cell"')
}

if(!cell.centroids && is.null(segmentations)) {
stop(
"Must load either centroids or cell/nucleus segmentations"
)
}

data <- ReadXenium(
data.dir = data.dir,
type = c("centroids", "segmentations"),
type = c("centroids", "segmentations", "nucleus_segmentations")[
c(cell.centroids, isTRUE(segmentations == 'cell'), isTRUE(segmentations == 'nucleus'))
],
outs = c("segmentation_method", "matrix", "microns")[
c(cell.centroids || isTRUE(segmentations != 'nucleus'), TRUE, molecule.coordinates && (cell.centroids || !is.null(segmentations)))
],
mols.qv.threshold = mols.qv.threshold,
flip.xy = flip.xy
)

segmentations.data <- list(
"centroids" = CreateCentroids(data$centroids),
"segmentation" = CreateSegmentation(data$segmentations)
)
coords <- CreateFOV(
coords = segmentations.data,
type = c("segmentation", "centroids"),
molecules = data$microns,
assay = assay
segmentations <- intersect(c("segmentations", "nucleus_segmentations"), names(data))

segmentations.data <- Filter(Negate(is.null), list(
centroids = if(is.null(data$centroids)) {
NULL
} else {
CreateCentroids(data$centroids)
},
segmentations = if(length(segmentations) > 0) {
CreateSegmentation(
data[[segmentations]]
)
} else {
NULL
}
))

coords <- if(length(segmentations.data) > 0) {
CreateFOV(
segmentations.data,
assay = assay,
molecules = if(is.null(data$microns)) {
NULL
} else {
CreateMolecules(data$microns)
}
)
} else {
NULL
}

slot.map <- c(
`Blank Codeword` = 'BlankCodeword',
`Unassigned Codeword` = 'BlankCodeword',
`Negative Control Codeword` = 'ControlCodeword',
`Negative Control Probe` = 'ControlProbe',
`Genomic Control` = 'GenomicControl'
)

xenium.obj <- CreateSeuratObject(counts = data$matrix[["Gene Expression"]], assay = assay)
if("Blank Codeword" %in% names(data$matrix)) {
xenium.obj[["BlankCodeword"]] <- CreateAssayObject(counts = data$matrix[["Blank Codeword"]])
} else if("Unassigned Codeword" %in% names(data$matrix)) {
xenium.obj[["BlankCodeword"]] <- CreateAssayObject(counts = data$matrix[["Unassigned Codeword"]])

if(!is.null(data$metadata)) {
Misc(xenium.obj, 'run_metadata') <- data$metadata
}

if(!is.null(data$segmentation_method)) {
xenium.obj <- AddMetaData(xenium.obj, data$segmentation_method)
}

for(name in intersect(names(slot.map), names(data$matrix))) {
xenium.obj[[slot.map[name]]] <- CreateAssayObject(counts = data$matrix[[name]])
jsicherman marked this conversation as resolved.
Show resolved Hide resolved
}
xenium.obj[["ControlCodeword"]] <- CreateAssayObject(counts = data$matrix[["Negative Control Codeword"]])
xenium.obj[["ControlProbe"]] <- CreateAssayObject(counts = data$matrix[["Negative Control Probe"]])

xenium.obj[[fov]] <- coords
return(xenium.obj)
Expand Down
Loading