spacr.qt.screens.power

Workflow inputs and outputs

Power / Design

Estimate sampling needs from chosen effect sizes and variability; pilot measurements are optional evidence for assumptions.

Open: Home → Power / Design.

Inputs and outputs below include conditional alternatives. The guidance and handoff notes say which route applies.

Inputs

  • Screen design and power estimates — Saved planning tables or figures based on explicitly chosen effect sizes, variability and sampling assumptions.

  • 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.

  • Experimental layout — Exported plate/condition/control/replicate map. Keep its plate and well identifiers consistent with the acquired data.

Outputs

  • Screen design and power estimates — Saved planning tables or figures based on explicitly chosen effect sizes, variability and sampling assumptions.

Before this module

  • Experiment Design: Use the experimental layout to define sampling assumptions, then revise the design.

API reference.

Module tutorial.

Power / Design — how many cells per well, and how many wells, do I need?

The science has been in the tree since the spaCRPower port landed: spacr.power_simulate builds a screen you know the truth for, and spacr.power_model fits the horseshoe-Poisson model to it and scores how well the fit recovered the hits you planted. What has never existed is the surface where a screener asks the question they actually have, which is not “what is the AUROC of a design with well_abundance_factor_mu = 4.6” but:

I have 452 genes and four 384-well plates. My classifier is about
0.80 / 0.12. How many cells per well do I have to image before this
screen finds its hits, and would more wells be cheaper than more cells?

Layout:

┌────────────────────────┬───────────────────────────────────────────────┐
│ Library                │  At 123 cells per well and 4.6 constructs per │
│  genes           [452] │  well, 1536 wells detect a 6.7-fold effect in │
│  gRNAs/gene      [4]   │  67% of simulations — 2 of 3 replicates …     │
│  score per    [gene ▾] │                                               │
│  constructs/well [4.6] │  ! spaCRPower splits a well's cells evenly …  │
│ Plate                  │  ! a replicate whose fit failed counts as …   │
│  wells/plate  [384 ▾]  │  ! mean-field ADVI, not NUTS: the ranking …   │
│  plates          [4]   ├───────────────────────────────────────────────┤
│   → 1536 wells         │  detection probability vs cells per well      │
│ Effect                 │   1 ┤        ╭──●───────                      │
│  background     [0.12] │     │   ╭────╯                                │
│  effect (fold)  [6.67] │   0 ┼───╯                                     │
│   → 0.800 positive     │      15   31   62  123  246                   │
│  prevalence    [0.025] ├───────────────────────────────────────────────┤
│ …                      │  cells  power  mean AUROC  mean AP  n         │
└────────────────────────┴───────────────────────────────────────────────┘

Design notes:

  • The screen computes no statistics of its own. Every number on it comes out of spacr.power_model.scan_parameters(); the only arithmetic here is counting how many replicates cleared the user’s threshold. The translation from “genes, plates, fold-change” to simulator keyword arguments lives in one function, spacr.qt.widgets.power_design.simulator_kwargs(), so a run started here and the same run typed at a Python prompt are the same call. The test suite asserts that byte for byte on a fixed seed.

  • The caveats are next to the number, not in a docstring. The port departs from spaCRPower in ways that change the answer — most importantly that R’s even cell-split overstates power — and a power analysis whose caveats are one import away is a power analysis that gets quoted without them. The ones that move the number are rendered beside the sentence; the rest are one click below it.

  • Off the GUI thread, and the thread actually retires. A full sweep is minutes. It goes through spacr.qt.bridge.make_thread(), and every thread.finished slot is a BOUND METHOD — see PowerScreen._retire_finished_jobs() for what a closure does here and why it is not a style preference.

  • No modal dialogs. Every failure lands in the inline status line.

  • threaded=False runs the same code inline, firing the same signals, so the tested path and the shipped path differ only in where they run.

Classes

CaveatPanel

The port's departures from spaCRPower, rendered where they are read.

PowerCurveView

Detection probability against one swept axis, painted directly.

PowerScreen

The Power / Design app.

Functions

make_power_screen(→ PySide6.QtWidgets.QWidget)

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

power_default_settings(→ Dict[str, Any])

The app's default settings dict, in spacr.settings shape.

register(→ bool)

Put Power / Design in the app registry, through the public seam.

register_settings(→ bool)

Register this app's defaults through spacr.settings.register_defaults().

run_power_sweep(→ Dict[str, Any])

Run both sweeps for payload['spec'] and put the result in payload.

spec_from_settings(...)

Build a DesignSpec from a settings dict.

Module Contents

