TableHandle
The feature table, whatever format it was imported from.
from plexora.api import TableHandleTableHandle(project: Project, provider=None)Every method warms the table first, so a caller never has to reason
about load order or touch data_model's globals directly.
Constructor parameters
projectProjectrequiredThe project record this handle wraps. A plugin
gets one from ProjectData.table, e.g.
api.project_data(name).table, rather than constructing this
directly.
providerdefault NoneA data node's own loaded table. Set only when this handle is built on a node serving several tables at once; a plugin building its own handle never passes this.
Properties
available
available: boolWhether this project has a feature table at all.
boolTrue once a table has been registered.
source_kind
source_kind: strThe feature table's format.
strOne of "csv", "parquet", "anndata" or "spatialdata".
locator
locator: ResourceLocatorWhere this project's feature table is served from.
ResourceLocatorThe table's location -- local, or on a data node.
is_local
is_local: boolWhether the table's file is on this machine.
Worth asking before offering a control rather than after pressing it: a node that is asleep cannot run an export, and a button that fails is a worse answer than one that explains itself.
boolTrue when the table is a local file.
source
source: TableSource | NoneThe feature table file's own path, format and read spec.
TableSource | NoneNone for a project with no feature table.
log_transformed
log_transformed: boolWhether frame() hands back log1p'd marker values.
Marker intensities are log-normal, so anything that fits a distribution to them -- gating's auto-threshold, for instance -- has to know which side of the transform it is standing on. This is the project's recorded answer (the log1p switch beside the matrix picker); nothing about the numbers themselves says whether they have already been transformed.
boolTrue when the values frame() returns are already log1p'd.
expression_fingerprint
expression_fingerprint: strA short string identifying which values frame() currently returns.
strWhich matrix is read and whether it is log1p'd, combined into one string. Useful as part of a cache key for anything derived from the table's values, so a cached result is invalidated when the matrix or the transform changes.
markers
markers: list[str]Columns a plugin can meaningfully threshold or plot.
This is the classification the project recorded at import -- one
answer, shared by every plugin, so two tools never disagree about
whether a column is a marker. NOT the same list as
ImageHandle.channel_names: a structural channel like DNA is
commonly a real image channel with no matching feature column.
list[str]Column names. Falls back to a histogram-based guess
for a project whose columns were never classified -- costs a
describe() call, which is why it is not the primary path, but
gives a usable answer instead of an empty panel.
metadata_columns
metadata_columns: list[str]The non-marker columns: identifiers, coordinates, morphology, annotations.
For a CSV or Parquet table this is the recorded half of the
marker/metadata split: the file's columns, minus the ones classified
as markers. For AnnData and SpatialData it is the file's own .obs
column names.
list[str]Column names.
Methods
frame
frame()The whole feature table as a polars DataFrame.
polars.DataFrame | NoneNone if this project has no feature table.
ResourceNotLocalIf the table is on a data node. Only a reduced
copy is kept on this server (see geometry()), and returning
that here as if it were the whole frame would be missing
every marker column while still answering frame["id"]
correctly -- a shape of bug that passes every test written
against a local table. Use geometry() for ids and
coordinates, columns() for named columns, or run() for
work that must read the file.
geometry
geometry()The cell id, the coordinates, and whatever other columns fill a role -- the part of the table this server always has.
Identical to frame() for an ordinary project, since it is the same
object. For a table on a data node it is the compact copy the
primary server keeps, so the spatial index, the centroid layers and
the hover lookup never wait on a network round trip. Prefer this
over frame() whenever only ids and positions are needed: it works
the same way regardless of where the table physically is.
polars.DataFrame | NoneNone if this project has no feature table.
physical_coordinates
physical_coordinates()Each cell's position in micrometres, for analysis that measures distance (neighbourhoods, contact, density) in the tissue's own units.
A polars DataFrame of the cell id (cell_id), X_um, Y_um and,
for a table with a z role (3-D objects), Z_um; with a frame role (a
time series), t too. An object table measured in µm (x_um,
y_um, z_um) is read as measured; any other table's pixel columns
are scaled by the image's pixel size and z step
(pixel_scale.spacing). Nothing is written to the table.
polars.DataFrame | NoneNone if this project has no feature table.
CoordinatesUnavailableThe pixel size is unknown (no µm at all), or the table has a z role and the z step is unknown -- say which, rather than substituting a default.
run
run(operation: str, payload: Mapping[str, Any] | None = None) -> AnyRun a registered table operation wherever this table's file is.
The way to do anything that cannot be expressed as "send me some
values": a spatial join with an ROI, writing an annotation column
onto the cells, writing gate thresholds into an AnnData's uns,
exporting the whole table as CSV. Locally this calls the operation
directly; for a table on a data node the name and payload are sent to
the node, which runs the same registered function against its own
loaded copy and sends the result back.
operationstrrequiredThe registered operation's name. See
plexora.api.table_operation.
payloaddictdefault NoneArguments for the operation.
AnyWhatever the operation returns.
stream
stream(operation: str, payload: Mapping[str, Any] | None = None)Run a registered streaming table operation, without materializing its whole result at once.
The same mechanism as run, for a result too large to build in
memory -- exporting the whole table as CSV is the current use.
Locally the generator is consumed directly; for a table on a data
node the chunks arrive off the wire and are forwarded straight
through, so the export is never held whole in either process.
operationstrrequiredThe registered streaming operation's name. See
plexora.api.table_stream.
payloaddictdefault NoneArguments for the operation.
An iterator over the operation's result, in chunks.
describe
describe() -> dictPer-column summary statistics and a histogram.
dictOne entry per column, each with summary statistics and a 50-bin histogram. Cached per datasource.
metadata_values
metadata_values(column: str) -> MetadataColumnOne metadata column's values, aligned row-for-row with frame().
The format-agnostic way to read an annotation: metadata_columns
names what a project has, this is how a plugin gets the values,
without needing to know that a CSV keeps them in the loaded frame
while AnnData and SpatialData keep them in an .obs the frame never
materializes. The same row subset that built the table is applied
here, so alignment is never the caller's problem.
columnstrrequiredA column name from metadata_columns.
MetadataColumnThe column's values.
KeyErrorIf this project has no such column.
columns
columns(names) -> dictNumeric numpy views of the named columns.
namesIterable[str]requiredColumn names to read.
dict{name: numpy array}. Cached one set of names at a time,
so repeated range queries over the same columns reuse the same
arrays.
rows
rows(ids) -> listWhole rows for a few cell ids, every marker and metadata column.
The cheap way to look at a sample of a table wherever it is: for a
table on a data node, one request for these rows rather than whole
columns. Values are as frame() reads them (log1p applied when the
project applies it).
idsIterablerequiredCell ids, as geometry()["id"] holds them. Ids
not in the table are left out.
list[dict]One {column: value} per id found, in the order asked.
range_mask
range_mask(gates: Mapping[str, tuple], mode: str = 'and')A boolean mask over table rows for a set of column ranges.
gatesdictrequired{column: (low, high)} ranges.
modestrdefault 'and'"and" (every gate must match, the
default) or "or" (matching any gate is enough).
A boolean numpy array, one entry per row of geometry().
ids_matching
ids_matching(gates: Mapping[str, tuple], mode: str = 'and') -> listCell ids whose rows satisfy a set of column ranges.
gatesdictrequired{column: (low, high)} ranges.
modestrdefault 'and'"and" (every gate must match, the
default) or "or" (matching any gate is enough).
listMatching cell ids, in table order. Empty if this project
has no feature table or gates is empty.
Examples
from plexora import api
data = api.project_data("my_project")
if data.table.available:
markers = data.table.markers
geometry = data.table.geometry()