CLI: cellucid serve quickstart#

cellucid serve is the fastest way to open the Cellucid viewer from a terminal.

It:

  • detects your input format (any file or folder Cellucid reads: .h5ad, an AnnData Zarr folder or .zip, .h5mu or MuData Zarr, .loom, a 10x .h5 file or Cell Ranger outs/ folder, a MatrixMarket folder, an .rds/.rda/ .RData, gzipped or not),

  • starts a small local HTTP server,

  • prints a Viewer URL you open in your browser,

  • and keeps serving data until you stop it.

At a glance#

Audience

  • Wet lab / non-technical: copy/paste the commands; you don’t need to write Python.

  • Computational: focus on direct input (AnnData, MuData, Loom, 10x, MatrixMarket, R objects) and SSH tunnel workflows.

Time

  • Local machine: ~5–10 minutes

  • Remote/HPC with SSH tunnel: ~15–30 minutes

Prerequisites

  • pip install cellucid

  • One of: .h5ad, a Zarr store (folder or .zip), .h5mu, .loom, a 10x .h5 or outs/ folder, a MatrixMarket folder, an .rds/.rda/.RData

Step-by-step (fast path)#

Step 0 — Install#

pip install cellucid

Step 1 — Start the server (auto-detects format)#

cellucid serve /path/to/data.h5ad --dataset-name "My dataset" --dataset-id my-dataset
# or:
# cellucid serve /path/to/data.zarr --dataset-name "My dataset" --dataset-id my-dataset

The viewer reads the file itself — in place where the format allows it, once from the start for an R object or a MatrixMarket folder; the server serves its bytes (with sendfile and exact Range answers). --dataset-name and --dataset-id default to the AnnData or MuData’s own uns['cellucid']['name'] and ['id']; a dataset that declares none (any R object, Loom, 10x or MatrixMarket dataset) is named after its file, its id the file’s name without the extension, printed when the server starts. A file written by cellucid.prepare() declares both, so cellucid serve pbmc_cellucid.h5ad is the whole command for one.

The same command serves the other formats:

cellucid serve cite.h5mu                       # a MuData from cellucid.prepare()
cellucid serve GSM4567_seurat.rds.gz --dataset-name "GSM4567" --dataset-id gsm4567
cellucid serve GSE164378_RAW/ --dataset-name "GSE164378" --dataset-id gse164378   # GSM…_matrix.mtx.gz files
cellucid serve outs/ --dataset-name "PBMC 1k" --dataset-id pbmc-1k            # a Cell Ranger outs/ folder

You should see a banner like:

CELLUCID SERVER RUNNING
Local URL:  http://127.0.0.1:8765
Viewer URL: http://127.0.0.1:8765/?viewer=true&viewerId=…&viewerToken=…&anndata=true

