Python → frontend commands#

This page documents how your notebook Python code can control the embedded Cellucid viewer.

Under the hood:

  • Python injects a small JS snippet into the notebook output.

  • That JS uses postMessage(...) to send a command into the iframe.

  • Commands include a per-viewer secret (viewerToken) for authentication.

If you’re new, start with the quickstart: Quickstart: minimal round-trip (select → highlight).

At a glance#

Audience

Fast path recipes (copy/paste)#

Highlight a few cells#

viewer.highlight_cells([0, 10, 42], color="#ff00ff")

Clear all highlights#

viewer.clear_highlights()

Reset camera#

viewer.reset_view()

Color by an obs field#

viewer.set_color_by("cell_type")

Hide a set of cells (advanced)#

viewer.set_visibility([0, 10, 42], visible=False)

Important

Most commands require the viewer to be displayed and (for best results) ready:

viewer.wait_for_ready(timeout=60)

If you call commands before the iframe exists, the Python side will warn and do nothing.


## Command catalog (public Python API)

All of these are methods on the `viewer` object returned by `show(...)` or `show_anndata(...)`.

| Python method | Purpose | Frontend message type |
|---|---|---|
| `viewer.send_message(message)` | Low-level escape hatch | `message["type"]` |
| `viewer.highlight_cells(cell_indices, color="#ff0000")` | Highlight specific cells by index | `"highlight"` |
| `viewer.clear_highlights()` | Remove highlights | `"clearHighlights"` |
| `viewer.set_color_by(field)` | Change active color-by field | `"setColorBy"` |
| `viewer.set_visibility(cells=None, visible=True)` | Hide/show specific cells (or all) | `"setVisibility"` |
| `viewer.reset_view()` | Reset camera | `"resetCamera"` |

Diagnostics / utilities (also useful for debugging):

| Python method | Purpose | Frontend message type |
|---|---|---|
| `viewer.debug_connection()` | End-to-end connectivity report | `"ping"`, `"debug_snapshot"` (internal) |
| `viewer.get_session_bundle()` | Request a session bundle upload | `"requestSessionBundle"` |
| `viewer.stop()` | Freeze the view (best-effort) then stop server | `"freeze"` (best-effort) |

## Message schema (what actually crosses the boundary)

### Important: you usually do **not** include `viewerId`/`viewerToken` yourself

When you call `viewer.send_message({...})`, the embedding layer automatically injects:
- `viewerId`: routes the message to the correct iframe
- `viewerToken`: authenticates the command inside the iframe

You normally send only the message-specific fields below.

### `highlight`

```json
{
  "type": "highlight",
  "cells": [0, 10, 42],
  "color": "#ff00ff"
}

Notes:

  • cells should be a list of 0-based integer indices.

  • color should be a hex string (#RRGGBB). If the UI ignores custom colors, it may fall back to a default highlight style.

clearHighlights#

{ "type": "clearHighlights" }

setColorBy#

{ "type": "setColorBy", "field": "cell_type" }

Notes:

  • In notebooks, the safest assumption is that field refers to an obs column.

  • If the field does not exist, the UI may ignore the command or surface a warning toast.

setVisibility#

{ "type": "setVisibility", "cells": [0, 10, 42], "visible": false }

Notes:

  • cells=null can be used to target “all cells” (depends on UI support).

  • If you need reproducible filtering, prefer the UI’s filter system; cell-level visibility is best treated as an interactive convenience.

resetCamera#

{ "type": "resetCamera" }

requestSessionBundle (internal, used by viewer.get_session_bundle())#

{ "type": "requestSessionBundle", "requestId": "..." }

ping / debug_snapshot (internal, used by viewer.debug_connection())#

{ "type": "ping", "requestId": "..." }
{ "type": "debug_snapshot", "requestId": "..." }

Performance notes#

  • Huge cell lists (hundreds of thousands) can be slow because they must cross a notebook boundary.

    • If you need to highlight “almost everything”, consider highlighting the small complement instead.

  • High-frequency commands (sending a highlight every mousemove) are not recommended; prefer debouncing and batching.

  • If you want to drive many interactions, consider using hooks events to decide what to do, but send commands only when needed.

Edge cases and footguns#

  • Indexing is positional: indices refer to row positions, not stable cell IDs.

  • Out-of-range indices may be ignored or may trigger errors in the frontend (depends on UI version).

  • Viewer not displayed: viewer.send_message(...) warns and does nothing.

  • Multiple viewers: make sure you are sending commands to the same viewer instance that rendered the iframe you are looking at.

Troubleshooting#

If “nothing happens” when sending commands:

  1. Confirm the viewer is displayed and ready:

    viewer.wait_for_ready(timeout=60)
    
  2. Run:

    viewer.debug_connection()
    
    • If ping/pong fails, you have a connectivity/proxy problem.

  3. If ping/pong succeeds but a specific command is ignored:

    • Clear/update the cached web UI (offline caches can pin older UI behavior):

      viewer.clear_web_cache()
      
    • Re-run the cell to create a fresh viewer.

  4. Use the full guide: Troubleshooting (hooks)

Next steps#