Overview
cellucid_prepare() writes single-cell data – embeddings,
cell and gene metadata, gene expression, a neighbour graph, and vector
fields – as a Cellucid-ready AnnData: a standard
AnnData store, written as AnnData Zarr, that Python’s anndata reads like
any other and the Cellucid web app opens directly. Gene expression is
stored gene-major, so the viewer reads one gene with one small range of
the file, and uns['cellucid'] carries the dataset’s name,
citation, source, and launch defaults.
format chooses the output, and has no default because
only you know where the store goes: format = "zarr" writes
a .zarr folder of small files, the way to publish on GitHub
(no file over 100 MB, no LFS); format = "zarr.zip" writes
the same store as one .zarr.zip file. out_path
ends in the matching suffix. (An .h5ad file is written by
Python’s cellucid.prepare(format = "h5ad"); this package
writes no HDF5, so it needs no HDF5 library.)
From an object
A Seurat, SingleCellExperiment or SummarizedExperiment object is
written as it is: data = reads its cells, metadata,
reductions, assays and neighbour graph the way the Cellucid web app
reads the same object saved with saveRDS(), and any other
argument replaces one component. To look at an object before writing it,
cellucid_view(object) opens it in the viewer from memory
(see vignette("viewer", package = "cellucid")).
data("pbmc_small", package = "SeuratObject")
pbmc <- SeuratObject::UpdateSeuratObject(pbmc_small)
#> Validating object structure
#> Updating object slots
#> Ensuring keys are in the proper structure
#> Updating matrix keys for DimReduc 'pca'
#> Updating matrix keys for DimReduc 'tsne'
#> Ensuring keys are in the proper structure
#> Ensuring feature names don't have underscores or pipes
#> Updating slots in RNA
#> Updating slots in RNA_snn
#> Setting default assay of RNA_snn to RNA
#> Updating slots in pca
#> Updating slots in tsne
#> Setting tsne DimReduc to global
#> Setting assay used for NormalizeData.RNA to RNA
#> Setting assay used for ScaleData.RNA to RNA
#> Setting assay used for RunPCA.RNA to RNA
#> Setting assay used for BuildSNN.RNA.pca to RNA
#> No assay information could be found for FindClusters
#> Warning: Adding a command log without an assay associated with it
#> Setting assay used for RunTSNE.pca to RNA
#> Setting assay used for JackStraw.RNA.pca to RNA
#> Setting assay used for ScoreJackStraw.pca to RNA
#> Setting assay used for ProjectDim.RNA.pca to RNA
#> Setting assay used for FindVariableFeatures.RNA to RNA
#> Validating object structure for Assay 'RNA'
#> Validating object structure for Graph 'RNA_snn'
#> Validating object structure for DimReduc 'pca'
#> Validating object structure for DimReduc 'tsne'
#> Object representation is consistent with the most current Seurat version
store <- cellucid::cellucid_prepare(
file.path(tempdir(), "pbmc_small.zarr"), "zarr",
data = pbmc,
dataset_id = "pbmc-small",
dataset_name = "PBMC (small)",
latent_key = "X_pca",
force = TRUE
)
list.dirs(file.path(store, "obsm"), full.names = FALSE, recursive = FALSE)
#> [1] "X_pca" "X_tsne"With several assays the store is a Cellucid-ready MuData, one
modality per assay; modalities = "RNA" writes one.
Minimal example
library(cellucid)
umap <- matrix(c(0, 0,
1, 0,
0, 1,
1, 1),
ncol = 2, byrow = TRUE)
pca <- matrix(c(0, 3, 1,
2, 5, 1,
4, 0, 2,
1, 1, 1),
ncol = 3, byrow = TRUE)
obs <- data.frame(
cluster = factor(c("A", "A", "B", "B")),
score = c(0.1, 0.2, 0.3, 0.4),
row.names = paste0("cell", 1:4)
)
expr <- matrix(c(0, 1,
2, 3,
4, 5,
0, 0),
nrow = 4, byrow = TRUE,
dimnames = list(rownames(obs), c("ENSG01", "ENSG02")))
var <- data.frame(symbol = c("CD3E", "MS4A1"), row.names = colnames(expr))
store <- cellucid_prepare(
out_path = file.path(tempdir(), "vignette.zarr"),
format = "zarr",
embeddings = list(X_umap = umap, X_pca = pca),
dataset_id = "vignette",
dataset_name = "Vignette dataset",
obs = obs,
var = var,
gene_expression = expr,
embedding_defaults = c("2d" = "X_umap"),
latent_key = "X_pca",
var_gene_id_column = "symbol",
force = TRUE
)
list.files(store, all.files = TRUE, no.. = TRUE)
#> [1] ".zattrs" ".zgroup" ".zmetadata" "layers" "obs"
#> [6] "obsm" "obsp" "uns" "var" "varm"
#> [11] "varp" "X"In the web app at https://www.cellucid.com, open a .zarr.zip
file from your computer (see below), or a .zarr folder or
.zarr.zip file published at a web address with the
Link or server address bar. In Python:
What goes where
| Argument | In the store |
|---|---|
data |
every element below; several assays or experiments as modalities
(mod/<name>, a Cellucid-ready MuData) |
embeddings |
obsm[<key>], float32, every column |
obs |
obs, every column |
var |
var, every column |
gene_expression |
X, gene-major float32, float64 for integer counts above
2^24 (csc_matrix for a sparse matrix) |
connectivities |
obsp['connectivities'], float32
csr_matrix
|
vector_fields |
obsm['<field>_<basis>'] for the embedding
X_<basis>
|
dataset_id, dataset_name,
dataset_description
|
uns['cellucid'] id, name,
description; uns['title']
|
citation_text, citation_url
|
uns['cellucid']['citation'] |
source_name, source_url
|
uns['cellucid']['source'] |
embedding_defaults, latent_key,
obs_keys, var_gene_id_column,
vector_field_default, normalize_embeddings,
centroid_min_points, modality
|
uns['cellucid']['defaults'], only the ones you set |
latent_key (with centroid_min_points) |
uns['cellucid']['outlierQuantiles']: each categorical
obs column’s outlier quantiles in that latent space |
Embeddings are stored at their own coordinates; the viewer chooses
the columns it plots and scales them to its view.
embedding_defaults names the embedding each view dimension
shows first, by obsm key (its first columns) or as
list(key = "X_pca", columns = 1:3).
latent_key names the latent space the viewer’s outlier
filter measures in, such as PCA or scVI, never a 2D or 3D layout.
cellucid_prepare() computes the outlier quantiles of every
categorical obs column there (each cell’s distance to its
category’s centre, as the share of its category at that distance or
closer) and stores them, so the filter works as soon as the file opens.
A category needs centroid_min_points cells (10 when not
set); ?cellucid_prepare states the rule.
Vector fields are stored under scVelo’s names and drawn over the
embedding they are named for:
vector_fields = list(velocity = list(X_umap = velocity_umap, X_branches = velocity_branches))
stores obsm['velocity_umap'] and
obsm['velocity_branches'], which the viewer lists as the
one field velocity and draws whenever a view shows
X_umap or X_branches. Each array has one
column per column of its embedding, and the embedding’s key starts with
X_.
cellucid_transition_drift(transition_matrix, umap, normalize_rows = TRUE)
computes such a field from a cell-cell transition matrix (CellRank’s,
for example): each cell’s transition-weighted mean successor position
minus its own, ready for
vector_fields = list(T_fwd = list(X_umap = drift)).
Cell names are the row names your inputs agree on, and gene names the
var row names (else the column names of
gene_expression). Repeated names are made unique the way
anndata’s var_names_make_unique() does it – the second
MATR3 becomes MATR3-1 – with a message saying
what changed.
Choosing what an object writes
With data =, any other argument replaces the one
component it names, and modalities chooses the assays
(Seurat) or experiments (SingleCellExperiment) to write, in order. An
object’s matrices are read in their own genes x cells orientation;
nothing needs transposing.
cellucid_prepare(
"seurat.zarr", "zarr",
data = seu,
dataset_id = "seurat-dataset",
dataset_name = "Seurat dataset",
obs = seu[[c("seurat_clusters", "orig.ident")]], # only these columns
modalities = "RNA", # one assay: a plain AnnData
embedding_defaults = c("2d" = "X_umap"),
latent_key = "X_pca"
)
cellucid_prepare(
"sce.zarr", "zarr",
data = sce, # logcounts, colData, reducedDims, altExps
dataset_id = "sce-dataset",
dataset_name = "SingleCellExperiment dataset",
latent_key = "X_PCA" # reducedDim names keep their case
)A matrix you take out of an object yourself and pass as
gene_expression is cells x genes: transpose a Seurat or
SingleCellExperiment matrix with Matrix::t() first.
cellucid_prepare() checks the dimensions but never guesses
the orientation.
One file instead of a folder
format = "zarr.zip" writes the same store as one
.zarr.zip file: the format for everywhere but GitHub –
single-file hosts (a Zenodo record, a Figshare or Hugging Face file, a
bucket URL, a lab server), local files, cellucid serve and
Jupyter. A .zarr folder is the format for GitHub, which
refuses files over 100 MB and answers many small files at once over
HTTP/2. The archive’s entries are stored uncompressed (each chunk is
compressed already), so the web app reads any part of it with one range
request, and its metadata comes last, so it opens with one read of the
archive’s end.
archive <- cellucid_prepare(
out_path = file.path(tempdir(), "vignette.zarr.zip"),
format = "zarr.zip",
embeddings = list(X_umap = umap),
dataset_id = "vignette",
dataset_name = "Vignette dataset",
force = TRUE
)
file.size(archive)
#> [1] 15756