spacr.qt.widgets.fold_strip

A row of icon buttons, one per module folded into a host screen.

A module that has been folded into another one stops being a tile on Home and becomes a button on its host’s masthead. The button IS the module’s icon: no text beside it, the module’s own one-line description as its tooltip, and the same maturity colour on hover that the tile used to light up in – green-cyan for alpha, magenta for beta, blue for stable.

That last part is the whole point of doing it here rather than with a plain QPushButton at each site. The hover colour is not decoration, it is the maturity of the code behind the button, and it is read from spacr.qt.theme.STAGE_HOVER through spacr.qt.app.app_stage() – the same two tables the tiles read. A fold that hard-coded its colour would drift the day a module was signed off, and the button would go on promising alpha long after the tile had stopped.

Typical use, from a host screen’s __init__:

from spacr.qt.widgets.fold_strip import FoldStrip

self.folds = FoldStrip([
    ("train_cellpose", self._open_training),
    ("model_zoo",      self._open_model_zoo),
], parent=self)
masthead_layout.addWidget(self.folds)

Each entry is (app_key, callback), or (app_key, callback, checkable) when the button is a switch rather than a press. The key is the registry key the module had as a tile, which is what supplies the icon, the name and the stage; the callback is what the button does when pressed.

A checkable fold controls a workflow implemented by settings on its host. Timelapse settings are mounted on Mask Generation because tracking extends the segmentation run; its button reveals those categories and enables the corresponding pipeline stage. Motility Assay instead opens from Measure and analyzes existing masks. A checkable button remains active while its workflow is enabled, and its callback receives the new state.

Classes

FoldButton

One folded module, drawn as its own icon and nothing else.

FoldStrip

The row of FoldButton for one host screen.

Functions

fold_label(→ Tuple[str, str, str])

(name, description, stage) for one folded key, from anywhere.

folded_fallback(→ Tuple[str, str, str])

(name, description, stage) a folded key kept, or three blanks.

folded_modules(→ dict)

Every folded key, as key -> (name, description, stage, host).

mark_folded_categories(→ dict)

Mark host categories associated with folded modules.

mark_folded_sections(→ Tuple[str, ...])

Mark settings-section headings with a folded module's icon.

Module Contents

class spacr.qt.widgets.fold_strip.FoldButton(key: str, parent: PySide6.QtWidgets.QWidget | None = None, checkable: bool = False)[source]

Bases: PySide6.QtWidgets.QPushButton

One folded module, drawn as its own icon and nothing else.

Parameters:
  • key – the module this button opens. Also read for the name, description and maturity stage, so it must be a real registry key rather than a caption.

  • parent – parent widget.

  • checkable – whether the button is a SWITCH rather than a press. True for a fold whose workflow is implemented by settings on the host module – Timelapse rides on Mask Generation, because tracking extends the segmentation run – so the button stays active while that workflow is enabled and its callback receives the new state. False, the default, is a plain click that opens a module.

Build one fold button: a module’s icon, with its name in the tooltip.

The icon is resolved through the application’s own lookup rather than by filename, because a module that BORROWS another’s picture is resolved wrongly by filename alone – the Cellpose Workbench button drew a dumbbell, since train_cellpose.png exists and is the training glyph while the override sends that key to the cell outline. Every other borrower had the same fault. With no icon at all the button falls back to the module’s initial rather than to an empty square.

The release stage rides as a Qt property so the stylesheet can select on it, set before the first polish so the first paint is already the right colour, and the name and summary are stored as canonical English so a language switch retranslates rather than translating a translation.

Parameters:
  • key – the module this button opens.

  • parent – parent widget, or None.

  • checkable – make it a toggle rather than a one-shot button.

paintEvent(event)[source]

Draw the folded module’s icon, and its maturity as a rim colour.

THE COLOUR CARRIES INFORMATION, so it is drawn rather than left to a stylesheet: alpha and beta modules are marked, and a reader who cannot distinguish the hues still gets the tooltip.

Parameters:

event – the Qt paint event.

set_stage(stage: str) → None[source]

Re-state the maturity this button is drawn in.

The stage is read from the app registry when the button is built, and the registry stops answering for a module the day its row is dropped – which is what folding one ends in. Restating it has to move BOTH things the stage decides: the Qt property the shipped hover rule selects on, and the widget-local :checked fill, which is a stylesheet computed once from whatever the stage was at construction. Moving only the property left a switch that hovered in its own colour and lit stable-blue when it was on.

Parameters:

stage – "alpha", "beta" or "stable".

set_verdict(level: str, detail: str = '') → None[source]

Badge this button with a run’s worst verdict.

A DOT, NOT A COLOURED BUTTON. The button’s own colour already means maturity – alpha, beta, stable – and a second meaning on the same surface makes both unreadable. The dot is drawn over the corner instead, and the detail goes in the tooltip where the reason can be a sentence.

"" clears it, which is what a run that has produced no diagnostics yet must show: no dot rather than a green one, because “not measured” and “measured and fine” are different answers.

