spacr.embeddings¶
Compute label-free image embeddings per object, beside the measured features.
spaCR’s other two routes from image to numbers both depend on prior
choices. The measured panel in spacr.measure computes a fixed,
hand-designed set – intensity, shape, texture, distances – so it captures
only named properties. The supervised path in spacr.deep_spacr
fine-tunes a backbone against annotations, so it captures only annotated
classes. A phenotype with no corresponding column or annotation (for
example a subtle vacuole mislocalisation) is not detected by either route.
Channel handling is an explicit choice. Pretrained encoders expect three channels; spaCR images often have four or five, with no RGB meaning (a DAPI channel is not “red”). Two policies are available, and the choice is recorded in the spec rather than inherited from a backbone:
Per-channel, then concatenate (
CHANNEL_PER_CHANNEL, the default). Each channel is encoded on its own and the vectors are joined. Channel identity is preserved: dimension 517 belongs to channel 2 only, sospacr.attribution_columnscan report which stain a hit came from and a reader can drop a channel without re-embedding. Costs N forward passes.Project to three (
CHANNEL_PROJECT). One pass over a fixed channel-to-RGB mapping. Cheaper, but irreversible: once channels are mixed, no downstream column can be attributed to a stain, so a screen cannot tell whether a difference came from the parasite or the host channel.
Per-channel is the default because it keeps stain attribution: an embedding that detects a phenotype without identifying the stain that carries it is of limited use. Projection remains available when the number of forward passes is the limiting cost.
Scope: this module does not crop – it takes the crops spacr.crops
produces – and does not write to the database. It returns a float32 matrix
keyed by object id, the same shape that UMAP, the regression family, hit
calling and the AnnData export already consume.
Exceptions¶
Raised when embedding is refused, with the reason a caller can show. |
Classes¶
The matrix, its column names, and how it was made. |
|
Embedding configuration, recorded with each matrix as its provenance. |
Functions¶
|
Return the channel index a column came from, or |
|
Embed a stack of crops. |
|
Names for the columns a run produces. |
|
Describe this encoder as a |
|
The stable id for one encoder configuration. |
Module Contents¶
- exception spacr.embeddings.EmbeddingError[source]¶
Bases:
ValueErrorRaised when embedding is refused, with the reason a caller can show.
Initialize self. See help(type(self)) for accurate signature.
- class spacr.embeddings.EmbeddingResult[source]¶
The matrix, its column names, and how it was made.
- Parameters:
values –
(n_objects, n_dims)float32.columns – one name per column, all starting with
EMBEDDING_PREFIX.spec – the
EmbeddingSpecthat produced it.n_channels – how many channels were encoded.
per_channel_dims – dimensions contributed by ONE channel.
- to_frame(object_ids: Sequence[Any])[source]¶
A DataFrame keyed by object id, ready to join to measurements.
- Parameters:
object_ids – one id per row, in the order the crops were given.
- Raises:
EmbeddingError – when the count does not match the matrix, because a silent misalignment here corrupts every downstream join and is unrecoverable afterwards.
- class spacr.embeddings.EmbeddingSpec[source]¶
Embedding configuration, recorded with each matrix as its provenance.
For
subcell_rybg, chooseCHANNEL_PROJECT, supply four explicit, distinctchannelsin microtubules (R), ER (Y), DNA (B), protein (G) order, and setnormalize=False. Crops must be at least 16 pixels in each spatial dimension. Native dimensions are preserved; the encoder applies the authors’ whole-crop min-max normalization.- Parameters:
backbone – Name of a timm or registered foundation encoder.
channel_policy –
CHANNEL_PER_CHANNELorCHANNEL_PROJECT.channels – which channel indices to encode, in order.
Nonemeans every channel the array has.batch_size – crops per forward pass.
device – torch device string, or
Noneto choose one.normalize – divide each channel by a fixed scale before encoding, clipped to [0, 1]. Microscopy dynamic range varies by orders of magnitude between stains, and an encoder trained on photographs will otherwise see one channel as noise.
channel_scale – that scale, one value per encoded channel in encoding order – normally each channel’s 99th percentile over a random sample of the plate. The same numbers for every crop mean an object’s embedding does not depend on which crops share its batch.
Noneletsembed_array()estimate it from the crops it is given.cell_dino_factory – pinned official Cell-DINO hub factory.
checkpoint_path – local official Cell-DINO weights file.
checkpoint_sha256 – expected SHA-256 of those weights.
- __post_init__() None[source]¶
Reject a specification that could not produce a matrix.
The check is made where the spec is built rather than where it is used, because one spec encodes every crop of a run: an unknown channel policy or a batch size below one is a typing mistake, and discovering it after the images are loaded wastes the load.
- Raises:
EmbeddingError – when
channel_policyis not one ofCHANNEL_POLICIES,batch_sizeis below one, orchannel_scaleis set withoutnormalize, holds a negative or non-finite value, or does not give one value per channel inchannels.
- spacr.embeddings.channel_of_column(column: str) int | None[source]¶
Return the channel index a column came from, or
None.A projected column returns
Nonebecause the channels were mixed and no dimension belongs to one stain. Names without the embedding prefix also returnNone.- Parameters:
column – an embedding column name.
- spacr.embeddings.embed_array(crops: numpy.ndarray, spec: EmbeddingSpec | None = None, *, encoder: Callable[[numpy.ndarray], numpy.ndarray] | None = None) EmbeddingResult[source]¶
Embed a stack of crops.
Every crop is divided by the same per-channel scale,
spec.channel_scale, so a crop’s embedding does not depend on the other crops incrops. When the spec carries none, one is estimated from a random sample ofcrops– the stack is treated as the whole plate – and a warning is logged. To embed one plate in several calls, pass the same scale to each: the returnedspeccarries it.- Parameters:
crops –
(n, height, width, channels), the shapespacr.cropsalready produces.spec – how to embed; the default is per-channel resnet18.
encoder – a callable taking
(n, height, width, 3)float32 in [0, 1] and returning(n, dims). An encoder with anin_channelsattribute takes that many planes instead, and one whosein_channelsisNonetakes any number: each channel alone under the per-channel policy, every encoded channel in one pass under the projection policy. Injected by the tests so the wiring can be exercised without downloading a backbone; production callers leave itNone.
- Returns:
an
EmbeddingResult.- Raises:
EmbeddingError – on a shape or channel problem, before any model is loaded, so an invalid array does not trigger a model download.
- spacr.embeddings.embedding_column_names(per_channel_dims: int, channels: Sequence[int], policy: str = CHANNEL_PER_CHANNEL) Tuple[str, ...][source]¶
Names for the columns a run produces.
Under the per-channel policy the channel is part of the name –
emb_c2_017– so attribution needs no separate map beside the matrix. Under projection the names usergbinstead of a channel index, because no column belongs to one channel.- Parameters:
per_channel_dims – dimensions one forward pass produces.
channels – channel indices, in encoding order.
policy – which channel policy produced them.
- Returns:
column names, in matrix order.
- spacr.embeddings.encoder_entry(spec: EmbeddingSpec | None = None, *, scorecard: Mapping[str, Any] | None = None)[source]¶
Describe this encoder as a
spacr.model_zoo.ModelEntry.The entry combines encoder configuration, available checkpoint provenance and optional retrieval metrics. Compare entries only when their channel policies are compatible.
The entry records the backbone, channel policy and digest of readable local checkpoint bytes without downloading or loading a model. Public backbones use timm cache metadata; OpenPhenom and ChAda-ViT use pinned Hugging Face revisions; SubCell uses its official torch hub checkpoint; a local DINO encoder uses its configured checkpoint path. Cell-DINO uses the declared local checkpoint only when stable regular-file bytes match its expected SHA-256; changed or mismatched files leave no digest. Foundation entries name the original provider and checkpoint URL. A checksum alone does not verify a model or establish its training-data provenance.
If no readable local checkpoint is found, the entry keeps an empty digest and explains this in its notes.
spacr.model_zoo.fetch()refuses entries without a digest.- Parameters:
spec – Encoder configuration; uses
EmbeddingSpec()when omitted.scorecard – Optional retrieval metrics measured on labelled controls, stored in
ModelEntry.metricsfor display byscorecard_lines.
- Returns:
A
ModelEntrywith kind'encoder'.
- spacr.embeddings.encoder_key(spec: EmbeddingSpec) str[source]¶
The stable id for one encoder configuration.
The key combines backbone and channel policy, because both determine what a dimension represents. The same backbone under
CHANNEL_PER_CHANNELandCHANNEL_PROJECTproduces columns of the same width and dtype that measure different quantities – one is per-stain, the other a mixture – so a key naming only the backbone would allow them to be compared.- Parameters:
spec – the run’s configuration. Only
backboneandchannel_policyreach the key. Other fields that change the values, such aschannels,normalizeandchannel_scale, are covered byEmbeddingSpec.fingerprint(); batch size and device are in neither.
Nested helpers¶
- _cell_dino_model.forward(net, x)¶
Return the official DINOv2 head output for each declared crop.
Channel-Adaptive DINO encodes each plane on its own and returns the class tokens of every plane side by side, as its official bag-of-channels evaluation does.
spacr/embeddings.py:1074
- _dino_classifier_apply.refuse()¶
Refuse an incompatible own-DINO classifier application.
spacr/embeddings.py:1910
- _dino_classifier_from_config.Classifier.__init__(self)¶
Attach recorded scaling, teacher encoder and an untrained head.
spacr/embeddings.py:1618
- _dino_classifier_from_config.Classifier.features(self, images)¶
Scale selected raw NCHW planes and return recorded encoder features.
spacr/embeddings.py:1639
- _dino_classifier_from_config.Classifier.forward(self, images)¶
Scale selected raw NCHW planes and return class logits.
spacr/embeddings.py:1667
- _dino_classifier_from_config.Classifier.train(self, mode=True)¶
Keep a frozen teacher in evaluation mode while training its head.
spacr/embeddings.py:1632
- _dino_classifier_from_config.refuse()¶
Refuse an incompatible own-DINO classifier contract.
spacr/embeddings.py:1566
- _dino_encoder.run(stack: np.ndarray) np.ndarray¶
Encode
(n, h, w, k)crops in batches at the training size.- Parameters:
stack – float32 in [0, 1], channels last.
- Returns:
(n, dims)float32 features.
spacr/embeddings.py:2009
- _dino_finetune_classifier.refuse()¶
Refuse an incompatible own-DINO classifier contract.
spacr/embeddings.py:1772
- _dino_finetune_classifier.stop_if_requested()¶
Stop between batches when the caller requests cancellation.
spacr/embeddings.py:1822
- _dino_network.Head.__init__(self)¶
Build the MLP and the prototype layer.
spacr/embeddings.py:1376
- _dino_network.Head.forward(self, x)¶
(n, dim)features to(n, out_dim)prototype scores.spacr/embeddings.py:1384
- _dino_views.uniform(low, high, *shape)¶
Uniform draws from the run’s generator, on the batch’s device.
spacr/embeddings.py:1325
- _foundation_encoder.run(stack: np.ndarray) np.ndarray¶
Encode declared Cell-DINO planes at the official crop size.
Each plane of each crop is centred and scaled to unit standard deviation after resizing, as the official evaluation transform does.
Encode
(n, h, w, k)crops in model-appropriate batches.- Parameters:
stack – float32 crops, channels last. SubCell R/Y/B/G uses native intensity values; other foundations expect [0, 1].
- Returns:
(n, dims)float32 features.
spacr/embeddings.py:963spacr/embeddings.py:997
- _hub_model.forward(net, x)¶
OpenPhenom’s mean patch token; it rescales 0-255 itself.
ChAda-ViT’s class token, each crop’s channels as one sequence.
spacr/embeddings.py:1106spacr/embeddings.py:1113
- _mil_fit._Attention.__init__(self)¶
Build the cell projection, attention gate and binary well head.
spacr/embeddings.py:2226
- _mil_fit._Attention.forward(self, x, mask)¶
Return well logits, attention and cell evidence, excluding padding.
spacr/embeddings.py:2236
- _subcell_model.Pooled.__init__(self)¶
Build the ViT and the pooler the checkpoint expects.
spacr/embeddings.py:1154
- _subcell_model.Pooled.forward(self, x)¶
(n, k, h, w)to(n, 1536), with the two heads joined.The checkpoint determines whether
kis two or four channels.spacr/embeddings.py:1162
- _subcell_model.forward(net, x)¶
Min-max scale the expected model planes, then encode.
spacr/embeddings.py:1179
- _timm_encoder.run(stack: np.ndarray) np.ndarray¶
Encode one three-channel stack, in batches, under no_grad.
Batched here rather than by the caller because the batch size belongs to the device, not to the channel policy: the per-channel policy calls this once per channel and each call must fit the same GPU.
- Parameters:
stack –
(n, height, width, 3)float32 in [0, 1].- Returns:
(n, dims)float32 features.
spacr/embeddings.py:837