class spacr.qt.screens.power.CaveatPanel(parent: PySide6.QtWidgets.QWidget | None = None)[source]

Bases: PySide6.QtWidgets.QWidget

The port’s departures from spaCRPower, rendered where they are read.

The ones flagged changes_the_number are always visible; the rest are behind a “show all” toggle. That split is the panel’s whole job: a list where the COM-Poisson third moment sits at the same weight as “the R version overstates power” is a list that gets skimmed, and the one line that would have changed somebody’s plate count goes with it.

Parameters:

parent – parent widget.

Build the caveat list, with the harmless ones folded away.

Caveats that change the reported power are always visible; the rest – departures from spaCRPower that only affect how these numbers compare with the R package’s – sit behind a toggle.

Parameters:

parent – parent widget, or None.

all_text() → str[source]

Every caveat line the panel holds, shown or not.

visible_text() → str[source]

Every caveat line currently on screen, joined. For tests.

class spacr.qt.screens.power.PowerCurveView(title: str = '', parent: PySide6.QtWidgets.QWidget | None = None)[source]

Bases: PySide6.QtWidgets.QWidget

Detection probability against one swept axis, painted directly.

QPainter rather than a matplotlib canvas: the plot is two axes, five points and a threshold line, and a FigureCanvas here would import matplotlib’s Qt backend, own a timer and need the deleted-C++-object care spacr.qt.widgets.umap_explorer documents — for a drawing that is thirty lines of drawLine.

describe() returns exactly what is drawn, as text, so a test can assert the content of the plot without reading pixels.

Parameters:
  • title – the caption drawn above the curve. Empty draws none.

  • parent – parent widget.

Create an empty curve view.

Parameters:
  • title – caption drawn above the curve.

  • parent – parent widget, or None.

describe() → str[source]

The plotted values as one line of text, for tests and for export.

is_empty() → bool[source]

Whether there is anything to draw.

paintEvent(event)[source]

Draw the power curve.

Parameters:

event – the Qt paint event.

set_curve(curve, x_label: str, marker: float | None = None, threshold: float = 0.8) → None[source]

Show curve, a frame from power_design.power_curve().

Parameters:
  • curve – the curve, or None to clear.

  • x_label – what the x axis counts.

  • marker – x value of the user’s own design, drawn as a rule so the point they came for is findable on their own plot.

  • threshold – the detection AUROC, for the caption only.

class spacr.qt.screens.power.PowerScreen(threaded: bool = True, parent: PySide6.QtWidgets.QWidget | None = None)[source]

Bases: PySide6.QtWidgets.QWidget

The Power / Design app.

Parameters:
  • threaded – run sweeps on a make_thread() worker (the shipped behaviour). False runs the identical code inline and emits the identical signals, which is what the tests use to get a deterministic result without pumping an event loop.

  • parent – Qt parent.

Build the screen with the design form beside the curves and table.

Parameters:
  • threaded – run the sweep on a worker thread. Set False in tests so run finishes before it returns.

  • parent – parent widget, or None.

active_jobs() → int[source]

How many sweep threads are still winding down.

answer_text() → str[source]

The headline sentence currently on screen.

cancel() → None[source]

Ask the running sweep to stop after the fit in flight.

The fit itself is atomic — there is no safe point inside an ADVI optimisation to abandon — so this sets the event the sweep’s on_point hook checks between grid points, and asks the worker to cancel as well so the run registry and Home see it too.

caveat_text() → str[source]

Every caveat line on screen, shown or hidden.

closeEvent(event)[source]

Let every in-flight sweep stop before the widget dies.

Parameters:

event – the close event; it is passed on to the base class after the sweep is cancelled and running threads are waited on for up to 10 s each.

is_busy() → bool[source]

Whether a sweep is in flight.

result() → Dict[str, Any] | None[source]

The last sweep’s result dict, or None.

run() → bool[source]

Start both sweeps. Returns whether one was started.

Returns:

True if a sweep started (or, when threaded=False, ran to completion successfully).

set_spec(spec: spacr.qt.widgets.power_design.DesignSpec) → None[source]

Put spec on the form. For tests and for reloading a run.

The fields the form does not show are carried in _held, so a spec round-trips: screen.set_spec(s); screen.spec() == s. Dropping them would quietly re-simulate a different screen from the one asked for, with no visible difference on the form.

Parameters:

spec – the design to load; its shown fields fill the form and the fields the form does not show are kept for spec() to return.

spec() → spacr.qt.widgets.power_design.DesignSpec[source]

The design currently on the form.

Returns:

