Round trip (results back from the viewer, columns and gene sets into it)#
What a person makes in Cellucid – highlight groups (lasso, k-NN, proximity,
category and range picks), labels and annotations, clusters made from pages,
gene-signature scores, gene sets – comes back into your AnnData (or
MuData) in one call, and what you compute in Python goes into the viewer
the same way. R’s cellucid_pull(), cellucid_push() and friends do the same
for Seurat, SingleCellExperiment and data frames
(Public Functions).
You want… |
Call… |
|---|---|
everything made in a live viewer, as |
|
the same from the web app’s exports, without a viewer |
|
the cells of a highlight group or the last selection, as an AnnData |
|
the last selection, with obs names |
|
every gene set the viewer holds |
|
a column (labels or a score) shown in the viewer |
|
a gene set offered in the viewer |
|
a GMT file |
|
cells highlighted from Python, by row, mask or obs name |
Nothing waits#
The viewer sends what is made as it is made: once its dataset opens (and
whenever its channel opens again) every result and then an inventory, and
each change after. AnnDataViewer.results is that copy, and pull,
subset, gene_sets and selection read it at once, with no request. In a
notebook the widget hears the viewer between cells, so a lasso drawn and
viewer.pull(adata) run in the next cell finds it; a browser-tab
AnnDataViewer works the same way.
One page, one dataset. The copy is of one epoch: one page’s account of one dataset it opened. A reloaded page, or the viewer shown again in another output, replaces what an older page sent, whatever its revisions. While the person has another dataset open in the viewer (one picked in it instead of the notebook’s),
resultsand everything reading it raise, naming the dataset to open again: that dataset’s cells are notadata’s.Pushes and mappings wait for the viewer, not the cell.
push,gene_set_saveandmapping_set(like every action) send at once and return their request. A column of a format only the browser reads goes once the viewer has said how many cells it has – so a notebook run top to bottom (“Run All”) works. The field or set is listed inviewer.results, and the mapping isAnnDataViewer.data_mapping, from a later cell; anything refused fails its request (request.result()raises with the reason) and is printed unless acommand_errorhook handles it.Sessions in two steps. A session (the viewer’s history) is asked for, then collected – the one way, in a notebook (in a later cell) and a browser tab alike:
request = viewer.request_session(), thenrequest.result()orviewer.apply_session_to_anndata(adata, request), which returns the plan (plan.apply()writes it) (R:cellucid_request_session(), thencellucid_session_result()orcellucid_apply_session(object, request), thencellucid_apply_plan()).
Fast path#
import cellucid
viewer = cellucid.show_anndata(adata, dataset_name="My study", dataset_id="my-study-v1")
# … lasso cells, label clusters, score a gene signature in the viewer, then:
summary = viewer.pull(adata)
summary.columns # every obs column written, and what it holds
adata.uns["cellucid"]["gene_sets"] # every gene set, by name
viewer.push(adata.obs["leiden"]) # the clusters, as a field in the viewer
viewer.gene_set_save(name="T cells", genes=["CD3E", "CD3D", "IL7R"])
t_cells = viewer.subset(adata, "Lasso selection")
Without a notebook, export from the web app (Highlights: CSV or .h5ad; a field’s Export button; a gene set editor’s Export; Save session), then:
cellucid.pull(adata, "~/Downloads/cellucid-exports") # a folder, a file, or a list of files
What pull writes#
Result |
|
dtype |
|---|---|---|
a highlight group |
|
|
a user-defined categorical field (labels, annotations, clusters) |
|
|
a continuous field, a score (a gene signature’s: |
|
|
the cells the filters leave visible ( |
|
|
a gene set |
– |
|
Genes by identifier. A gene set’s
genesare the var index, asadata.var_namesholds them. When the viewer’s Data mapping names the genes by a var column (such as a CELLxGENE dataset’sfeature_name),symbolsholds the names it shows for them; named by the var index, the identifiers are the names and there is nosymbols.AnnDataViewer.gene_sets()returns the identifiers, or withsymbols=Truethe names shown. A gene set name used twice is"name (2)","name (3)"…, as everywhere in Cellucid.Missing values. A cell with no category or no value is missing (
NaN).Subsamples. When the viewer opened a subsample, every result names cells by their row in
obs(original positions). A cell the viewer did not open is<NA>in a highlight column (the nullablebooleandtype) and missing in a field’s: unknown, not “not selected”.The cells in the viewer’s order. A viewer names a cell by its original position: an object’s own row, or, for a file
prepare()wrote (cells stored in priority order,uns["cellucid"]["priority"]verified against its cells), the original position its record gives the row.pullandsubsetfrom a viewer refuse an object holding the viewer’s cells in another order – that file read withanndata.read_zarras stored (put it back withrestore_original_order()), or an object sorted since – and an object whose cells the viewer’s dataset does not have; nothing is written. R’scellucid_pull()andcellucid_subset()refuse the same.Again and again.
uns["cellucid"]["pulled"]records every column a pull wrote: where it came from (pulled_from:"viewer", or the file’s name) and the result it holds (kind,id). A pull of the same result writes the same column again, whatever suffix it took. Withprune=True(the default) a viewer pull removes the columns – and the gene sets – of an earlier viewer pull whose result the viewer no longer holds. A result left out of a pull (highlights=False, a pushed field withoutinclude_pushed) keeps its column; a file’s columns are never removed.Conflicts. A column of the same name that no pull from the same source wrote is a conflict:
on_conflict="error"(the default) raises and writes nothing,"replace"overwrites it,"suffix"writes<name>_2beside it (and the next pull writes<name>_2again). Session files follow the same rule.Pushed columns. A field this notebook pushed comes back only with
include_pushed=True:adataholds it already.One call, one write. Every column is built first and
obschanges once: a pull that fails leavesadataas it was. A MuData takes the columns on its globalobs.
apply_cellucid_session_to_anndata() plans, then writes, the state at
any step of a saved session (its highlight groups and fields, with the same
column rules, its gene sets and its renames). pull of a .cellucid-session
file writes the highlight groups and user-defined fields of the state the
session shows (its active tracker’s head), checked against adata as an
apply checks it (the dataset, the cells and their order, the fields that
state uses), names them with every other column of the pull, and adds the
session’s gene sets; the renames are the apply’s to write.
The web app’s exports#
Export |
File |
Read back as |
|---|---|---|
Highlights › CSV |
a |
one |
a field’s Export |
the field under its name, of kind |
a |
Highlights › .h5ad |
the highlighted cells as an AnnData, in the dataset’s own order (ascending original position): obs names, every obs field (continuous ones as |
|
a gene set editor’s Export, Marker genes › GMT |
GMT, genes by identifier |
|
Save session |
|
An obs-column CSV declares itself, so nothing is guessed from its values:
obs_name,leiden,cellucid_highlight__highlight_1,score
kind,categorical,highlight,continuous
AAACCTG-1,0,true,0.25
AAACGGG-1,10,false,
The header row names the columns (obs_name first); the kind row (kind
first) gives each column’s kind: highlight (true, false, or empty for
unknown), categorical (labels – 0, 1, 10 or True stay labels;
empty: no category) or continuous (numbers; empty: no value). A CSV
without a kind row is refused, saying how to add one. A cell the file does
not list is unknown (<NA>, or missing), as an unopened cell of a subsample;
a cell the file names that adata does not have is refused, naming it.
GMT files follow one rule in the web app, Python and R: cells are
tab-separated and trimmed; each run of tabs and line breaks inside a name,
description or gene is written as one space; a line without a name reads as
Set <n>; a name used again is name (2), name (3)…; each gene is kept
once. write_gmt() writes a name that repeats an earlier one, once
cleaned, as read_gmt() will name it.
Pushing into the viewer#
AnnDataViewer.push() takes a pandas.Series aligned by its index
(obs names: every cell, or some, the rest without a value) or an array of one
value per cell. Categorical, string, object and bool values become a
categorical field (a bool column’s labels are "false" and "true", as R
pushes a logical one); numbers a continuous one. Pushing the same key again
replaces it; a field of that key the notebook did not push is never replaced
(the viewer refuses it, and the request’s result() raises the reason). A pushed column is saved with the
session (a step of its history, the column’s values in that step’s state).
viewer.gene_set_save(name=..., genes=...)
(the gene-set.save action) adds a gene set (or replaces the one of that
name), its genes by identifier (adata.var_names) or by the names the viewer
shows when a var column names them (identifiers first): Gene signature and
Dot plot offer it under From notebook, and it is saved with the session
(its gene-sets state).
The wire (for developers)#
Results travel on the viewer channel (Architecture: the viewer channel,
protocol schema 4, viewer-protocol.json):
the
resultevent (binary): a header{type, epoch, revision, kind, id, nObs, meta, cells, values}and a body ofcellsoriginal positions (uint32, strictly ascending, none whencellsisnull) thenvalues(one per named cell, or per cell of the dataset whencellsisnull; a code type’s largest value is “no category”,NaN“no value”). Kinds:highlight,categorical,continuous,geneSet(genesby identifier, the var index, andsymbols, the names shown in the same order when a var column names the genes, elsenull),visibility(visible),subset(open, the open cells);result_removed {epoch, revision, kind, id}andresults {epoch, dataset, revision, nObs, items}, the inventory ending a snapshot: after a dataset opens (a page reload included) and whenever the channel opens again the viewer sends every result and the inventory, so a notebook’s copy is replaced whole.datasetis the id of the notebook’s dataset the results describe, ornullwhile another one is open in the viewer (thenitemsis empty and no result, selection, hover or click is sent);an
epoch(16 hexadecimal characters, drawn at random) is one page’s account of one dataset: a new page, and every dataset a page opens, start one, with revisions from 1. The notebook keeps one epoch’s results; an event of another epoch starts afresh. Within an epoch revisions grow with every result and removal: a frame whose revision the notebook already holds is one it applied;a pushed column (
field.push, a binaryaction) and a gene set (gene-set.save) are actions: each is answered by itsaction_result(the outcome names the field or set it made; theresultit made follows as for any result) or by acommand_errorwith itsrequestId;color_by {field, source, symbol, cells_read}: a gene by its identifier, and the name the viewer shows for it when a var column names the genes (symbol;nullotherwise, and for an obs field); for a gene read for a sample of the open cells (a large dataset stored cell by cell opens every cell on the map and reads a gene for the first of them: Genes on a sample of the cells), how many of the open cells it is read for (cells_read;nullwhen every open cell’s are, and for an obs field), sent again when that grows.AnnDataViewer.resultsis the live copy;viewer.register_hook("result", …)runs on each. Nothing in the round trip is a request the notebook waits on.
API reference#
- cellucid.pull(adata, source, *, highlights=True, fields=True, scores=True, visibility=False, gene_sets=True, include_pushed=False, highlight_prefix='cellucid_highlight__', field_prefix='', on_conflict='error', prune=True)[source]#
Write what was made in Cellucid onto
adatain one call, in place.Every highlight group (a
boolcolumn each), every user-defined categorical field – labels, annotations, clusters – (category), every continuous field and score (float32) go toadata.obs(a MuData’s globalobs), and every gene set toadata.uns['cellucid']['gene_sets']: its genes by identifier – the var index,adata.var_names– and, from a viewer whose data mapping names the genes by a var column (such asfeature_name), the names it shows for them (symbols).- Parameters:
adata (anndata.AnnData or mudata.MuData) – The object the viewer shows, with its cells in the same order. From a viewer, an object holding the viewer’s cells in another order is refused (nothing is written), as is one read from a Cellucid-ready file in its stored order (
obs['cellucid_original_position'];cellucid.restore_original_order()puts it back).source (AnnDataViewer, path, or list of paths) – A viewer (a notebook widget or a browser-tab viewer): what it holds, as it last sent it – every result as it was made, and all of them whenever it opened its dataset, so a cell run after the work pulls it all, with no waiting – or what the web app exports with no notebook: obs-column
.csvfiles (a header row,obs_namethen each column’s name; a kind row,kindthen each column’s kind,highlight,categoricalorcontinuous; a row per cell; each column written as named),.gmtgene sets and.cellucid-sessionfiles (the state the session shows, its active tracker’s head: its highlight groups, user-defined fields and gene sets, checked againstadataas an apply checks it;apply_cellucid_session_to_anndata()applies another step, and the session’s renames) – one file, a list, or a folder of them.highlights (bool) – Which results to write: highlight groups; user-defined fields; scores an analysis made (a gene signature’s); the filter’s visible cells (
cellucid_visible, off by default); gene sets.fields (bool) – Which results to write: highlight groups; user-defined fields; scores an analysis made (a gene signature’s); the filter’s visible cells (
cellucid_visible, off by default); gene sets.scores (bool) – Which results to write: highlight groups; user-defined fields; scores an analysis made (a gene signature’s); the filter’s visible cells (
cellucid_visible, off by default); gene sets.visibility (bool) – Which results to write: highlight groups; user-defined fields; scores an analysis made (a gene signature’s); the filter’s visible cells (
cellucid_visible, off by default); gene sets.gene_sets (bool) – Which results to write: highlight groups; user-defined fields; scores an analysis made (a gene signature’s); the filter’s visible cells (
cellucid_visible, off by default); gene sets.include_pushed (bool) – Also write the fields this notebook pushed (
viewer.push), whichadataholds already.highlight_prefix (str) – Each highlight’s column is
<highlight_prefix><group id>; each field’s and score’s<field_prefix><key>.field_prefix (str) – Each highlight’s column is
<highlight_prefix><group id>; each field’s and score’s<field_prefix><key>.on_conflict ({"error", "replace", "suffix"}) – What a column name that
adata.obshas already does, when no pull from the same source (the viewer, or the same file) wrote it: raise (nothing is written), overwrite it, or write<name>_2beside it. A result an earlier pull wrote is written to the same column again, suffix and all.prune (bool) – For a viewer: remove the columns, and the gene sets, an earlier viewer pull wrote whose result the viewer no longer holds (a highlight deleted since). A result left out of this pull (
highlights=False, a pushed field) keeps its column, and files never remove anything.
- Returns:
The columns written (
columns), removed (removed), the gene sets brought (gene_sets) and removed (removed_gene_sets), and the results left out (skipped).- Return type:
Notes
When the viewer opened a subsample, a cell it did not open is
<NA>in a highlight column and missing in a field’s: unknown, not “not selected”; a cell an obs-column CSV does not list is the same.adata.uns['cellucid']['pulled']records each column written, with where it came from (pulled_from) and the result it holds (kind,id); each gene set inuns['cellucid']['gene_sets']the same.
- cellucid.write_gmt(gene_sets, path, *, descriptions=None)[source]#
Write gene sets as a GMT file (MSigDB): one set per line,
name<TAB>description<TAB>gene....- Parameters:
- Returns:
The file written. Each run of tabs and line breaks in a name, description or gene is written as one space; a name that then reads as an earlier one’s is written
"name (2)"(asread_gmt()names a repeated name); each gene once. The Cellucid web app reads it (Gene signature and Dot plot: Import), as doread_gmt()and cellucid-r’scellucid_read_gmt().- Return type:
Path
- cellucid.read_gmt(path)[source]#
Read a GMT file into
{set name: [genes]}, in file order.One rule with the web app and cellucid-r: cells are tab-separated and trimmed; blank lines are skipped; a line without a name is
"Set <n>"(its place among the sets); a name used again is"name (2)","name (3)"…; each gene is kept once.
- class cellucid.PullSummary(columns=<factory>, removed=<factory>, gene_sets=<factory>, removed_gene_sets=<factory>, skipped=<factory>)[source]#
Bases:
objectWhat
pull()wrote.columnsmaps eachobscolumn written to what it holds (kind,id,pulled_fromand the result’s label, key or page);removedlists the columns of an earlier viewer pull whose result the viewer no longer holds;gene_setsmaps each gene set brought to its genes, andremoved_gene_setsnames the gene sets of an earlier viewer pull the viewer no longer holds;skippednames results left out and why.- Parameters:
- class cellucid.Selection(cells, obs_names, source)[source]#
Bases:
objectThe cells of the viewer’s last selection: original positions, their obs names (when the viewer’s dataset has them in this process), and the tool that made it.
- class cellucid.ViewerResult(kind, id, revision, n_obs, meta, cells, values, column=None, source='viewer', known=None)[source]#
Bases:
objectOne result the viewer holds (the protocol’s
result).cellsare original positions (rows ofobs), ascending, or None for every cell;valuesare a field’s codes or numbers, one per cell named (or per cell of the dataset), or None.- Parameters:
-
column:
str|None= None# The
obscolumn it is written to, when it names one itself (a column of an obs-column CSV); None follows the naming scheme.
-
known:
ndarray|None= None# A highlight’s known cells, when the source says which (an obs-column CSV lists them); None: the cells the viewer opened.
- classmethod from_event(payload)[source]#
The result of one decoded
resultevent payload.- Return type:
- Parameters:
- __init__(kind, id, revision, n_obs, meta, cells, values, column=None, source='viewer', known=None)#
The viewer methods (AnnDataViewer.pull(),
AnnDataViewer.results, AnnDataViewer.selection,
AnnDataViewer.obs_names, AnnDataViewer.subset(),
AnnDataViewer.gene_sets(), AnnDataViewer.push(), and the action
methods such as gene_set_save and highlight_cells) are documented with the
class in Viewer classes (Jupyter objects you control from Python).