Codebase Architecture#
Audience: contributors
Time: 10 minutes
What you’ll get: what each source file does, and how one call flows through them
cellucid-r has three parts that share one core: the writer
(cellucid_prepare()), the viewer (cellucid_view(), cellucid_serve() and
the functions that talk to an open viewer, one per action of the viewer),
and the session reader (cellucid_read_session(), the history’s reading functions, cellucid_apply_session()). The core is the dataset plan: every
component checked once and a function that puts the whole store into a
backend, a folder, a ZIP file or memory. One plan for both writing and serving
is what makes the served store the same bytes as the written one.
Everything is base R plus a hand-written Zarr (format 2 and 3) and ZIP writer
and a few C routines. The imports are jsonlite (JSON), and httpuv, later
and curl for the viewer; Matrix, SeuratObject, SingleCellExperiment,
SummarizedExperiment, S4Vectors and DelayedArray are suggested, each
loaded only when an object of its class arrives; so is BPCells (not on CRAN,
so not declared), whose matrices are read through their own methods.
Files#
The writer and the plan#
File |
Responsibility |
|---|---|
|
The exported |
|
What a Seurat, SingleCellExperiment or SummarizedExperiment object holds, as the plan’s components: |
|
An expression matrix held outside R’s memory (BPCells, DelayedArray backends), read a block at a time in the order the backend stores it (whole cells, or whole genes), never whole: a counting pass (each gene’s values and extent; a value that cannot be stored refused), then placing passes of at most a quarter of the machine’s memory each, each reading only its genes’ rows ( |
|
|
|
One expression matrix with its orientation: a caller’s cells x genes, or an object’s genes x cells, never transposed or reordered whole: its gene-major indices and values are made block by block ( |
|
The Cellucid-ready MuData: MuData’s own Zarr layout, |
|
Embeddings, |
|
What counts as a matrix (base matrix, numeric data frame, |
|
Which |
|
anndata’s |
|
Names that become paths in the store: |
|
A session’s data mapping (embeddings, graph, gene names, expression matrix, latent space, vector fields, modality), checked by the web app’s and cellucid-python’s rules; also the default modality’s shared rule. |
|
|
|
Flags, strings, numbers, and the missing-required-arguments message. |
|
How values are quoted back ( |
|
Text that is the same in every R session, whatever its native encoding (Windows R before 4.2 runs in a code page): every message, warning and error is raised as a condition object ( |
|
AnnData’s encodings ( |
|
With |
|
The Zarr writer, format 2 ( |
|
The |
|
|
|
Priority order, the row order of a Cellucid-ready store: |
|
The exported |
|
The exported |
The viewer#
File |
Responsibility |
|---|---|
|
The exported viewer functions: |
|
One |
|
The exported |
|
One exported function per action of the catalogue, |
|
The channel’s commands and events (protocol schema 5), checked exactly (key sets and types), against the shared fixture |
|
JSON written as JavaScript writes it: an action command’s text (compact, keys in their order: the protocol fixture’s binary actions are matched byte for byte) and a session value’s canonical text (keys sorted by UTF-16 code units, |
|
The viewer in a Jupyter notebook (IRkernel): |
|
The round trip: the exported |
|
The web app the viewer serves: one published build of www.cellucid.com, downloaded at first use into |
|
The input-format registry, |
Sessions#
File |
Responsibility |
|---|---|
|
Reading a |
|
The reading model, function for function cellucid-python’s |
|
The history model’s rules for every record a session file holds, before anything reads it: its shape against the model’s fields, then what the types cannot say (who acted, a step’s, a turn’s and a journal entry’s own rules, and each tracker’s whole tree: one root, branches that are lines from their fork step to their tip, unique names, the head on the active branch’s path); the messages are the web app’s and cellucid-python’s, word for word. |
|
The model’s fields, constants and enumerations, written by |
|
The exported |
|
The object as the viewer showed it, through the same extraction |
|
The shared golden history ( |
|
The exported |
|
A viewer’s end for local agents: its agent port (agent events never reach |
|
Writes |
Native code and registration#
File |
Responsibility |
|---|---|
|
Every |
|
The noun a count takes in every message ( |
|
The prefix counts of a gene-major |
|
The subsampling specification’s arithmetic: uint32 hashes, exact 64-bit allocations, the spatial grid in binary64 operations never fused. |
|
The CRC-32 of a raw vector, for each ZIP entry and each gzip stream of a session file. |
|
A session file’s codecs (linked against zlib: |
|
The text JavaScript gives a number ( |
|
The process’s own standard input and output for |
|
SHA-256, which checks every file of the downloaded web build against its inventory. |
|
Bytes from the operating system’s cryptographic generator: the viewer’s id and token, never R’s own generator. |
|
One-pass finite / float32-range / whole-number checks of large matrices, and the |
|
The passes over a backed matrix’s blocks ( |
|
What |
|
A chunk’s bytes from an R vector in one pass (canonical NaN, zero padding), one block of the gene-major arrays of a genes x cells |
|
The outlier quantiles of one column, bit for bit the rule of cellucid-python’s |
|
DEFLATE of many chunks at once, on every core ( |
|
The version of a file served as it is ( |
|
Whether a process runs on this machine, never signalling it ( |
|
Stops every viewer’s server, then unloads the shared library. |
|
Hand-written and authoritative; no roxygen. |
One cellucid_prepare() call, in order#
Required arguments. Missing
out_pathandformat, and withoutdatamissingembeddings,dataset_idanddataset_name, are reported together.Output.
format,zarr_format,force,out_path(suffix, existing target).Components. With
data, the object’s components (.dataset_from()), each replaced by the argument given;modalitieschooses and orders the feature spaces.Plan.
dataset_idand the displayed text, citation and source;obsm(embeddings, then vector fields bound toX_<basis>embeddings of the same width);obs; each feature space’sX, gene names (made unique),gene_identifierssubset andvar_gene_id_column; the cell names, which must agree across inputs; the neighbour graph; the launch defaults and the default modality; withlatent_keythe outlier quantiles.Write. The plan’s
write()puts the root group,X,obs,var,obsm,obsp, the empty groups anduns(or the MuData groups) into.<name>.<pid>.partialbeside the target, the consolidated metadata last. Then the existing target (withforce) is moved aside, the new store renamed into place, and the old one deleted. On any error the partial store is removed.
Column-type problems (a Date column, an infinite value) are found while
writing, so they exercise the cleanup in step 5. cellucid_view() runs steps
3 and 4 the same way and writes into a memory store instead.
Design rules#
The store must read without a warning in Python’s anndata (0.8+ encodings) and mudata, and open in the web app;
tests/testthat/test-anndata-interop.Randtest-mudata-interop.Rcheck the first two.The store served for an object is the store written for it, key for key and byte for byte (
tests/testthat/test-served-store.R).A change to what the store holds is coordinated with
cellucid-pythonand the web app; see Cellucid-ready AnnData specification (R writer).