Version 0.9.1 is the PyPI submission release.
Added
- Scanpy's own
X_umapis served as written: when noX_umap_1d/X_umap_2d/X_umap_3d
key exists, a bareX_umapof 1, 2, or 3 columns is read at the dimension its column
count states, without renaming or writing to the caller's object. A dimensional key
still decides its own dimension and still takes precedence; any other width is refused
with its shape named. The same rule governsadd_transition_drift_to_obsmand the
browser's h5ad and Zarr readers, so one file opens the same way everywhere. - A long build reports its phases. The direct-AnnData server runs five reported steps, the
adapter reports obs classification, embedding resolution and which key resolved it,
vector-field discovery and the graph read, and the manifest and centroid pass is a step
of its own instead of silence after a success line.AnnDataAdaptertakesquiet. GET /_cellucid/protocolanswers with the wire capabilities the installed package
accepts, so a web build can ask instead of assuming.- A notebook learns when a command it sent was refused, rather than reading
Noneas
success. - Release-contract validation for package, citation, documentation, and downstream recipe
metadata; installed wheel and sdist gates on Linux, macOS, and Windows before
publication; reproducible sdist normalization. - Antialiasing is a live setting, applied on the next frame instead of at context
creation, and defaults from the cell count until the checkbox is touched. - A dataset opens at a point size derived from its cell count; the slider now
reaches 0.050 and every stored session keeps the size it recorded. - The volumetric smoke render mode is marked
alphain the viewer.
Changed
- Web app: the synthetic benchmark generator's custom point-count ceiling is
raised from 50,000,000 to 250,000,000. The previous value was a declared product
limit chosen well below the hardware's; the new one is the hardware's, sitting
just inside theMAX_TEXTURE_SIZE²bound on the renderer's interaction-state
texture (268,435,456 on a context reporting 16,384). A request that the machine
cannot allocate now fails as an allocation error rather than being refused in
advance, because the limit that actually binds is the machine's memory — 16 bytes
per point, so 4 GB of source arrays at the new ceiling — and the control cannot
know what that is. The point-count input, both validators and the worker boundary
all read the one constant, so they cannot drift apart. - The neighbor graph and the vector-field overlay are read only when asked for:
serve_connectivity=True/--connectivityandserve_vector_fields=True/
--vector-fieldson the direct-AnnData path. Each is otherwise never touched,
and a dataset serving neither publishes exactly what an object holding neither
publishes. Both are read and validated in full before the socket binds, because
the manifests the viewer fetches first declare what they contain — and on a
large object that is the longest part of startup, for a capability most sessions
never turn on. A 50-neighbor graph over 18 million cells is roughly 900 million
stored neighbors. A graph asked for and absent is reported; vector fields asked
for and undeclared serve none, which is ordinary;vector_field_defaultis
refused unless the fields it selects among are served. The startup report names
each state separately.prepare()is unchanged in Python and R — both already
tookconnectivities=andvector_fields=as data — and now announces all
three optional capabilities alike when they are not requested. - Connectivity validation is measurably cheaper: degrees are counted with
np.bincount
rather than the unbufferednp.add.at(about twenty times faster on that step), one
coordinate expansion feeds every check, and a graph already in compressed form is
compared against its own transpose instead of being rebuilt first. Measured on a
9M-stored-neighbor graph: 1.2x faster end to end and 36% lower peak memory. A graph
above 50,000,000 stored neighbors logs what it is about to cost. - The tested Python range is 3.11 through 3.14, and direct AnnData operation targets
AnnData 0.12, Zarr 3, and numcodecs 0.16. - PyPI publication uses GitHub OIDC trusted publishing and accepts only a pushed tag
matching package version metadata exactly. - Export format: every scientific payload is named by its integer index or a fixed
neutral name, so no filename carries an observation key, a gene name, or a field id. - The notebook viewers, the session applier, the data server, and the export writer are
packages rather than single files. Every public name is imported from where it was, and
the servers no longer import numpy at startup. - Adaptive level of detail is bounded by a point budget rather than a fixed ratio.
A cloud already inside the budget is never reduced, and one above it stops at the
budget instead of falling to a 44x reduction on any pull-back: at 18.1M cells that
is 2.4M points rather than 412,000. - Served gene columns are cached under a byte budget rather than a count of a
hundred columns, which on an 18.1M-cell object was 7.3 GB of resident memory.
Fixed
cellucid serve --helpno longer fails withUnicodeEncodeErroron a Windows console
using a single-byte code page such as CP1252. argparse wrote help, usage, version and
error text straight to the stream, bypassing the escaping every other console line
already gets; the CLI's parsers now route that text through the same writer, so any
character the console cannot show is printed as a backslash escape instead of
aborting the command. Theservehelp text itself is now plain ASCII.- A bind address is no longer printed as a URL.
--host 0.0.0.0used to print
http://0.0.0.0:<port>and try to open it; the banner now prints the loopback origin,
which is what a browser on the serving machine and the near end of an SSH tunnel both
use, plus the machine's own name for clients elsewhere. An IPv6 literal bind is
bracketed as a URL requires. - An import failure names the condition it is: a distribution absent from the environment
(pip install), one present but too old to hold the name asked of it
(pip install --upgrade), or a standard-library module the running interpreter cannot
provide -- reported with that interpreter's version, its executable, and the supported
range, because no package supplies it. cellucid serveanswers an operator mistake with one actionable line instead of a
traceback, and keeps the traceback for genuine defects.- Both writers refuse the same numeric input everywhere it can enter. Float32
underflow (a nonzero value below2^-149becoming0.0) is now refused for vector
fields here, and for quantized fields, embeddings, andlatent_spacein the R
writer, where normalization hid the caller's magnitude from the write-time check. - A label the browser cannot draw, including an unpaired surrogate, is refused by name.
prepare()reconciles the export root, so it cannot publish coordinates the viewer is
never told to fetch, and every manifest is checked as written bytes rather than only as
an in-memory payload.- Every published payload is little-endian whatever the host is.
prepare()and the web cache both refuse a directory nobody set aside for them,
including the working directory, the home directory, and paths reached through a link.- A constant continuous field -- a gene at one level in every cell, or a column a subset
flattened -- has an encoding instead of failing the export. debug_connection()reports the dataset probes for a published sample state.- Publishing a sample's starting view restates its identity. A view is captured
against a locally served copy of the generation, and the catalog serves the
same bytes under its own id, so a capture published as-is was refused on every
open and the sample showed none of its view. The publish step now renames the
capture -- only after re-deriving the generation's counts and cell-order digest
from what it ships -- and repoints the catalog digest itself, so the artifact
and its advertisement cannot part company. - A refusal that names two identical cell and gene counts as proof of a
different dataset now names the two dataset identities instead. prepare()no longer rejects an export because of a gene it was never asked to export.- A payload that is not entirely finite is refused with counts -- how many NaN,
infinite, or outside float32, and the first positions -- instead of a bare500.
The server answers422with that diagnosis as JSON; the exporter and the R writer
raise the same report, and the viewer shows it. - An official sample's published view applies over plain HTTP. Its integrity check used
crypto.subtle, which is secure-context only, so a LAN bind opened the dataset
uncoloured; the digest is computed the same way with or without WebCrypto. Autolevel of detail follows the camera at every dataset size. Its floor was an
absolute point budget, unsatisfiable below two million points, so on most datasets
Autoanswered full detail at every distance and did nothing; the floor is now the
smaller of that budget and an 8x reduction cap.- Level of detail no longer draws square patches. Sorting on a bit-reversed Morton
code made each level a set of whole lattice cells; sorting on the plain code and
bit-reversing the rank takes the same fraction from every neighbourhood. At 44x
reduction: density error 138% to 5%, empty populated regions 42% to none. - The velocity overlay's
Sync with LODread the level scale backwards, running at
full particle count on the coarsest level and disposing itself at full detail.
Security
- Jupyter events, session uploads, web asset inventories, and server artifacts use
authenticated schemas, bounded payloads, and path confinement. - Both servers validate the
Hostheader ahead of routing, file reads, and event
delivery. A non-loopback bind is an explicit request for network exposure and has no
authentication of any kind: every user who can reach the port reads the dataset.
Documentation
- The Python, R, and web guides are organized around the current executable contracts,
with real browser screenshots for primary workflows. - Serving from an HPC cluster is documented end to end, including why a compute node
commonly refuses SSH, the login-node port forward that works instead, why that requires
--host 0.0.0.0, and why the browser address stays127.0.0.1. - Corrected embedding guidance: an all-identical embedding has no finite normalization
range and is rejected. - Added a complete standard scVelo recipe for vector fields.