Multi-modal spatial transcriptomics with Bioconductor
MultiAssaySpatialExperiment extends MultiAssayExperiment with spatial context for integrated analysis of spatially resolved multi-omics data. It provides:
spatialMap
# From Bioconductor (devel)
if (!requireNamespace("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install("MultiAssaySpatialExperiment", version = "devel")
# Or install from a local source checkout
# R CMD INSTALL path/to/MultiAssaySpatialExperiment
library(MultiAssaySpatialExperiment)
# Read 10x Xenium data
mase <- readXeniumMASE("path/to/xenium/output")
# Read 10x Visium HD data
mase <- readVisiumHDMASE("path/to/visium_hd/output",
bin_size = c("008", "016"))
# Read Vizgen MERSCOPE data
mase <- readMERSCOPEMASE("path/to/merscope/output",
segmentation = "cellpose",
load_transcripts = TRUE)
# Read NanoString CosMx data
mase <- readCosMxMASE("path/to/cosmx/output")
# Read standard 10x Visium data
mase <- readVisiumMASE("path/to/visium/output")
Supported technologies: Xenium, Visium, Visium HD, MERSCOPE (Vizgen), CosMx (NanoString)
# Create from scratch
mase <- MultiAssaySpatialExperiment(
experiments = ExperimentList(
rna = rna_counts,
protein = protein_counts
),
colData = sample_metadata,
sampleMap = sample_map,
points = PointsLayerList(
transcripts = transcript_coords,
centroids = cell_centroids
),
shapes = ShapesLayerList(
cells = cell_polygons,
tissue = tissue_boundary
),
spatialMap = spatial_map
)
# From SpatialExperiment
spe <- SpatialExperiment(...)
mase <- as(spe, "MultiAssaySpatialExperiment")
# From SpatialFeatureExperiment
sfe <- SpatialFeatureExperiment(...)
mase <- as(sfe, "MultiAssaySpatialExperiment")
# Point-in-polygon annotation
mase <- annotateWithRegions(mase,
points = "centroids",
shapes = "cells")
# Aggregate expression by spatial region
cell_expr <- aggregateByRegion(mase,
by = "cells",
FUN = "sum")
# Spatial subsetting
library(sf)
roi <- st_polygon(list(matrix(c(0, 0, 1000, 0, 1000, 1000, 0, 1000, 0, 0),
ncol = 2, byrow = TRUE)))
mase_tissue <- subsetByPolygon(mase, roi)
mase_bbox <- subsetByBoundingBox(mase,
xmin = 0, xmax = 1000,
ymin = 0, ymax = 1000)
Uses the same subsetBy* API as MultiAssayExperiment — no separate spatial query API.
Specimen subsetting ([, j], subsetByColData) and observation filtering
(subsetByColumn with a list) both propagate to spatialMap, imgData, and linked
points, shapes, images, and labels.
# By sample, assay, column, or observation metadata
mase[, colData(mase)$tissue_type == "tumor"]
mase[c("rna", "protein"), ]
subsetByAssay(mase, "rna")
cdf <- colData(experiments(mase)[["rna"]])
subsetByColumn(mase, list(rna = cdf$region == "core"))
spmap <- buildSpatialMap(sampleMap(mase), region = "cells", element_type = "shapes")
prepared <- prepMASE(experiments(mase), colData(mase), sampleMap(mase),
points = spatialPoints(mase), spatialMap = spmap)
mase2 <- do.call(MultiAssaySpatialExperiment, prepared)
# Points (DataFrame with coordinates)
transcripts <- spatialPoints(mase)$transcripts
centroids <- spatialPoints(mase)$centroids
# Shapes (sf objects with geometries)
cell_boundaries <- spatialShapes(mase)$cells
tissue_outline <- spatialShapes(mase)$tissue
# Images and labels
spatialImages(mase)
spatialLabels(mase)
# Spatial mapping table
spatialMap(mase)
Problem: SpatialExperiment handles one assay per object. Multi-assay data requires managing separate objects or ad-hoc lists.
Solution: MASE provides a unified multi-assay container with shared spatial layers. Subset once, filter everywhere. Full coercion support maintains interoperability.
# Instead of managing multiple SPE objects
spe_rna <- SpatialExperiment(...)
spe_protein <- SpatialExperiment(...)
# Use one MASE object
mase <- MultiAssaySpatialExperiment(
experiments = ExperimentList(rna = spe_rna, protein = spe_protein),
...
)
Problem: SFE extends SPE with rich geometry support but is single-assay.
Solution: MASE provides the multi-assay scaffold. Each assay can be an SFE. Instance-level spatialMap links columns across assays to shared spatial features.
# Multi-assay with SFE-compatible structure
mase <- MultiAssaySpatialExperiment(
experiments = ExperimentList(
rna = sfe_rna, # SFE object
morphology = sfe_morph # SFE object
),
shapes = ShapesLayerList(cells = boundaries),
spatialMap = map # Links both assays to same cells
)
Inherited from MultiAssayExperiment:
ExperimentList: Multiple experiments (SummarizedExperiment, etc.)colData: Sample-level metadatasampleMap: Links experiment columns to samplesSpatial extensions:
points: PointsLayerList (transcripts, centroids, etc.)shapes: ShapesLayerList (cells, nuclei, regions)images: RasterLayerList (user-attached rasters; distinct from reader metadata)labels: RasterLayerList (segmentation masks)imgData: Specimen-level image metadata from vendor readers (SPE-compatible)spatialMap: Instance-level mapping (assay/colname → spatial feature)Links assay columns to spatial features at instance level:
| assay | colname | element_type | region | instance_id |
|---|---|---|---|---|
| rna | ACGT-1 | shapes | cells | cell_001 |
| rna | ACGT-2 | shapes | cells | cell_002 |
| protein | ACGT-1 | shapes | cells | cell_001 |
Built-in support for major spatial transcriptomics platforms:
| Technology | Reader Function | Data Types | Special Features |
|---|---|---|---|
| 10x Xenium | readXeniumMASE() |
Counts, cells, boundaries, transcripts | Auto-format detection (HDF5/Parquet) |
| 10x Visium | readVisiumMASE() |
Counts, positions, images | H&E integration, spot geometries |
| 10x Visium HD | readVisiumHDMASE() |
Multi-bin counts, images | Multiple resolutions (002µm, 008µm, 016µm) |
| Vizgen MERSCOPE | readMERSCOPEMASE() |
FOV-based, transcripts, boundaries | Multi-FOV, cellpose/watershed segmentation |
| NanoString CosMx | readCosMxMASE() |
FOV-based, expression, boundaries | Multi-FOV, GeoParquet geometries |
All readers:
browseVignettes("MultiAssaySpatialExperiment")
Data import:
readXeniumMASE(), readVisiumMASE(), readVisiumHDMASE()
readMERSCOPEMASE(), readCosMxMASE()
Spatial operations:
annotateWithRegions(): Point-in-polygon annotationaggregateByRegion(): Aggregate assays by spatial featuressubsetByBoundingBox(): Subset to spatial extentsubsetByPolygon(): Subset to polygon interiorsubsetByColumn(): Filter assay columns (with spatial propagation when y is a list)spatialJoin(): Join spatial layer tables (DataFrame × DataFrame via sf)Accessors:
spatialPoints(), spatialShapes(), spatialImages(), spatialLabels()
spatialMap(), imgData()
Coercion:
as(x, "MultiAssaySpatialExperiment"): From SPE/SFEas(mase, "SpatialExperiment"): To SPEMASE aligns with spatialdata (Python) for interoperability:
colData (primary identifier); often a tissue section,
patient, or replicatespatialMap (spatialdata uses slot names; MASE needs explicit column)See ?MultiAssaySpatialExperiment for detailed terminology documentation.
instance_id validation ensures referential integrity:
Helps catch data inconsistencies early.
All readers use S4 generics (readParquetForMASE(), readGeoParquetForMASE(), etc.) enabling:
Technology readers orchestrate reusable components:
Add new platforms without modifying core code.
Version 0.9.3 - Pre-release for community feedback
?MultiAssaySpatialExperiment
browseVignettes("MultiAssaySpatialExperiment")
MIT License — see DESCRIPTION.
Copyright (c) 2023-2026 Genentech, Inc.