Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Cube files and provenance records

A cube file stores a cube so that any Zarr v3 reader can open it, and so that a reader can tell whether it holds what was written. Its fingerprint names the cube’s values, and its provenance record says how the cube was made. This page describes all three, for programs that read or write cubes; Export a cube from another tool shows the steps in code.

The file

write_cube writes a directory, by convention <name>.cube.zarr, holding a Zarr v3 group Miles et al., 2025; read_cube opens it. Format cytepool.cube, version 1:

<name>.cube.zarr/
  zarr.json      the root group, with the attributes below
  samples/       one 1-D array per sample column
  groups/        one 1-D array per group column
  features/      one 1-D array per feature column
  n_cells        integers (n_samples, n_groups); absent from bulk cubes
  layers/
    counts       (n_groups, n_samples, n_features)
    ...          detected, log_sum, log_sum_sq and linear_sum, when present
Root attributeValue
format"cytepool.cube"
format_version1; a reader rejects other versions
axes["groups", "samples", "features"], the axes of every layer
layersthe layer names, in the order counts, detected, log_sum, log_sum_sq, linear_sum
has_n_cellswhether n_cells is present
normalizationhow the normalized layers were computed, or null
provenancethe provenance record (below)
digeststhe digests of the parts and the fingerprint (below)

Other attributes are ignored, so a publisher may add, say, a title.

Tables. Each column is a 1-D array with dimension_names set to its axis. Text is stored as int32 codes with the attribute levels (the distinct values in order of first appearance; -1 marks a missing value); datetimes and timedeltas as int64 counts of their unit; booleans and numbers as their own type. The group’s attributes columns and arrays give the column names and the array of each.

Layers. Chunks hold one group, every sample and up to 4,096 features, so one group, or a few features of it, is read without the others. Every array uses the bytes codec (little endian) followed by gzip (the default; Deutsch (1996)), zstd (Python 3.14 or newer; Collet & Kucherawy (2021)) or no compression Zarr developers, 2023Maitin-Shepard, 2025. Chunks of zeros are not stored.

Reading and writing. Reading is strict: the format, every attribute, every array’s metadata and every value is checked, and the tables, cell numbers and normalization are checked against their digests when the file is opened, and each group of a layer whenever a read covers all of it. Cube.verify_fingerprint checks everything. Writing is atomic: the store is written to a hidden directory beside the target and renamed into place when it is complete.

Fingerprints

Cube.fingerprint identifies the values an analysis reads: the sample, group and feature tables, the cell numbers, the normalization and every layer. It leaves out the provenance, so two cubes with the same values have the same fingerprint however they were made, and a result can name the cube it was computed from.

It is built from digests, each the SHA-256 National Institute of Standards and Technology, 2015 of a byte string, written as sha256: and 64 hexadecimal digits: one per table, one for the cell numbers, one for the normalization, and one for each group of each layer, so that one group can be checked without reading the others. Numbers are little endian; integer layers are hashed as int64 and float layers as float64, with -0.0 written as 0.0. The fingerprint is the digest of a text manifest that lists them:

cytepool cube fingerprint 1
shape <groups> <samples> <features>
samples <digest>
groups <digest>
features <digest>
n_cells <digest, or none without cell numbers>
normalization <digest, or none without normalization>
layer <name> <int64 or float64> <group position> <digest>

with a layer line for every group of every layer. The 1 of the first line is the version of the encoding (FINGERPRINT_VERSION). Cube.digests returns the digests in the form the file stores them, and the module documentation of cytepool.cube.fingerprints gives the encoding of every part byte by byte, so that another program can compute the fingerprint. The encoding was checked against an independent implementation (Validation).

Provenance records

Cube.provenance is a JSON object with three keys that CytePool writes; Cube.history and describe_provenance print it in plain terms.

KeyWhat it records
sourceWhere the data came from, as the caller gives it to from_cells, from_bulk, from_anndata, from_mudata or Cube(provenance={"source": ...}). Stored as given and never interpreted; its references are reported as unresolved, since following them needs the tool that wrote them.
constructionHow the cube was built: the function, the CytePool version and its settings, such as the layers computed, the target sum of the normalization and where the sample, group and feature ids came from.
operationsEvery operation that derived the cube, oldest first: select, regroup or collapse_samples, with what it kept, merged or pooled, and input, the fingerprint of the cube it was applied to.

Because each operation names its input by fingerprint, a chain can be checked link by link: apply each operation to the cube with its input fingerprint, and compare the result’s fingerprint with the next record’s input. The results of an analysis, such as limma_voom’s, keep the fingerprint of the cube they were computed from, the digests it is built from and its provenance record.

What an exporter records

A tool that pools cells into a cube, such as CyteArc or another single-cell analysis platform exporting its pseudobulk profiles, builds a Cube from its sums, with lazy layers if they are large, and passes its own record as source. CytePool keeps whatever the record holds; these fields make a cube traceable to the cells it came from:

FieldExample
the tool and its version{"tool": "example-exporter", "version": "0.1"}
the dataset’s identityan accession or a dataset id, with the checksum or version of the data read
the frozen cell selectiona reference to the exporter’s record of which cells entered each sample and group, such as a stored membership and its digest
the sample and group columnswhich cell annotations defined the samples (for example donor and condition) and the groups (the cell-type annotation and its version)
the settingsfilters, the counts layer used, the normalization of the summed values

Cell memberships stay in the exporter’s files; the cube records how to find them.

References
  1. Miles, A., Striebel, J., Rzepka, N., Maitin-Shepard, J., & Moore, J. (Eds.). (2025). Zarr core specification, version 3.1. https://zarr-specs.readthedocs.io/en/latest/v3/core/
  2. Deutsch, P. (1996). GZIP file format specification version 4.3 (RFC No. 1952). RFC Editor. 10.17487/RFC1952
  3. Collet, Y., & Kucherawy, M. (2021). Zstandard Compression and the “application/zstd” Media Type (RFC No. 8878). RFC Editor. 10.17487/RFC8878
  4. Zarr developers. (2023). Bytes codec, version 1.0, and gzip codec, version 1.0. Zarr specifications. https://zarr-specs.readthedocs.io/en/latest/v3/codecs/
  5. Maitin-Shepard, J. (2025). Zstd codec. Zarr extensions. https://github.com/zarr-developers/zarr-extensions/tree/main/codecs/zstd
  6. National Institute of Standards and Technology. (2015). Secure Hash Standard (SHS) (Federal Information Processing Standards Publication No. 180–4). National Institute of Standards and Technology. 10.6028/NIST.FIPS.180-4