Python package for visualizing single-cell data with the Cellucid WebGL viewer. Cellucid renders millions of cells in real-time 3D, enabling interactive exploration of UMAP/tSNE embeddings with gene expression overlays, filtering, and KNN connectivity visualization.
- Interactive Single-Cell Data Visualization of UMAP/tSNE embeddings
- Gene expression overlays with fast queries
- Cell metadata filtering and categorical coloring
- KNN connectivity edge visualization
- Multi-dimensional support (1D timelines, 2D, 3D)
- Jupyter integration with bidirectional communication
- Scales to millions of cells with adaptive LOD
pip install cellucidAll features (Jupyter, AnnData, server) are included in the standard install.
The fastest way to get started - visualize your AnnData directly:
from cellucid import show_anndata
# In-memory AnnData
show_anndata(adata)
# From h5ad file (lazy loading)
show_anndata("/path/to/data.h5ad")
# From zarr store (lazy loading)
show_anndata("/path/to/data.zarr")For production use or sharing, export to optimized binary format:
from cellucid import prepare, show
# Pick UMAP embeddings (explicit keys preferred, fall back to X_umap).
X_umap_1d = adata.obsm.get("X_umap_1d")
X_umap_2d = adata.obsm.get("X_umap_2d")
X_umap_3d = adata.obsm.get("X_umap_3d")
X_umap = adata.obsm.get("X_umap") # may be 1D/2D/3D depending on how it was computed
if X_umap is not None and X_umap_1d is None and X_umap.shape[1] == 1:
X_umap_1d = X_umap
if X_umap is not None and X_umap_2d is None and X_umap.shape[1] == 2:
X_umap_2d = X_umap
if X_umap is not None and X_umap_3d is None and X_umap.shape[1] == 3:
X_umap_3d = X_umap
# Optional: include any per-cell displacement vector fields in embedding space
# (see "Vector Field Overlay" section below for naming conventions).
vector_fields = {
"velocity_umap_2d": adata.obsm.get("velocity_umap_2d"),
"velocity_umap_3d": adata.obsm.get("velocity_umap_3d"),
}
prepare(
# Required inputs
latent_space=adata.obsm.get(
"X_pca",
X_umap_3d if X_umap_3d is not None else (X_umap_2d if X_umap_2d is not None else X_umap_1d),
),
obs=adata.obs,
var=adata.var,
gene_expression=adata.X,
# Optional inputs
connectivities=adata.obsp.get("connectivities"),
vector_fields=vector_fields,
# Embeddings (provide any/all of 1D/2D/3D)
X_umap_1d=X_umap_1d,
X_umap_2d=X_umap_2d,
X_umap_3d=X_umap_3d,
# Output + performance knobs
out_dir="./my_export",
compression=6,
var_quantization=8,
obs_continuous_quantization=8,
)
# View anytime (fastest loading)
show("./my_export")Your AnnData needs UMAP coordinates in obsm:
# Required: one of these
adata.obsm['X_umap_3d'] # shape (n_cells, 3) - recommended
adata.obsm['X_umap_2d'] # shape (n_cells, 2)
adata.obsm['X_umap'] # shape (n_cells, 2 or 3)
# Optional
adata.obsm['velocity_umap_2d'] # shape (n_cells, 2) - vector field overlay (velocity/drift)
adata.obs # Cell metadata (categorical/continuous)
adata.X # Gene expression (dense or sparse)
adata.obsp['connectivities'] # KNN graph edgesCellucid’s web viewer can render an animated particle-flow overlay from per-cell displacement vectors in embedding space.
Store vectors in adata.obsm with keys like:
velocity_umap_2d,velocity_umap_3dT_fwd_umap_2d,T_bwd_umap_2d(CellRank-style drift)
If you have a CellRank transition matrix T and an embedding X_umap, you can derive drift vectors:
from cellucid import add_transition_drift_to_obsm
add_transition_drift_to_obsm(
adata,
T_fwd, # (n_cells, n_cells) sparse/dense transition matrix
basis="umap",
field_prefix="T_fwd", # -> writes adata.obsm['T_fwd_umap_<dim>d']
)Computing 3D UMAP with scanpy:
import scanpy as sc
sc.pp.neighbors(adata)
sc.tl.umap(adata, n_components=3)
adata.obsm['X_umap_3d'] = adata.obsm['X_umap']Cellucid supports 6 deployment modes, each with support for pre-exported binary data, h5ad files, and zarr stores:
| # | Method | Exported | h5ad | zarr | Python | Lazy Load | Performance |
|---|---|---|---|---|---|---|---|
| 1 | Local Demo (GitHub) | ✅ | - | - | No* | Yes | Best |
| 2 | Remote Demo (GitHub) | ✅ | - | - | No* | Yes | Best |
| 3 | Browser File Picker | ✅ | - | - | No | Yes | Best |
| 4 | Browser File Picker | - | ✅ | - | No | No | Slower |
| 5 | Browser File Picker | - | - | ✅ | No | No | Slower |
| 6 | Server CLI | ✅ | - | - | Yes | Yes | Best |
| 7 | Server CLI | - | ✅ | ✅ | Yes | Yes | Good |
| 8 | Python serve() | ✅ | - | - | Yes | Yes | Best |
| 9 | Python serve_anndata() | - | ✅ | ✅ | Yes | Yes | Good |
| 10 | Jupyter show() | ✅ | - | - | Yes | Yes | Best |
| 11 | Jupyter show_anndata() | - | ✅ | ✅ | Yes | Yes | Good |
* Python required for initial export, not for viewing
Summary by method:
| Method | Exported | h5ad | zarr | Total |
|---|---|---|---|---|
| Local/Remote Demo | ✅ | - | - | 2 |
| Browser File Picker | ✅ | ✅ | ✅ | 3 |
| Server CLI | ✅ | ✅ | ✅ | 3 |
| Python serve | ✅ | ✅ | ✅ | 3 |
| Jupyter | ✅ | ✅ | ✅ | 3 |
| Total | 14 |
- Browser h5ad/zarr: Entire file loaded into memory - no lazy loading due to JavaScript limitations
- Python h5ad/zarr modes: True lazy loading via AnnData backed mode (h5ad) or zarr's native chunked access
- Pre-exported data: Always fastest - use for production and sharing
- zarr stores: Can be a directory (.zarr) or a file - the Python server auto-detects the format
| Mode | Best For | Command/Function |
|---|---|---|
| Jupyter | Interactive analysis | show_anndata(adata) |
| Local server | Development | cellucid serve ./data |
| Remote + SSH | Team access | cellucid serve data.h5ad |
| Browser | Quick preview | cellucid.com file picker |
| Static hosting | Public sharing | GitHub Pages |
# Serve any data - format auto-detected
cellucid serve /path/to/data.h5ad # h5ad file
cellucid serve /path/to/data.zarr # zarr store
cellucid serve /path/to/export # pre-exported data
# With options
cellucid serve data.h5ad --port 9000 --no-browser
# Show version
cellucid --version# On remote server
cellucid serve /data/cells.h5ad --no-browser
# On local machine (SSH tunnel)
ssh -L 8765:localhost:8765 user@server
# Then open: https://cellucid.com?remote=http://localhost:8765from cellucid import show_anndata
viewer = show_anndata(adata, height=600)
# Programmatic control
viewer.highlight_cells([1, 2, 3], color="#ff0000")
viewer.set_color_by("cell_type")
viewer.set_visibility([0, 1, 2], visible=False)
# Cleanup
viewer.stop()| Method | Load Time | Gene Queries | Recommended For |
|---|---|---|---|
Pre-exported (show) |
Fast | Fast | Production, sharing |
Direct AnnData (show_anndata) |
Medium | Medium | Exploration |
| Browser file picker | Slower | Slower | Quick preview |
For datasets > 500k cells, pre-export with prepare() is recommended.
- Full documentation - API reference and tutorials
- Web viewer - JavaScript repository
BSD-3-Clause