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.

API reference.

Module tutorial.

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

EmbeddingsScreen

Pick a crop source and a channel policy, and embed every object.

Functions

make_embeddings_screen(→ PySide6.QtWidgets.QWidget)

Factory handed to spacr.qt.app.register_app().

register(→ bool)

Put Embeddings in the app registry. Idempotent.

Module Contents

class spacr.qt.screens.embeddings.EmbeddingsScreen(parent=None, *, threaded: bool = True)[source]

Bases: PySide6.QtWidgets.QWidget

Pick 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_loader plans the selection, this screen pages through it on a worker thread, and set_crops() – which used to be reachable only from Python – is what each finished load calls.

Parameters:
  • parent – the usual Qt parent.

  • threaded – False runs 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. shutdown waits 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.CropQuery the controls describe.

Returns:

the query a press of Load would run, which is what makes the panel testable without pressing anything.

crop_record() → Dict[str, Any][source]

What the last load actually read, as the loader recorded it.

crop_source() → str[source]

Which kind of place the crops come from.

embed() → None[source]

Runs the encoder in the background and shows the result.

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.

is_loading() → bool[source]

Whether a crop load is in flight.

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.crops produces and what spacr.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.EmbeddingSpec the controls describe.

stop_loading() → None[source]

Stop a load between pages, keeping the pages already read.

The flag is checked before each page rather than inside one, so a stop costs at most one page and never leaves a half-written crop in the stack.

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 reach EmbeddingsScreen from 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.

emit is 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