Seurat Recipe#
Audience: Seurat users (wet lab + computational)
Time: 20–45 minutes (depends on dataset size)
What you’ll do: extract data from a Seurat object → export → open in the Cellucid web app
This recipe exports a Seurat object using cellucid::cellucid_prepare() (alias: prepare()).
Note
cellucid-r does not depend on Seurat. This page shows how to extract the needed inputs from Seurat.
Prerequisites#
A Seurat object
seumust have a UMAP (or other) reduction you want to visualize
should have a latent space (PCA is typical)
R packages:
cellucidSeuratMatrix(recommended)
Step 0 — Decide what you want to export#
Before writing code, decide:
Which embedding?
2D UMAP (most common)
3D UMAP (if you computed 3 components)
Which latent space?
PCA is typical
Which metadata columns (
obs) matter?clusters, sample, batch, QC metrics, scores…
Do you need gene expression?
exporting all genes can be huge; consider a gene panel
Do you need a connectivity graph?
Seurat stores graphs; exporting them is optional
If you don’t know yet:
start with embeddings + a handful of obs columns + a small gene panel
Step 1 — Load packages and the Seurat object#
library(cellucid)
library(Seurat)
library(Matrix)
# Example:
# seu <- readRDS("my_seurat_object.rds")
Step 2 — Choose a stable cell order#
Cellucid uses row order as cell identity, so we pick a canonical order and reuse it everywhere.
cells <- colnames(seu)
stopifnot(length(cells) > 0)
Step 3 — Extract embeddings (UMAP 2D / 3D)#
2D UMAP#
umap <- Embeddings(seu, reduction = "umap")
umap2 <- umap[, 1:2, drop = FALSE]
umap2 <- umap2[cells, , drop = FALSE]
Optional: 3D UMAP#
Only do this if you actually computed a 3D UMAP (e.g., RunUMAP(..., n.components = 3)).
# umap3 <- umap[, 1:3, drop = FALSE]
# umap3 <- umap3[cells, , drop = FALSE]
Step 4 — Extract a latent space (PCA is typical)#
pca <- Embeddings(seu, reduction = "pca")
pca <- pca[cells, , drop = FALSE]
latent_space <- pca
Note
If you don’t have PCA, you can use the embedding as latent space (less ideal, but valid):
latent_space <- umap2
Step 5 — Build obs (cell metadata)#
Seurat metadata lives in seu@meta.data (rows = cells).
obs_all <- seu@meta.data
obs_all <- obs_all[cells, , drop = FALSE]
Optional: select only a subset of obs columns#
Exporting dozens/hundreds of columns is allowed, but can be noisy in the UI.
obs_keys <- c("seurat_clusters", "orig.ident", "nCount_RNA", "nFeature_RNA")
obs_keys <- intersect(obs_keys, colnames(obs_all))
obs <- obs_all[, obs_keys, drop = FALSE]
Make sure key columns are the types you want:
obs$seurat_clusters <- factor(obs$seurat_clusters)
Step 6 — (Optional) Export gene expression + var#
6.1 Choose which assay/slot to export#
Common Seurat choices:
slot="data": log-normalized (often best for visualization)slot="counts": raw counts (can have huge dynamic range)
assay_name <- DefaultAssay(seu) # often "RNA"
expr_gxc <- GetAssayData(seu, assay = assay_name, slot = "data") # genes x cells
6.2 Align cells and transpose to cells × genes#
GetAssayData returns genes × cells. Cellucid expects cells × genes.
expr_gxc <- expr_gxc[, cells, drop = FALSE]
expr_cxg <- Matrix::t(expr_gxc)
6.3 Build var (gene metadata)#
The simplest var is a data.frame with rownames set to the gene IDs you want users to search.
gene_ids <- rownames(expr_gxc)
var <- data.frame(symbol = gene_ids, stringsAsFactors = FALSE)
rownames(var) <- var$symbol
6.4 Optional: export a gene panel instead of all genes#
Exporting every gene can be huge. A panel often gives a better experience.
gene_panel <- c("MS4A1", "CD3D", "NKG7")
gene_panel <- intersect(gene_panel, rownames(var))
Step 7 — (Optional) Export connectivities (Seurat graphs)#
Seurat graphs often live in seu@graphs, for example:
RNA_snn(shared nearest neighbor graph)
conn <- NULL
if ("RNA_snn" %in% names(seu@graphs)) {
conn <- seu@graphs$RNA_snn
conn <- conn[cells, cells]
}
Note
Connectivity export symmetrizes and binarizes edges. Weights are not preserved.
Step 8 — Run cellucid_prepare()#
Pick an output directory (best: a fresh folder per export iteration):
out_dir <- file.path(getwd(), "exports", "seurat_export")
dir.create(out_dir, recursive = TRUE, showWarnings = FALSE)
Recommended “starter” settings#
force=TRUEwhile iteratingobs_continuous_quantization=8for smaller metadataif exporting expression:
var_quantization=8and/orgene_identifiers=gene_panel
cellucid_prepare(
latent_space = latent_space,
obs = obs,
X_umap_2d = umap2,
# X_umap_3d = umap3, # optional
out_dir = out_dir,
force = TRUE,
compression = 6,
obs_continuous_quantization = 8,
var = if (exists("var")) var else NULL,
gene_expression = if (exists("expr_cxg")) expr_cxg else NULL,
gene_identifiers = if (exists("gene_panel")) gene_panel else NULL,
var_quantization = if (exists("expr_cxg")) 8 else NULL,
connectivities = conn
)
Step 9 — Quick validation#
list.files(out_dir, recursive = TRUE)
ident <- jsonlite::read_json(file.path(out_dir, "dataset_identity.json"), simplifyVector = TRUE)
ident$stats
Confirm:
n_cellsmatches your Seurat objectn_genesmatches the exported gene panel (if you used one)
Step 10 — Open in the Cellucid web app#
Follow: Open Exports in the Cellucid Web App
What success looks like
The web app loads points quickly.
You can color by your
obsfields.If you exported gene expression, you can search genes and color by them.
Edge cases and common gotchas#
“My metadata columns turned into categories”#
If a column is not numeric, it becomes categorical. Explicitly coerce numeric columns. See: obs: Cell Metadata
“I exported and got a gigantic folder”#
You probably exported too many genes for your cell count. Use:
gene_identifiers(gene panels)var_quantization=8compression=6
“My UMAP is missing / Embeddings(…) errors”#
Check which reductions exist:
Reductions(seu)
“Cell order mismatch”#
Always reorder:
embeddings
metadata
expression columns/rows
connectivity matrix
using the same cells vector.
Troubleshooting (symptom → fix)#
Symptom: export fails with shape mismatches#
Most common causes:
expression orientation is wrong (genes × cells vs cells × genes)
you subset a matrix but not the others
Confirm:
dim(expr_cxg)is(n_cells, n_genes)nrow(obs) == nrow(umap2) == nrow(latent_space)
Fix:
reorder/transpose explicitly (Steps 2–6)
Symptom: web app loads but fields look wrong#
Likely cause:
row order mismatch (the export “worked” but you exported misaligned inputs)
Fix:
rebuild using a single
cells <- colnames(seu)ordering and reorder everything.
More:
export-time troubleshooting: Troubleshooting: Prepare/Export
loading-time troubleshooting: Web App Loading Issues