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.
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 everythread.finishedslot is a BOUND METHOD — seePowerScreen._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=Falseruns the same code inline, firing the same signals, so the tested path and the shipped path differ only in where they run.
Classes¶
The port's departures from spaCRPower, rendered where they are read. |
|
Detection probability against one swept axis, painted directly. |
|
The Power / Design app. |
Functions¶
|
Factory handed to |
|
The app's default settings dict, in |
|
Put Power / Design in the app registry, through the public seam. |
|
Register this app's defaults through |
|
Run both sweeps for |
|
Build a |
Module Contents¶
- class spacr.qt.screens.power.CaveatPanel(parent: PySide6.QtWidgets.QWidget | None = None)[source]¶
Bases:
PySide6.QtWidgets.QWidgetThe port’s departures from spaCRPower, rendered where they are read.
The ones flagged
changes_the_numberare 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.
- class spacr.qt.screens.power.PowerCurveView(title: str = '', parent: PySide6.QtWidgets.QWidget | None = None)[source]¶
Bases:
PySide6.QtWidgets.QWidgetDetection 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_explorerdocuments — for a drawing that is thirty lines ofdrawLine.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.
- set_curve(curve, x_label: str, marker: float | None = None, threshold: float = 0.8) None[source]¶
Show
curve, a frame frompower_design.power_curve().- Parameters:
curve – the curve, or
Noneto 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.QWidgetThe Power / Design app.
- Parameters:
threaded – run sweeps on a
make_thread()worker (the shipped behaviour).Falseruns 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
Falsein tests sorunfinishes before it returns.parent – parent widget, or
None.
- 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_pointhook checks between grid points, and asks the worker to cancel as well so the run registry and Home see it too.
- 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.
- run() bool[source]¶
Start both sweeps. Returns whether one was started.
- Returns:
Trueif a sweep started (or, whenthreaded=False, ran to completion successfully).
- set_spec(spec: spacr.qt.widgets.power_design.DesignSpec) None[source]¶
Put
specon 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.
- 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.settingsshape.- Parameters:
settings – existing settings to fill in; a fresh dict if omitted.
- Returns:
settings, with every missingpower_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 inSECTION_ORDERand 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:
Trueif 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_APPStable, at the bottom of that module, which is the one point where a registration is visible to everybody and happens onimport spacr.qt.apprather 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_noteand noentry, sospacr-run poweranswers withAPP_CLI_NOTEinstead 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 otherspacr-runmodule takes asrcand 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 tospacr.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-wideexpected_types,tooltipsandcategoriestables regardless of which screen or test happened to import it first.- Parameters:
replace – overwrite an existing registration for this key.
- Returns:
Trueif this call registered it,Falseif 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 inpayload.Shaped as
fn(settings)because that is whatspacr.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
specthe
DesignSpec.canceloptional
threading.Event; set it and the sweep stops after the fit in flight, keeping the rows it already has.progressoptional
fn(done, total, label)called on the worker thread.fit_kwargsoptional 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(rawscan_parameters()frames),cells_curve,wells_curve(frompower_curve()),cancelled,n_clipped_screensandclip_message.
- spacr.qt.screens.power.spec_from_settings(settings: Dict[str, Any]) spacr.qt.widgets.power_design.DesignSpec[source]¶
Build a
DesignSpecfrom 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