spacr.qt.screens.embeddings¶
Workflow inputs and outputs¶
Embeddings¶
Encode object images with a chosen model and channel policy. Retain object identities and encoder provenance when supplying the features to downstream exploration.
With alpha features enabled, Save for Similar crops stores the full embedding matrix in the measurements database that supplied the loaded objects. It requires the original database object identities and verified encoder weights. Annotate can then search those stored vectors with compatible model, channel and preprocessing identities. Folder-loaded crops use the standalone Python example to save matrices; they cannot use the database save action.
Open: Home → Embeddings.
Inputs and outputs below include conditional alternatives. The guidance and handoff notes say which route applies.
Inputs
Measured objects — measurements/measurements.db; object tables depend on the enabled cell, nucleus, pathogen and organelle masks. Relevant tables, depending on the route:
cell,nucleus,pathogen,cytoplasm. Relevant columns, depending on the route:plateID,rowID,columnID,fieldID.Object crops — data/**/*_png when save_png is enabled; png_list indexes saved crops. Supported workflows can instead stream crops from merged arrays and masks. Relevant tables, depending on the route:
png_list. Relevant columns, depending on the route:png_path,prcfo.
Outputs
Image embeddings — Object-indexed encoder features, with channel policy and encoder provenance. The encoding API returns features; persistence is caller-dependent.
Before this module
Measure: Retain encoder and channel-policy provenance.
After this module
Image UMAP: Supply the encoder feature table with matching object IDs; do not assume every GUI route automatically persists it.
Embeddings — a label-free vector for every object, beside the measured panel.
A SCREEN, NOT A SCRIPT. The upstream Cell-DINO, OpenPhenom and SubCell models expose Python interfaces. This screen also accepts an explicitly mapped, digest-verified local official Cell-DINO checkpoint.
The engine serves this lab. The screen offers that embedding without any Python.
What it is for. Giving every object a vector that no label went into. The measured panel says how big a cell is and how bright each stain was; an embedding says what the cell LOOKS like, in a few hundred numbers a pretrained backbone produces without being told what to look for. That is what makes it worth having beside the panel rather than instead of it – a phenotype nobody thought to measure is in the vector, and is in no column.
What it needs. Crops, and a choice about the channels. The crops come
from the row at the top of the screen, which is spacr.crop_loader:
either a measurements.db, narrowed to one object class, one plate or
any condition its crop table supports, or a folder of crop PNGs. Whichever
it is, the pixels are read the way every other screen reads them –
spacr.crops.resolve_crop_source() decides between the crops already
under data/ and cutting them from merged/*.npy, and says which it
chose. Nothing has to be prepared first: a plate that has been through
Measure with crop output on is ready.
What it produces. One row per object and one column per dimension,
named emb_c<channel>_<dimension> under the per-channel policy and
emb_<dimension> under projection, with the model-zoo entry that made
them – backbone, policy and the checksum of the weights on this machine.
Two runs’ dimension 17 are the same number and not the same thing unless
all three match, which is why the entry is part of the result rather than
a log line. The preview shows the first few dimensions of the first fifty
objects. The alpha Save for Similar crops action stores the complete matrix
in the database that supplied the loaded objects, so Annotate can search
those vectors. Folder and programmatically supplied crops have no database
object identity and cannot use this action.
What to do next. Treat the columns as a feature source, not as a result. They are consumed exactly as the measured panel is – a reduction to see whether the objects separate, a regression against the perturbation, a retrieval to find the objects most like one you picked. Which is also the warning the screen repeats under the table: a single dimension is not a phenotype, and the only honest way to read one of these numbers is through something that takes the whole vector.
All the arithmetic is in spacr.embeddings and none is here. This module
is the surface, and three of the engine’s decisions shape it:
THE CHANNEL POLICY IS THE FIRST CONTROL, NOT A BURIED OPTION. 386 calls the channel problem “the design problem” and warns against letting it be decided implicitly by whatever the backbone wants. Per-channel encoding keeps the channel in every column name and costs N forward passes; projection mixes the channels into three and costs one. Those produce columns of identical width and dtype that mean entirely different things, so the screen makes the choice explicit and says what each one costs.
A DIMENSION IS NOT A PHENOTYPE, and the screen says so where a user will
read it. The table shows the first few dimensions because a user needs to
see that something happened; the caption says they are meaningless
individually. Presenting emb_c2_017 = 0.43 without that sentence invites
exactly the reading the family cannot survive.
THE ENCODER IS A MODEL, so it is named with its checksum. The run summary
carries the model-zoo entry from spacr.embeddings.encoder_entry() –
backbone, policy and the digest of the weights on this machine – because two
runs’ dimension 17 are the same number and not the same thing unless all
three match.
The embedding runs off the GUI thread through
spacr.qt.job_runner.JobRunner, like every other compute in the Qt
layer: a plate of crops through a backbone is minutes, and threaded=False
runs the identical code inline so a test drives the shipped path.
register() is not called at import; read its docstring.
Classes¶
Pick a crop source and a channel policy, and embed every object. |
Functions¶
|
Factory handed to |
|
Put Embeddings in the app registry. Idempotent. |
Module Contents¶
- class spacr.qt.screens.embeddings.EmbeddingsScreen(parent=None, *, threaded: bool = True)[source]¶
Bases:
PySide6.QtWidgets.QWidgetPick a crop source and a channel policy, and embed every object.
The crop loader above the encoder controls is the screen’s entry point:
spacr.crop_loaderplans the selection, this screen pages through it on a worker thread, andset_crops()– which used to be reachable only from Python – is what each finished load calls.- Parameters:
parent – the usual Qt parent.
threaded –
Falseruns the embedding and the crop load inline, emitting the same signals in the same order, so a test drives the screen synchronously without the behaviour diverging.
Build the screen: the controls above, the preview table below.
- choose_path() str[source]¶
Ask for the database or the folder, and remember the answer.
- Returns:
what was chosen, or
''when the dialog was dismissed, so a caller can tell a cancel from a choice.
- closeEvent(event)[source]¶
Let the job runner stop its thread before the widget goes.
The stop flag is set FIRST.
shutdownwaits a bounded time and then parks a worker that outlasts it, so a load told to stop between pages finishes in one page instead of reading the rest of the plate into an array whose screen has gone.- Parameters:
event – Qt’s close event, passed to the base class unchanged. Never ignored – a screen that refuses to close because a run is in flight would trap the window, so the run is stopped instead.
- crop_query()[source]¶
The
spacr.crop_loader.CropQuerythe controls describe.- Returns:
the query a press of Load would run, which is what makes the panel testable without pressing anything.
- is_busy() bool[source]¶
Whether a run is in flight.
A crop load counts. It is the longer of the two jobs by far – a plate of crops off a network mount is minutes – and a screen that reported itself idle through it would let the window close over a live read.
- load_crops() None[source]¶
Plan the selection, then read it a page at a time.
TWO JOBS, NOT ONE, and the split is the point: planning is a count and a select, so it comes back in milliseconds and the screen can say what was matched – or why nothing was – before committing to read a single pixel. Only then does the second job start, and it reports after every page.
- set_crops(crops: numpy.ndarray, *, label: str = '') None[source]¶
Hand the screen an
(objects, height, width, channels)stack.The channel axis comes last. Getting it backwards does not raise here; it reaches the model and fails there with a message about tensor shapes.
That layout is what
spacr.cropsproduces and whatspacr.embeddings.embed_array()documents.- Parameters:
crops – the object stack, channels LAST. Not copied: the screen keeps this array and hands it to the backbone, so a caller that mutates it afterwards changes what gets embedded.
- Raises:
ValueError – when the stack is not four-dimensional.
- spec()[source]¶
The
spacr.embeddings.EmbeddingSpecthe controls describe.
- spacr.qt.screens.embeddings.make_embeddings_screen(app_key: str | None = None) PySide6.QtWidgets.QWidget[source]¶
Factory handed to
spacr.qt.app.register_app().
- spacr.qt.screens.embeddings.register() bool[source]¶
Put Embeddings in the app registry. Idempotent.
Called from
spacr.qt.SELF_REGISTERING_MODULES, not at import, so importing this module to reachEmbeddingsScreenfrom a test or a notebook does not mutate process-wide state.SECTION_DATA, beside the other things that turn a table into numbers. An embedding is a feature source, not an analysis: it produces columns that the reductions, the regressions and the hit calling then consume exactly as they consume the measured panel – which is the whole point of 386 step 5.
Nested helpers¶
- EmbeddingsScreen._cell_dino_dialog.accept_mapping() None¶
Keep only plain configuration values after the form closes.
spacr/qt/screens/embeddings.py:742
- EmbeddingsScreen._cell_dino_dialog.choose_file() None¶
Choose local weights without downloading or accepting terms.
spacr/qt/screens/embeddings.py:684
- EmbeddingsScreen._cell_dino_dialog.sync_width() None¶
The fifth plane belongs only to the Cell Painting factory.
spacr/qt/screens/embeddings.py:725
- EmbeddingsScreen._learn_from_well_labels.work()¶
Score and fit off the GUI thread.
spacr/qt/screens/embeddings.py:1046
- EmbeddingsScreen._on_planned.work()¶
Read the pages on a worker thread.
emitis a bound signal and the only thing here that reaches the GUI: Qt queues it onto the receiving thread, so the progress line moves without this function ever touching a widget.spacr/qt/screens/embeddings.py:1479
- EmbeddingsScreen._pretrain_dino.work()¶
Train off the GUI thread.
spacr/qt/screens/embeddings.py:1179
- EmbeddingsScreen._read_database_choices.done(answer) None¶
Put the answer in the two combos.
spacr/qt/screens/embeddings.py:1343
- EmbeddingsScreen._read_database_choices.work()¶
Ask the database what it holds. Two cheap indexed reads.
THE STAT IS HERE, on the worker, because it is the part that blocks on a slow mount. A path that is not a file yet is not an error – it is one somebody is halfway through typing – so it answers with nothing to offer rather than raising, which would put a refusal in the status line for every keystroke.
spacr/qt/screens/embeddings.py:1328
- EmbeddingsScreen._save_for_similarity.finished(answer)¶
A newer crop selection keeps its own status and controls.
spacr/qt/screens/embeddings.py:1791
- EmbeddingsScreen._save_for_similarity.work()¶
Persist vectors without blocking the graphical thread.
spacr/qt/screens/embeddings.py:1780
- EmbeddingsScreen._subcell_channels_dialog.accept_mapping() None¶
Store only a complete four-index choice, then close.
spacr/qt/screens/embeddings.py:597
- EmbeddingsScreen._use_embeddings.work()¶
Map or classify off the GUI thread.
spacr/qt/screens/embeddings.py:882
- EmbeddingsScreen.embed.finished(answer)¶
Publish only to the crop selection that produced the vectors.
spacr/qt/screens/embeddings.py:1729
- EmbeddingsScreen.embed.work()¶
Run the backbone off the GUI thread.
The import is inside because it pulls torch in: a user who never opens this screen should not pay for it, and a user who does should pay for it once, here, rather than at launch.
spacr/qt/screens/embeddings.py:1713
- EmbeddingsScreen.load_crops.work()¶
Ask what matches. No pixels are read here.
spacr/qt/screens/embeddings.py:1413