spacr.qt.screens.lineage

Workflow inputs and outputs

Lineage

Inspect recorded cell/nucleus/pathogen/organelle containment links; this is object containment, not time-series lineage inference.

Open: Help search → Database Browser → Lineage.

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.

Outputs

  • Figures and table exports — The output location chosen by the tool; exports describe the selected data and filters.

API reference.

Module tutorial.

V9 B20 — the containment tree: cell → nucleus → pathogen.

The links have been in measurements.db since the first Measure run: nucleus and pathogen rows carry a cell_id naming the cell they sit inside (spacr.schema.CHILD_OBJECT_TABLES). Nothing has ever shown them. “This cell holds one nucleus and four pathogens” was a fact you could compute and could not see, and the questions that follow from it — which cells are uninfected, which pathogen is impossibly large for the cell around it, which nucleus belongs to no cell at all — had no view.

spacr.lineage builds the tree in plain pandas. This is the tree widget over it: a node selected here publishes through spacr.qt.linked_selection, so picking a cell rings the same cell on the plate view and in the UMAP, and picking it with its contents rings the whole family. Double-clicking opens the crops.

Orphans are a tab, not a footnote

A child whose cell_id names no cell is dropped by every join in the codebase. It is not noise: it means the nucleus mask found an object the cell mask did not, which is a segmentation disagreement worth looking at. It gets its own list here, with the same double-click-to-open, because a finding you have to write SQL to see is a finding nobody sees.

Classes

LineageScreen

The containment tree for one measurements database.

Functions

make_lineage_screen(→ LineageScreen)

Build the screen. The factory= for spacr.qt.app.register_app().

register(*[, section, stage, key])

Put Lineage in the app registry. Idempotent.

Module Contents

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

Bases: spacr.qt.linked_selection.LinkedView, PySide6.QtWidgets.QWidget

The containment tree for one measurements database.

Parameters:
  • threaded – False reads inline, so a test drives the screen without a worker thread and gets the same calls in the same order.

  • parent – parent widget; ownership only.

Build the screen, join the shared selection and arm its drop zone.

Parameters:
  • parent – parent widget, or None.

  • threaded – read the database on a worker thread. Set False in tests so load finishes before it returns.

closeEvent(event) → None[source]

Stop background work and unlink before going away.

Parameters:

event – the Qt close event.

collision_note() → str[source]

One line naming any objects the shared key cannot tell apart.

Empty, now and normally. The object key carries the object type, so a cell’s nucleus 1 and its pathogen 1 are two keys; this used to fire on every family that had both, and it is kept as the alarm for that ever being true again. It is cheap, and the failure it watches for — “opening four objects showed three crops” — is otherwise a mystery rather than a message.

family_ids() → List[str][source]

The same rows by table-qualified identity — one entry per object.

family_keys() → List[str][source]

The selected rows and everything inside them, parents first.

De-duplicated while keeping order: selecting a cell and one of its pathogens must not open that pathogen twice.

The de-duplication is on the SHARED key, which is what the routing contract takes — so a cell whose nucleus 1 and pathogen 1 collide yields three keys for four objects. That is not a bug here, it is the key having no object type in it; collision_note() is what puts it on screen instead of leaving the arithmetic unexplained.

load() → None[source]

Read the object tables and build the forest, off the GUI thread.

on_linked_selection_changed(selection) → None[source]

Reveal and highlight what another view selected, when we hold it.

Parameters:

selection – the shared selection published by another view; its keys are matched against the tree’s object keys, and None keys clear the highlight.

open_selected() → Any[source]

Open the selected objects as crops, parents before their contents.

publish_family() → List[str][source]

Publish the selected objects together with everything inside them.

selected_keys() → List[str][source]

The object keys of the selected tree rows, in tree order.

set_frames(frames: Dict[str, pandas.DataFrame]) → None[source]

Build and draw the tree from already-loaded tables.

The seam a test — or another screen holding the same frames — goes through, so nothing here needs a database to be exercised.

Parameters:

frames – {table name: rows}, as spacr.lineage.build_forest() takes it; None counts as empty. A LineageError from building the tree is shown in the summary instead of raised.

spacr.qt.screens.lineage.make_lineage_screen(**_kwargs) → LineageScreen[source]

Build the screen. The factory= for spacr.qt.app.register_app().

spacr.qt.screens.lineage.register(*, section: str | None = None, stage: str | None = None, key: str = APP_KEY)[source]

Put Lineage in the app registry. Idempotent.

Returns:

the registry row, or None when the key was already there.