Copy the printed Viewer URL exactly; the bare Local URL is the server origin for diagnostics, not the launch URL. Its viewerId and viewerToken open the page’s viewer channel, through which an agent app on this computer can work in it (cellucid mcp, Local agents (MCP): Claude Code, Claude Desktop, Cursor); the address without them (http://127.0.0.1:8765/?anndata=true) opens the same dataset with no channel.

Step 2 — Open the viewer in your browser#

Copy the Viewer URL into your browser address bar.

Note

You do not need to open cellucid.com manually. Before binding, the Python server establishes its exact published web generation and then serves it from the dataset-server origin.

Step 3 — Keep the terminal running#

If you close the terminal (or stop the process), the viewer will stop working.

Step 4 — Stop the server#

Press Ctrl+C in the terminal.

What startup prints#

Startup runs four numbered steps.

[1/4] Detecting format...
      Path: /path/to/pbmc_demo.h5ad
      Format: AnnData (.h5ad)
      ✓ Format detected

[2/4] Reading metadata...
      Cells: 4,096
      Genes: 512
      obsm: X_umap
      obs fields: 3
      obsp: connectivities
      layers: none
      ✓ Metadata read

[3/4] Checking launch choices...
      ✓ Launch choices checked

[4/4] Starting server...
      ✓ Server ready

Step 2 lists what the store holds, from its metadata; no array is read. Step 3 checks each launch choice you set (--embedding-2d, --knn, --expression, …) against that metadata and lists it; a name the store does not have stops the server here, before it binds. A format Python does not read (an R object, Loom, 10x, a MatrixMarket folder) prints Contents: read by the browser when it opens the data in step 2, and its choices are checked only for their form here and against the data by the viewer when it opens it. A choice you leave unset is taken from the store’s uns['cellucid']['defaults'], else the viewer’s default rule — see How the viewer reads AnnData (show_anndata() / serve_anndata()).

What the CLI auto-detects (important for debugging)#

cellucid serve <data_path> decides what you meant using these rules:

  • A regular file must be named as a format names it (.h5ad, a .zip holding a Zarr store, .h5mu, .loom, .h5, .rds[.gz], .rda[.gz], .RData[.gz]); its first bytes then decide what it is (an HDF5 root marked mudata is a MuData whatever its name).

  • A Zarr store folder must hold zarr.json (Zarr v3) or .zgroup (Zarr v2) at its root; a MuData Zarr store says so in its root attributes.

  • Any other folder must be a Cell Ranger outs/ (holding filtered_feature_bc_matrix.h5) or a MatrixMarket folder (a .mtx[.gz] file, whatever its name, such as GSM4567_matrix.mtx.gz, with its lists).

  • A directory name ending in .zarr is not sufficient by itself, and a valid Zarr store does not need that suffix.

  • Anything else → error: Unable to detect format for: <path>.

If detection fails, the error message will tell you what it expected.

Most useful flags#

Run cellucid serve --help anytime.

Networking / ports#

  • --port, -p <int>: change the port (useful if 8765 is in use)

  • --host, -H <host>:

    • default: 127.0.0.1, which no other machine can reach

    • 0.0.0.0: accept connections on every interface. This is what a compute node needs when the tunnel terminates on a different machine, such as a cluster login node

    • a non-loopback bind has no authentication: every user who can reach the port reads the dataset (see Security, privacy, CORS, and networking)

What the banner prints for a wildcard bind#

0.0.0.0, ::, and an empty host all name every interface. None of them is an address a browser can open, so none of them is ever printed as a URL. A server bound to one of them prints the loopback origin as its Local URL and Viewer URL, and adds a second block naming the machine itself when the machine has a hostname that means something outside it:

  Local URL:    http://127.0.0.1:8765
  Viewer URL:   http://127.0.0.1:8765/?viewer=true&viewerId=…&viewerToken=…&anndata=true

  Bound to every network interface. From another machine:
  Machine URL:  http://compute-node.example.org:8765
  Viewer URL:   http://compute-node.example.org:8765/?viewer=true&viewerId=…&viewerToken=…&anndata=true

Through an SSH tunnel, open the loopback Viewer URL: the tunnel’s near end is on your own laptop, so 127.0.0.1 is right there whatever the server bound to. The Machine URL block is for clients already on the same network. A bind to an IPv6 literal is bracketed as a URL requires, so --host :: prints http://[::1]:8765 and --host ::1 prints the same origin.

Browser behavior / output#

  • --no-browser: don’t auto-open a browser tab

  • --quiet, -q: suppress progress banners

  • --verbose, -v: debug logging (useful when reporting bugs)

Dataset and launch choices#

  • --latent-key KEY: the adata.obsm latent space (scVI, PCA) the outlier filter measures in, never a 2D/3D layout. Without it the store’s own latentKey is used, else none: the filter then waits for one chosen in Data mapping (Outlier filtering (per categorical field))

  • --dataset-name NAME: exact display name; defaults to the store’s uns['cellucid']['name'], required when it has none

  • --dataset-id ID: exact stable identifier; defaults to the store’s uns['cellucid']['id'], required when it has none

  • --obs-key KEY: show only this adata.obs column; repeat once per column, in order. Every column is shown when it is omitted. Use it to leave out a column Cellucid cannot show, such as a datetime64 collection date

  • --vector-field-default FIELD_ID: exact vector field id the overlay opens on

  • --modality NAME: the modality a multimodal dataset (a MuData) opens on; when omitted, the first gene-expression modality (rna, gex, …), else the first

  • --embedding-1d/2d/3d KEY[:COLUMNS]: the obsm array plotted in that dimension

  • --normalize-embeddings / --no-normalize-embeddings: scale every embedding into the viewer’s coordinate range (the default), or show the coordinates the store holds

  • --centroid-min-points N: the fewest cells a category needs for the outlier filter to measure it (10 when neither this nor the store says otherwise)

  • --knn KEY: the obsp neighbor graph the viewer draws, read only when a view draws it

  • --gene-id-column COLUMN, --expression X|raw|layer:NAME: gene names and the expression matrix

A flag overrides the store’s own uns['cellucid']['defaults'] for this server only; the file is not changed.

Debugging endpoints (high signal)#

If something looks wrong, open these in a browser (replace <port>):

  • http://127.0.0.1:<port>/_cellucid/health (is the server alive?)

  • http://127.0.0.1:<port>/_cellucid/info (server config + version)

  • http://127.0.0.1:<port>/_cellucid/datasets (the dataset id and name, the store’s route under _cellucid/anndata/, and the launch choices)

  • http://127.0.0.1:<port>/_cellucid/protocol (which wire capabilities does this cellucid accept?)

Edge cases (common)#

  • Port already in use: the requested port fails. Choose another explicit port, or use --port 0 to request one operating-system-assigned port.

  • --knn connectivities on an object with no graph: startup fails in step 3 naming obsp['connectivities'] instead of serving a dataset with no edges.

  • No uns['cellucid'] identity and no --dataset-name/--dataset-id: startup fails asking for both.

  • Viewer source blocked: startup fails instead of substituting a local generation; see Troubleshooting (viewing).

Troubleshooting#

Start here: Troubleshooting (viewing).