a DesignSpec. This is the only thing the sweep is given, which is what makes an exported run re-runnable from the record.

status_is_error() → bool[source]

Whether the status line is currently showing a failure. For tests.

status_text() → str[source]

The inline status line.

table_rows() → List[List[str]][source]

Every table cell as plain strings. For tests.

visible_caveat_text() → str[source]

Only the caveat lines the user can see without clicking.

spacr.qt.screens.power.make_power_screen(app_key: str | None = None) → PySide6.QtWidgets.QWidget[source]

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

spacr.qt.screens.power.power_default_settings(settings: Dict[str, Any] | None = None) → Dict[str, Any][source]

The app’s default settings dict, in spacr.settings shape.

Parameters:

settings – existing settings to fill in; a fresh dict if omitted.

Returns:

settings, with every missing power_ key defaulted.

spacr.qt.screens.power.register() → bool[source]

Put Power / Design in the app registry, through the public seam.

It claims spacr.qt.app.SECTION_DESIGN, which is declared in SECTION_ORDER and has never had an app — its note already reads “Plan the experiment before it runs: power, sample size, plate layout, controls and replicates”, so registering makes that tab appear with the description it was written for.

Returns:

True if this call is what registered it. Safe to call twice — a module imported from two paths must not raise.

Called from app.py’s _SELF_REGISTERING_APPS table, at the bottom of that module, which is the one point where a registration is visible to everybody and happens on import spacr.qt.app rather than only at launch. This module does not call it at its own import, so merely reading the screen’s code does not add a row.

GUI-only, deliberately. It passes cli_note and no entry, so spacr-run power answers with APP_CLI_NOTE instead of “unknown module”. The reason is not that the sweep cannot run headless — it can, and the note names the call — but that its inputs are not a settings file. Every other spacr-run module takes a src and processes it; this one takes a design, and its output is a curve you read by comparing points on it. A settings.csv that pinned one point of that curve would be a worse interface to spacr.power_model.scan_parameters() than calling it, which is exactly what the note tells the user to do.

spacr.qt.screens.power.register_settings(replace: bool = False) → bool[source]

Register this app’s defaults through spacr.settings.register_defaults().

Separate from register(), and called at the bottom of this module: register_app(defaults_module=...) defers importing the screen until its settings are requested, and that import must populate the same process-wide expected_types, tooltips and categories tables regardless of which screen or test happened to import it first.

Parameters:

replace – overwrite an existing registration for this key.

Returns:

True if this call registered it, False if it was already there.

spacr.qt.screens.power.run_power_sweep(payload: Dict[str, Any]) → Dict[str, Any][source]

Run both sweeps for payload['spec'] and put the result in payload.

Shaped as fn(settings) because that is what spacr.qt.bridge.make_thread() calls. Everything it needs is in the dict and everything it produces goes back into the same dict, so the GUI thread reads the result from an object it already owns rather than from a signal payload that would have to cross the thread boundary.

The well-count and cell-count axes are scanned separately. This measures each marginal response without evaluating the Cartesian product of both grids.

Parameters:

payload –

mutable job dict with keys

spec

the DesignSpec.

cancel

optional threading.Event; set it and the sweep stops after the fit in flight, keeping the rows it already has.

progress

optional fn(done, total, label) called on the worker thread.

fit_kwargs

optional extra keyword arguments for the fit, for tests that need a sweep to finish in seconds.

Returns:

the result dict, which is also stored at payload['result']: cells_scan, wells_scan (raw scan_parameters() frames), cells_curve, wells_curve (from power_curve()), cancelled, n_clipped_screens and clip_message.

spacr.qt.screens.power.spec_from_settings(settings: Dict[str, Any]) → spacr.qt.widgets.power_design.DesignSpec[source]

Build a DesignSpec from a settings dict.

The bridge between the generic settings machinery and this screen, so a design saved as settings and a design typed into the form are the same object by the time either reaches the simulator.

Parameters:

settings – any mapping; missing keys take their defaults.

Returns:

the design.

Nested helpers

PowerCurveView.paintEvent.to_px(x: float, y: float) → Tuple[float, float]

Data coordinates to pixels, clamping the vertical to the axis.

spacr/qt/screens/power.py:490

run_power_sweep._make_hook(label: str)

A scan_parameters on_point that reports progress and cancels.

Returns False — and only False — when the cancel event is set, which is the contract scan_parameters documents for stopping a sweep between points.

spacr/qt/screens/power.py:328

run_power_sweep._make_hook._hook(point: Dict[str, Any]) → Any

Report one completed point back to the progress bar.

spacr/qt/screens/power.py:335