spacr.qt.screens.control_chart

B9 — Control Charts: is the control still the control, plate after plate?

A screening campaign is dozens of plates run over weeks, and the one assumption holding the whole analysis up is that the controls are the same thing every time. Hit calling, plate normalisation and Z’ all measure against them, so when the controls drift, everything downstream is already wrong and nothing says so — the analysis normalises to whatever controls it is handed.

This screen is the picture that says so. One control value per plate along run order, limits estimated from a stated baseline and applied forward, every Nelson rule that fires marked on the plate it fired on and named in words underneath.

Everything numeric comes from spacr.qt.widgets.control_chart, which has no Qt in it and carries the argument for every decision — most of all the one the module turns on, that sigma comes from the average moving range over d2 and never from the standard deviation of the series, because the SD is inflated by exactly the drift the chart exists to detect. This file draws that result and nothing else: no statistic is computed here, so the chart on screen and the chart in a report cannot disagree.

Assembles:

register() is not called at import; read its docstring.

Attributes

Classes

ControlChartCanvas

The chart: zones, limits, points, and the violating plates marked.

ControlChartScreen

A table, the columns that say what a control is, and the chart.

Functions

make_control_chart_screen(→ PySide6.QtWidgets.QWidget)

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

register(→ bool)

Put Control Charts in the app registry. Idempotent.

Module Contents

class spacr.qt.screens.control_chart.ControlChartCanvas(parent=None)[source]

Bases: PySide6.QtWidgets.QWidget

The chart: zones, limits, points, and the violating plates marked.

Draws a ControlChartResult and computes nothing. The zones are filled from the result’s per-point limit arrays rather than from a single pair of numbers, so a campaign whose plates carry different numbers of control wells draws the stepped limits that are actually in force rather than an average that is in force nowhere.

closeEvent(event)[source]
render_now() None[source]

Redraw from the held result. Idempotent.

set_result(result: spacr.qt.widgets.control_chart.ControlChartResult | None, *, message: str = '') None[source]

Draw result; None draws message on an empty axis.

canvas[source]
figure[source]
rendered[source]
property result: spacr.qt.widgets.control_chart.ControlChartResult | None[source]

The result currently drawn, or None.

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

Bases: PySide6.QtWidgets.QWidget

A table, the columns that say what a control is, and the chart.

Parameters:

threadedFalse runs the table read and the chart inline, so a test drives the screen synchronously without the behaviour diverging.

active_jobs() int[source]

How many worker threads are still winding down.

choose_export() None[source]

Ask where to write the per-plate table and write it.

choose_table() None[source]

Ask for a measurement table and load it.

closeEvent(event)[source]
export_points(path: str) str | None[source]

Write one row per plate — value, limits, z, rules fired — as CSV.

is_busy() bool[source]

True while a read or a chart is in flight.

load_path(path: str, table: str | None = None) None[source]

Read a CSV or one table of a measurement database, off the GUI thread.

recompute() None[source]

Rebuild the chart from the form, off the GUI thread.

A second request supersedes the first, so dragging the baseline spinner does not deliver the charts in whatever order the workers finish and leave the picture disagreeing with the form.

set_frame(frame: pandas.DataFrame, *, label: str = '') None[source]

Chart frame. The one call a host needs.

spec() spacr.qt.widgets.control_chart.ControlChartSpec[source]

The spec the form describes.

Raises:

ControlChartError – for a form that cannot mean anything — the same refusals the engine makes, at the same point, with the same messages.

canvas[source]
failed[source]
report[source]
property result: spacr.qt.widgets.control_chart.ControlChartResult | None[source]

The chart currently drawn, or None if the last attempt was refused.

violations[source]
spacr.qt.screens.control_chart.make_control_chart_screen(app_key: str | None = None) PySide6.QtWidgets.QWidget[source]

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

spacr.qt.screens.control_chart.register() bool[source]

Put Control Charts in the app registry. Idempotent.

Called from spacr.qt.SELF_REGISTERING_MODULES, which spacr.qt.run() runs after spacr.qt.app is fully executed and before MainWindow.__init__ reads the registry.

Everything after SECTION_RESULTS is a table this key would otherwise need a hand-edit in: the screen header and blurb, the “no headless run” sentence, the API doc link and the nine translations of the display name. spacr.qt.app.register_app() distributes them from this one call.

Returns:

True if this call is what registered it. Safe to call again: a module imported twice, or a test that re-imports it, must not raise on the duplicate key.

spacr.qt.screens.control_chart.APP_CLI_NOTE = 'Control Charts is a picture you read: the zones, the marked plates and the rule list are the...[source]
spacr.qt.screens.control_chart.APP_DESCRIPTION = 'Track a control plate by plate and see drift before it ruins a screen'[source]
spacr.qt.screens.control_chart.APP_INTRO = "A campaign's controls are supposed to be the same thing every time, and when they stop being...[source]
spacr.qt.screens.control_chart.APP_KEY = 'control_chart'[source]
spacr.qt.screens.control_chart.APP_NAME = 'Control Charts'[source]
spacr.qt.screens.control_chart.APP_NAME_TRANSLATIONS = ('Styrdiagram', 'Regelkarten', 'Gráficos de control', '控制图', 'Cartas de controlo', 'कंट्रोल...[source]
spacr.qt.screens.control_chart.RULE_SETS: Tuple[Tuple[str, Tuple[int, Ellipsis]], Ellipsis][source]