Parameters:
  • level – "pass", "check", "fail" or "".

  • detail – the sentence behind the verdict, for the tooltip.

class spacr.qt.widgets.fold_strip.FoldStrip(folds: Iterable[FoldEntry], parent: PySide6.QtWidgets.QWidget | None = None)[source]

Bases: PySide6.QtWidgets.QWidget

The row of FoldButton for one host screen.

Build one button per fold.

Parameters:
  • folds – (key, callback) for a button that opens something, or (key, callback, True) for one that switches a module on and off. A switch is handed the new state, so the one callback answers both directions; a plain button is called with no arguments, because “pressed” carries no state worth passing on.

  • parent – the masthead the strip is hung on.

button_for(key: str) → FoldButton | None[source]

The button for key, or None if this strip has no such fold.

Parameters:

key – the app key of the folded module whose button is wanted.

keys() → Sequence[str][source]

The registry keys this strip carries, in the order shown.

spacr.qt.widgets.fold_strip.fold_label(key: str) → Tuple[str, str, str][source]

(name, description, stage) for one folded key, from anywhere.

THE ANSWER FOR A CALLER THAT HAS A KEY AND NEEDS WORDS, whether or not that key’s host is one this module walks. folded_modules() walks eight hosts; the dock draws rows for eleven, because four more grew fold strips later and are named only in spacr.qt.app._EXTRA_FOLD_HOSTS – so the seven modules folded onto Graph Builder, QC Dashboard and Database Browser had no catalogue entry and their rows read as the raw key: lineage, tabulate, control_chart.

Adding those hosts here would fix the rows and import three more screen modules while the window is still being built, which is precisely the startup cost this strip exists to avoid. Asking for the words instead of for the host costs nothing.

Parameters:

key – the folded module’s app key. The registry answers first, then the hosts’ fold fallbacks, then the declared catalogue; an unknown key gets its title-cased name, no description and stage stable.

spacr.qt.widgets.fold_strip.folded_fallback(key: str) → Tuple[str, str, str][source]

(name, description, stage) a folded key kept, or three blanks.

The answer for a module that is a button on some host’s masthead rather than a tile on Home. Blank when the key is not folded anywhere, so a caller can tell “folded, and this is what it said” from “never heard of it” – which the registry cannot, because it answers both the same way.

Parameters:

key – the module’s app key, looked up as a string in folded_modules().

spacr.qt.widgets.fold_strip.folded_modules() → dict[source]

Every folded key, as key -> (name, description, stage, host).

The host is determined by the button list it actually draws, not by the location of fallback text. The latter may be shared: Map Barcodes keeps descriptions for folds owned by Image UMAP, Regression, Mask, and other screens. Treating that shared catalog as membership sends documentation and tutorial links to the wrong screen.

A host’s own fallback record is preferred. If it does not keep one, the other host tables are searched in declaration order. The first host to list a duplicated key still wins, making an accidental double fold deterministic until its invariant test reports the duplication.

AND THEN THE REGISTRY, for a fold that still holds its row. Those keep their name, sentence and maturity where every other module keeps them, so their hosts deliberately record nothing – Regression says so in as many words beside investigate_hit and profiler. Reading only the fallback tables therefore left four folded modules out of this answer entirely, and the dock drew their rows as the raw key: a user looking for Investigate Hit found investigate_hit indented under Regression. A copy of the registry text in each host would fix the rows and be the same sentence written twice, with the copy free to go stale; asking the registry costs nothing and cannot.

Imported lazily, one host at a time and each guarded: this module is imported while a screen is being built, and the hosts import it back.

THE REGISTRY HALF IS ONLY COMPLETE ONCE THE REGISTRY IS. Several rows arrive from spacr.qt.register_self_registering_modules(), so a caller that asks before registration gets the folds whose hosts kept a record and not the ones whose rows have not been added yet. Every caller inside the application asks after the window is built; a test that asks earlier has to register first, and one that does not is testing the order it happened to import in.

Returns:

a fresh dict; callers may keep or mutate it.

spacr.qt.widgets.fold_strip.mark_folded_categories(sections: Iterable[PySide6.QtWidgets.QWidget], categories: dict) → dict[source]

Mark host categories associated with folded modules.

Parameters:
  • sections – Host settings sections.

  • categories – Mapping from module key to category titles. Titles are matched case-insensitively.

Returns:

Mapping from module keys to titles marked successfully.

spacr.qt.widgets.fold_strip.mark_folded_sections(key: str, sections: Iterable[PySide6.QtWidgets.QWidget]) → Tuple[str, ...][source]

Mark settings-section headings with a folded module’s icon.

Parameters:
  • key – Folded module registry key.

  • sections – Section widgets containing that module’s settings.

Returns:

Titles of sections marked successfully. Invalid sections and modules without artwork are skipped.

Nested helpers

_host_declarations._as_string(node)

The string an AST node stands for, or None if it is not one.

Handles a literal, a constant in this module, and a constant reached through an imported sibling – read from the source rather than by importing the module that defines it.

spacr/qt/widgets/fold_strip.py:272