Quickstart: minimal round-trip (select → highlight)#
Goal: in one notebook, get a working loop where you select cells in the viewer and Python highlights those same cells back in the viewer.
This is the fastest sanity check that:
the widget shows the viewer and the viewer reads the dataset,
viewer → Python events are arriving,
Python → viewer commands are being delivered.
At a glance#
Audience
Wet lab / beginner: follow the “Copy/paste” cells and focus on “What success looks like”.
Computational users: also read “Common pitfalls” and “Troubleshooting”.
Developers: jump to Architecture: the viewer channel and Reference (hooks, commands, schemas, endpoints).
Time: ~5–10 minutes
Prerequisites
A notebook that shows widgets (JupyterLab is tested; Notebook 7, VS Code, Colab, marimo and Databricks are expected to work, see Supported environments matrix)
pip install cellucidAn
AnnDataobject namedadata, or a path to a file or folder Cellucid reads (.h5ad, AnnData Zarr,.h5mu,.loom, 10x, MatrixMarket,.rds)
Note
If you have a file written by cellucid.prepare(), show_anndata("my_dataset.h5ad", height=600)
opens it under the name and id it declares.
Step 1 — Create and display a viewer#
Option A (most common): show an AnnData#
Run this in a notebook cell:
from cellucid import show_anndata
viewer = show_anndata(
adata,
height=600,
dataset_name="My study",
dataset_id="my-study-v1",
)
viewer
Option B: show a Cellucid-ready AnnData file#
from cellucid import show_anndata
viewer = show_anndata("my_dataset.h5ad", height=600) # written by cellucid.prepare()
viewer
Step 2 — Check that the viewer reported “ready”#
The viewer loads once the cell that showed it finishes. When it shows the data, run this in a new cell:
print("Ready payload:", viewer.state.ready)
None means it has not opened the dataset yet; run the cell again in a
moment. (A notebook widget hears the viewer only between cells, so
viewer.wait_for_ready() in the same cell would raise at once; a hook,
@viewer.on_ready, runs as soon as it is ready.)
Step 3 — Make a selection in the viewer UI#
In the embedded viewer:
use any selection tool (lasso is usually easiest), or
click cells (depending on your workflow), or
use the UI’s selection/highlight features.
For a deep dive on selection tools, see the web app guide:
Step 4 — Read the selection in Python#
After you made the selection, run this in a new cell:
selection = viewer.selection # None before the first selection
cells = selection.cells # rows of obs (0-based), ascending
print(f"Selected {len(selection)} cells (first 10):", cells[:10])
print(selection.obs_names[:10]) # their obs names
The selection reached Python while the kernel was idle, so the next cell sees it at once.
Important
Cell indices are the cells’ original positions (0-based): a cell’s row in the object or file as it was shown, in its original order.
For an object shown in its own order, that is its row as it was when shown;
selection.obs_namesare the names it had then, whatever is done to it after.An object read from a Cellucid-ready file in priority order (
anndata.read_zarrofprepare()’s output) holds its rows in another order: the viewer still names cells by original position,selection.obs_namesname the right cells, andviewer.pull(adata)/viewer.subset(adata)refuse that object untilcellucid.restore_original_order(adata)puts its cells back.To take the cells themselves, use
viewer.subset(adata)oradata[selection.obs_names]; an object reordered or subset after it was shown is refused bypullandsubsetrather than written in the wrong rows.
Step 5 — Highlight the same cells back in the viewer#
Run:
if cells:
viewer.highlight_cells(cells=cells, color="#ff00ff")
You should see the selection become highlighted (exact visual style depends on the web app UI).
Highlighted cells are stored on a named page that can be used by analysis and session workflows.#
Optional: “reactive” style with callbacks#
If you prefer event-driven code, you can register hooks:
@viewer.on_selection
def handle_selection(event):
cells = event.get("cells", [])
print("Selected:", len(cells))
Important
Hook callbacks run when the viewer’s event reaches Python on its channel, on that channel’s background thread, not in a notebook cell.
Keep callbacks fast and defensive.
If you do heavy work (Scanpy, model inference), consider queueing the work and handling it outside the callback.
Common pitfalls (read before you debug)#
Nothing prints in Step 4: you haven’t made a selection that triggers a
selectionevent yet.Selection indices don’t match your
adata: your AnnData row order changed after the viewer started.The output is empty or says the app did not load: the notebook does not show widgets (
anywidgetmissing where the notebook server runs), or the browser cannot reachhttps://www.cellucid.com(see troubleshooting).
Troubleshooting#
Start here:
report = viewer.debug_connection()
report
Then use the symptom-based guide:
Next steps#
Commands catalog (Python → viewer): Python → frontend actions
Events catalog (viewer → Python): Frontend → Python events
Viewer state + waiting: Viewer state and wait_for_event
Sessions (no download): Sessions: capture from the browser (request_session)