Notebook / widget mode (advanced)#
This page explains the notebook widget behind show_anndata(...), including:
what the widget is, and why it needs no port or proxy,
where the app comes from, and what happens offline,
what happens while a cell runs, and the calls that cannot wait in one,
how to choose the neighbor graph, which the viewer reads only when a view draws it,
and how to debug the browser ↔ kernel connection.
If you want the minimal notebook quickstart, go to Jupyter: show_anndata() quickstart.
At a glance#
Audience
JupyterHub, Colab, VS Code and cloud notebook users
anyone whose notebook output shows no viewer
The widget#
show_anndata() creates a Widget and
shows it; cellucid.Widget(...) creates one without showing it (end a cell
with it to show it). A widget is an anywidget: its
front end runs in the cell’s output, and talks to the kernel over the
notebook’s own connection (a Jupyter comm), which every notebook already has
between the browser and the kernel, wherever the kernel runs.
The output frames the Cellucid app at <web_source_url>/?viewer=embedded.
The app then:
tells the output it is ready; the output accepts that only from its own frame and the app’s origin, and answers with the viewer’s id and token and a private
MessagePort, posted to that origin only (the token never appears in an address, a widget trait a notebook may save, or a log);reads the dataset’s documents and bytes (by
Range, like any remote file) through the port; the output relays them to the kernel, which answers from the dataset (answer_request());opens the viewer channel through the port: the same
hello/welcome, commands and events as over a WebSocket (Architecture: the viewer channel).
Requests that arrive together go to the kernel in one message, and bodies travel as binary buffers. An R kernel (IRkernel answers one message at a time) has at most two of those messages at once; the requests asked meanwhile go together in the next.
Where the app comes from#
The browser loads the app from web_source_url, https://www.cellucid.com
by default: the build the site publishes, as every visitor gets it. The
protocol between the app and the kernel is negotiated when the viewer opens
(_cellucid/protocol), so an app newer than the installed package uses only
what the package understands. An institution can host a copy of the app and
pass its address:
viewer = show_anndata(adata, dataset_name="Example", dataset_id="example",
web_source_url="https://cellucid.example.org")
Offline: the browser needs to reach web_source_url; the kernel does not
(a compute node without internet is fine). A browser that cannot load the app
shows a message in the output after the page failed to answer. Without any
internet on the machine you browse from, host a copy of the app on your
network, or open the viewer in a browser tab with
AnnDataViewer, which serves a verified copy kept on disk
(Server mode (advanced)).
While a cell runs#
The kernel handles a widget’s messages between cells, on the thread that runs cells. Hence:
the viewer loads once the cell that showed it finishes; a long cell after
show_anndata(...)in the same cell delays it;while any later cell runs, the viewer waits for bytes it has not read yet (a new gene, a new embedding); what it shows stays interactive;
hooks run, and
viewer.stateupdates, whenever the kernel is idle;wait_for_ready(),wait_for_event(), an action’s, a session request’s and a session load’sresult()(andapply_session_to_anndata(adata, request)) wait for an answer that cannot arrive while their own cell runs: they raise at once with what to do instead (actions themselves never wait: they go as soon as the viewer can take them). A thread of your own may wait (the viewer answers it as soon as the cell ends). A session is asked in one cell and collected in the next:
request = viewer.request_session()
session = request.result()
plan = session.apply(adata) # what would change, one row each
report = plan.apply() # writes into adata
Actions (highlight_cells, color_by, …) are sent at once.
A notebook viewer starts quietly#
show_anndata(...) prepares the dataset with output suppressed, so the
numbered report a terminal server prints never appears. Nothing reads an
array there: the dataset’s metadata is read and the launch choices you passed
are checked; the viewer then reads what each view needs. Writing a temporary
.h5ad (.h5mu) for an in-memory AnnData (MuData), once, is what can
take a while. To see the report, run the same object through a terminal
server once — serve_anndata(..., quiet=False) prints the four steps.
The neighbor graph#
The viewer reads the neighbor graph only when a view draws it. Without
knn_key it uses obsp['connectivities'] when the store has it; knn_key
names another obsp matrix. The parameter is keyword-only and accepted by
show_anndata(), Widget and
AnnDataViewer:
from cellucid import show_anndata
viewer = show_anndata(
"data.h5ad",
dataset_name="My study",
dataset_id="my-study-v1",
knn_key="connectivities",
)
Important
Naming a matrix the object’s obsp does not hold raises during construction
rather than producing a graph-less viewer. Compute it first with
sc.pp.neighbors(adata), or leave the parameter unset.
The rules are in How the viewer reads AnnData (show_anndata() / serve_anndata()).
Debugging notebook connectivity (high-signal)#
In a new cell, run:
viewer.debug_connection()
It waits for nothing, and reports:
web_source_url: where the app is loaded fromconnected_outputs: the outputs whose page has a channel openchannel_connected: whether the viewer channel is openstate: whether the viewer said it is ready, and its last eventrecent_events: accepted events by type, and the commands the viewer refused (command_errors)
Troubleshooting#
The output shows text instead of a viewer (“This output is a Cellucid viewer (a notebook widget)…”) → the notebook cannot show widgets: install
anywidgetwhere the notebook server runs (pip install anywidget) and reload the page.The output says the page “has not answered as a Cellucid viewer” → the browser cannot load
web_source_url(offline, a blocked network), or it is not a Cellucid build that can be embedded.The output says it is waiting for the notebook kernel → a cell is still running; the viewer connects when it finishes.
A waiting call raised “… hears the viewer only between cells” → run it in a later cell, use a hook, or keep the request (
request.result()in a later cell).The edge overlay is missing although
adata.obsp['connectivities']exists → the graph is read only when a view draws edges; turn the edges on in the viewer, or choose the graph under Session › Data mapping.Construction raised “knn_key names obsp[‘connectivities’], which this store does not have.” → run
sc.pp.neighbors(adata)first, or construct without the parameter.Construction raised “Cellucid found nothing to plot” → no
obsmarray can be plotted. The message lists everyobsmentry and why it was not used; name a usable one withembedding_keys={"2d": "<key>"}. (“… has no cell embedding” meansobsmholds no dense numeric array at all: Symptom: “… has no cell embedding, and Cellucid draws cells at embedding coordinates”.) How the viewer reads AnnData (show_anndata() / serve_anndata()) has the complete rules.