Troubleshooting (viewing)#
This page is symptom-driven troubleshooting for:
cellucid serve …(CLI server mode),serve_anndata(...)(Python server mode),show_anndata(...)(a notebook widget), andAnnDataViewer(...)(a browser-tab viewer).
If you’re new, skim the “Quick triage” section first.
Quick triage (do these in order)#
1) What workflow are you using?#
Terminal/browser → you’re in server mode (
cellucid serve …,serve_anndata,AnnDataViewer)Viewer inside a notebook cell → you’re in notebook mode (
show_anndata)
This matters because notebook mode has no server: steps 2 and 3 do not apply to it, and its failure modes are the notebook’s (widget support, the browser loading the app, messages arriving between cells).
2) Is the server alive? (server mode)#
Open (replace <port>):
http://127.0.0.1:<port>/_cellucid/health
If this doesn’t load, the viewer can’t load either.
3) Is the exact viewer generation being served? (server mode)#
Open the exact Viewer URL printed by the server:
http://127.0.0.1:<port>/?anndata=true
A wildcard bind prints the loopback origin plus the machine’s own name; see Remote servers, SSH tunneling, and cloud.
If this fails, the server may have stopped.
4) Did one field fail, or did everything fail?#
A viewer that loads, draws points, and refuses exactly one gene or one
continuous obs column is not a broken server. The viewer checks each
continuous column as it reads it, and a column holding an infinity is refused
with a notification titled Gene cannot be shown that counts the offending
values and names the first affected cells. See
a gene or column that cannot be drawn.
5) If you are in a notebook: run a structured debug report (in a new cell)#
viewer.debug_connection()
Symptom: “Port already in use” / server won’t start#
Likely causes (ordered)#
another Cellucid server is still running
another program is using the port (often
8765)
How to confirm#
cellucid serveends withError: Port 8765 is already in use on 127.0.0.1, …;serve_anndata(...)/AnnDataServer.start()raise an address-in-useOSError
Fix#
choose a port explicitly:
cellucid serve ... --port 9000in Python, pass
port=0to request one operating-system-assigned port and then use the actualserver.url/server.viewer_urlan
AnnDataViewercreated withoutport=already takes the next free port after 8765 and prints a note; one created with an explicitport=raises instead (a notebook widget,show_anndata, uses no port)in notebooks, call
viewer.stop()on old viewers (see Viewer lifecycle, cleanup, ports, and multiple viewers)
Prevention#
stop servers when done
use fixed ports only when you need them (SSH tunnels)
Symptom: viewer startup cannot establish the web generation#
This failure occurs before the viewer server is published.
Likely causes (ordered)#
your environment cannot reach the configured
web_source_urlthe inventory or a declared object has an invalid MIME type, length, hash, file set, or build identity
the selected
web_cache_diris not writable
How to confirm#
read the exception raised by
cellucid serve,serve_anndata(...)orAnnDataViewer(...)(a notebook widget keeps no web build: the browser loads the app)exercise the same establishment contract directly with
cellucid.web_cache.ensure_web_ui_cached(force=True)(orviewer.ensure_web_ui_cached())
Fix#
allow access to the configured source and correct the exact response failure
pass a writable generation location:
cellucid serve /path/to/data --web-cache-dir /path/to/cache
Prevention#
verify source access and destination permissions before a workshop or job
do not plan on a previous local generation being substituted when the source is unavailable
Symptom: Viewer loads but dataset is blank / stuck loading#
Likely causes (ordered)#
no plottable embedding in
obsm, or a shape mismatcha Zarr store folder whose consolidated metadata is stale (written before later edits to the store)
How to confirm#
open
/_cellucid/datasetsand see what it listscheck
adata.obsm.keys()and shapes;/_cellucid/datasetsnames the store’s route and the launch choices the server passed
Fix#
confirm which
obsmarray each slot shows (Session › Data mapping names it), or name one withembedding_keys(see How the viewer reads AnnData (show_anndata() / serve_anndata()))
Symptom: the viewer shows a different embedding than I expected#
What decides it#
a slot you name with
embedding_keys(Python) or--embedding-<n>d(CLI) shows that array, at the 0-based columns you giveevery other slot follows the default rule:
X_umap_1d/X_umap_2d/X_umap_3dfill their own slots, and with none of them present a bareX_umapof 1, 2, or 3 columns fills the slot of its own widthonly when neither fills any slot is a deterministic best guess made from the other
obsmarrays (UMAP-like names first, latent spaces such as PCA last), and a wide array is then shown in 2D at columns 0 and 1nothing in your object is renamed, assigned, or written back — resolution is a read, and the server only reads the store’s bytes
How to confirm#
Session › Data mapping names the array each slot shows, and marks a guessed one
a file written by
prepare():adata.uns["cellucid"]["defaults"]holds theembedding1d/embedding2d/embedding3dit opens on
Fix#
name the array you want:
embedding_keys={"2d": "X_umap_harmony"}or--embedding-2d X_umap_harmony;embedding_keys={"2d": ("X_pca", [0, 1])}or--embedding-2d X_pca:0,1for chosen columns of a wide arrayor write the choice into the file once:
prepare(..., embedding_defaults=...)
Full rules: How the viewer reads AnnData (show_anndata() / serve_anndata()).
Symptom: “Cellucid found nothing to plot”#
Likely causes (ordered)#
every candidate array places no cell (every row non-finite), holds a value past float32, or has no spread in the columns that would be plotted
arrays do not have one row per cell (
adata.n_obs)(no embedding ever computed –
obsmempty, or only tables and sparse arrays – is refused as having no cell embedding instead: Symptom: “… has no cell embedding, and Cellucid draws cells at embedding coordinates”)
How to confirm#
The message lists every obsm entry and says why it was not used:
Cellucid found nothing to plot. It needs an obsm array with one row per cell (3,696) and at least one numeric column. obsm holds: X_df (table), spatial (3,696 × 2, not representable as float32 at cell 17). Add a 2-D embedding to obsm (for example with sc.tl.umap in Scanpy), then open the data again.
Fix#
compute an embedding (
sc.tl.umap(adata)), or repair the array the message names (the first cell past float32 is given)or name a usable array explicitly with
embedding_keys/--embedding-<n>da dimensional key whose array does not have exactly its own column count is an error, not a reinterpretation:
X_umap_3dholding(n_obs, 2)is refused rather than shown as 2Dserving a
.h5ador Zarr path? Re-save the file after a repair
Symptom: “Observation field ‘…’ has unsupported dtype”#
Likely causes (ordered)#
adata.obscarries adatetime64, timedelta, or period column; Cellucid shows categorical, boolean, numeric, and string columns only
How to confirm#
print(adata.obs.dtypes)
Fix#
convert the column:
adata.obs["collection_date"] = adata.obs["collection_date"].astype(str)or leave it out by naming the columns to show:
show_anndata(
adata,
dataset_name="My study",
dataset_id="my-study-v1",
obs_keys=["cell_type", "leiden", "total_counts"],
)
from the CLI, repeat
--obs-keyonce per column
Symptom: Gene search returns nothing / “Gene ‘X’ not found”#
Likely causes (ordered)#
your gene IDs are not in
var.index(default)wrong
gene_id_columnthe name is repeated in the chosen gene axis and is shown as
NAME-1
How to confirm#
print(adata.var.index[:5])
print(adata.var.columns)
Fix#
pass
gene_id_column="..."toshow_anndata(...)/serve_anndata(...)ensure the chosen gene ID field is unique
This symptom is about a gene the viewer never found. A gene it did find and then refused is the next section.
Symptom: a gene or column is found but cannot be drawn#
The gene is in var.index, the search offers it, you select it — and the viewer
answers with a notification titled Gene cannot be shown. Everything else keeps
working.
What happened#
A colour scale needs a finite minimum and maximum. NaN is drawn as the
neutral grey meaning “no value here”, but one +Inf makes a field’s range
infinite and collapses every other cell onto one colour, so the viewer refuses a
gene or continuous obs column holding an infinity. It checks each column as it
reads it, one column at a time, as you select it.
How to confirm#
The notification is the diagnosis. It names the gene or field, counts the offending values by kind out of all cells, lists the first affected cell indices, and gives a repair one-liner:
Gene "A2ML1" contains 12 infinite values, so it has no colour scale.
Of 18,142,044 cells: 12 +Infinity.
First affected cells: 4,127, 88,301, 250,994, 1,004,112, 7,730,015, ….
NaN is drawn as missing; an infinity is not a value on any scale.
Repair the expression matrix before serving or preparing it — for example `adata.X.data[~np.isfinite(adata.X.data)] = 0` for a sparse matrix, or `np.nan_to_num(adata.X, copy=False)` for a dense one — then reload.
Read the counts before acting: twelve cells out of eighteen million and every
cell in the column want opposite responses. A column whose every value is NaN
is reported separately (has no value in any of … cells); it usually means the
column was never computed.
Fix#
Repair the values in the object, then reload:
import numpy as np
adata.X.data[~np.isfinite(adata.X.data)] = 0 # sparse expression
np.nan_to_num(adata.X, copy=False) # dense expression
adata.obs["score"] = adata.obs["score"].replace([np.inf, -np.inf], np.nan).fillna(0)
Serving a .h5ad or Zarr path rather than an in-memory object? Re-save after
the repair.
Note
prepare() refuses the same values with a counted diagnosis. Running it once
is also how to find every affected value at once — it scans the whole matrix,
while the viewer refuses one column at a time as you select it.
Symptom (notebook): the output is empty, or says the app did not load#
Likely causes (ordered)#
the notebook front end does not show widgets (
anywidgetmissing where the notebook server runs)the browser cannot load the app from
https://www.cellucid.com(offline, blocked address); the output says “The page at … has not answered as a Cellucid viewer”
Fix#
pip install anywidgetwhere the notebook server runs, and reload the pageallow the address, or host a copy of the app and pass
web_source_url=
Deep dive: Notebook / widget mode (advanced).
Symptom (notebook): the viewer loads, but hooks and commands do nothing#
Likely causes (ordered)#
a cell is still running: the widget’s messages reach Python between cells
the output belongs to a stopped or older viewer (after
viewer.stop()or a kernel restart it keeps its last view and says so: where the notebook front end reports the kernel’s connection closing, JupyterLab and Notebook 7, the output says The notebook’s kernel stopped: run this cell again, and the viewer ends the reads that were waiting on the kernel instead of waiting for them)a long cell is running while the viewer opens a large dataset: the viewer waits for the kernel without a deadline (the kernel answers between cells) and goes on once the cell finishes
in a browser tab (
AnnDataViewer) behind a proxy: the channel (one WebSocket to/_cellucid/ws) cannot open because the proxy does not pass WebSockets; the viewer says “… this viewer cannot open its channel (…)”
How to confirm#
viewer.debug_connection()in a new cell reportschannel_connectedandrecent_events
Fix#
let the cell finish; re-run the cell that creates the viewer for a stale output
behind a proxy, configure it to pass WebSocket upgrades for
/_cellucid/wscommands sent meanwhile wait in Python and are delivered, in order, once the viewer connects
Symptom: Vector field / velocity overlay is missing#
Likely causes (ordered)#
vectors not present (no
<field>_<basis>key beside anX_<basis>inobsm)naming mismatch: the key names no embedding the object holds (e.g.
velocity_umap_2dbeside a bareX_umapwith noX_umap_2d— this no longer binds; scVelo’svelocity_umapdoes)width mismatch: the vector does not have as many columns as its embedding
the view shows another embedding (you have vectors for
X_umapbut are viewingX_umap_3d)
How to confirm#
inspect
adata.obsm.keys()for a<field>_<basis>key beside eachX_<basis>(velocity_umapbesideX_umap) and compare their shapesthe view must show the embedding the field is named for: a vector is drawn only over its own embedding
Fix#
rename keys to scVelo’s
<field>_<basis>convention (velocity_umap_2d→velocity_umapbesideX_umap), or bind the array under Session › Data mapping › Vector fieldsensure you have vectors for the embedding you’re viewing
Symptom: the neighbor-graph overlay is missing#
Likely causes (ordered)#
the store has no
obsp['connectivities'], and noknn_keynamed another graph (a file written byprepare()withoutconnectivities=has none)the named graph is not symmetric (such as
distances), and the viewer refused it when it read it
How to confirm#
print(list(adata.obsp.keys()));/_cellucid/datasetsshows"knn"underdefaultswhen the server was given one
Fix#
in Python, name the graph with
knn_key:
viewer = show_anndata(
"data.h5ad",
dataset_name="My study",
dataset_id="my-study-v1",
knn_key="connectivities",
)
from the terminal, add
--knn connectivities:
cellucid serve /path/to/data.h5ad \
--dataset-name "My study" \
--dataset-id my-study-v1 \
--knn connectivities
or choose a graph in the viewer under Session › Data mapping
the keyword is accepted by
show_anndata(...),serve_anndata(...),AnnDataViewer(...), andAnnDataServer(...)naming a graph the store does not carry stops the server before it binds:
knn_key names obsp['connectivities'], which this store does not have.Compute one withsc.pp.neighbors(adata)first
The graph is read only when a view draws it, so it costs nothing at startup.
Symptom: Performance is extremely slow / browser crashes#
Likely causes (ordered)#
the first gene of a large row-major (CSR) matrix: the browser builds a gene index once, and later genes are fast
a large in-memory AnnData whose cell-major
Xis still being laid out gene-major (in the background from the start; the first gene waits for it)huge categorical fields or heavy overlays
Fix#
serve a path rather than an in-memory object:
cellucid serve data.h5ad --dataset-name "My dataset" --dataset-id my-datasetwrite a Cellucid-ready AnnData once (
prepare) and open that: its matrix is gene-major, so no gene index is builtreduce or remove pathological fields (very high cardinality)
Performance guide: Performance, scaling, and lazy loading.
Reporting a bug (what to include)#
Please capture:
the exact command you ran (
cellucid serve ...), including flagswhether the input was
.h5advs Zarr store vs in-memory, and whetherprepare()wrote itthe full startup transcript: the numbered steps and the banner
the last
Error:line verbatim — it is written after everything else, and it names the one condition to correctthe output of:
/_cellucid/health/_cellucid/info/_cellucid/datasets
if one gene or one continuous field failed: the full text of its
Gene cannot be shownnotification — it already names the field, counts the offending values, and lists the first affected cell indicesin notebooks:
viewer.debug_connection()outputif safe:
adata.uns["cellucid"]of the file
This will save you (and maintainers) a lot of back-and-forth.
Server-side symptoms#
A banner URL that will not open, a startup that prints nothing for minutes, and an import Cellucid says is missing are covered in Server mode issues. Cluster-specific failures are in HPC and remote access issues.