spacr.qt.screens.dose_response

Workflow inputs and outputs

Dose–Response

Supply dose and response columns, controls and units before curve fitting; this route does not require sequencing.

Open: Home → Dose–Response.

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.

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

Outputs

  • Assay results — Assay-specific result tables and figures in the configured destination, preserving well and condition identities.

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

Before this module

  • Experiment Design: Acquire and measure the experiment first, preserving dose/condition identities.

  • Measure: Join the measured response to explicit doses and controls.

API reference.

Module tutorial.

Dose–Response — a concentration series, a 4PL curve, and an honest EC50.

The statistics are all in spacr.qt.widgets.dose_response and there is none in here. This module is the surface: pick the concentration column, the response column and (optionally) a grouping column, and get one curve per gene or compound with its EC50 and confidence interval, drawn on the log axis a dilution series is read on.

Three decisions shape the screen, and all three follow from the engine’s central claim — that the useful answer is sometimes “this experiment does not locate the EC50”, and a screen that cannot render that sentence would undo the module underneath it.

Refusals and one-sided bounds are rows, not silence. A plate where three compounds fit and the fourth is cytotoxic at the top dose has four rows. The cytotoxic one says refused and carries the engine’s message; a compound whose midpoint sits past the highest dose says unbounded and carries EC50 > 30 µM, with an empty EC50 cell. Dropping either from the table would turn “we checked and the answer is no” into “no data”, which is the one reading the numbers cannot survive.

Every number on this screen comes out of the engine. The table is table(), the text is report(), and the plotted line is curve(). Nothing is recomputed here, so the figure, the exported CSV and the sentence a user pastes into a methods section cannot drift apart.

The Curve picker offers the asymmetric model and does not choose it. Four-parameter is what Fit uses until someone says otherwise, and a five-parameter fit says in its own report whether the fifth parameter earned itself. A screen that picked the better-fitting model per group would hand back a plate whose EC50s came from two different models, compared as though they had not.

The fit runs off the GUI thread through spacr.qt.job_runner.JobRunner, like every other read and compute in the Qt layer. A profile-likelihood interval on a 96-compound plate is seconds, not milliseconds, and threaded=False runs the identical code inline so a test drives the same path the shipped screen does.

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

Classes

DoseResponseScreen

Load a concentration series, fit a 4PL per group, and read the EC50s.

Functions

make_dose_response_screen(→ PySide6.QtWidgets.QWidget)

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

register(→ bool)

Put Dose–Response in the app registry. Idempotent.

Module Contents

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

Bases: PySide6.QtWidgets.QWidget

Load a concentration series, fit a 4PL per group, and read the EC50s.

Parameters:
  • parent – the usual Qt parent.

  • threaded – False runs the table read and the fit inline, emitting the same signals in the same order, so a test drives the screen synchronously without the behaviour diverging.

Build the screen: the curve canvas beside the fit table and report.

Parameters:
  • parent – parent widget, or None.

  • threaded – read and fit on a worker thread. Set False in tests so fit finishes before it returns.

active_jobs() → int[source]

Worker threads still winding down.

choose_table() → None[source]

Ask for a file and load it.

closeEvent(event)[source]

Stop background work and unlink before going away.

Parameters:

event – the Qt close event.

fit() → None[source]

Fit every group, off the GUI thread.

is_busy() → bool[source]

Whether a read or a fit is in flight.

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

Load a CSV or one table of a SQLite measurement database.

The read runs on a worker thread through JobRunner; listing the table names stays inline because the picker has to be populated before the read is dispatched, to know which table to read. The same shape spacr.qt.screens.trellis.TrellisScreen uses, for the same reasons.

Parameters:
  • path – a CSV, TSV or TXT file (by extension), read as one table; any other path is opened as a SQLite measurement database and its tables are listed in the picker.

  • table – the database table to read, also selected in the picker when the database has it; None reads the picker’s current table.

result_set() → spacr.qt.widgets.dose_response.DoseResponseSet | None[source]

The last fit, or None. What a test and an exporter both read.

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

Offer frame’s columns and wait to be told which ones to fit.

The one call a host needs. It deliberately does not fit: which column is the dose is not guessable from a measurement table, and a curve through the wrong pair of columns is worse than an empty axis.

Parameters:
  • frame – the measurement table; its columns fill the concentration, response, group, plate, control, host and second-dose pickers.

  • label – the source line to show; empty shows the row and column counts.

show_group(index: int) → None[source]

Draw and describe the index-th curve of the last fit.

Parameters:

index – position of the curve in the last fit’s groups; out of range, or with no fit yet, nothing happens.

spec() → spacr.qt.widgets.dose_response.DoseResponseSpec[source]

The spec the controls currently describe.

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

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

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

Put Dose–Response 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 — the position the docstring there explains. Not called at import, so importing this module to reach DoseResponseScreen from a test or a notebook does not mutate process-wide state.

The row itself – the key, the name, the blurb, the section, the “no headless run” sentence, the API doc link and the nine translations of the display name – is declared in spacr.qt.app_catalog. spacr.qt.app.register_app() distributes those into the four tables each used to need a hand-edit in, and this function’s whole job is to name which row. That is what lets the app be registered without importing this module at all: the launch reads the table, and the screen is imported when somebody opens it.

SECTION_DESIGN, which is not the obvious answer and is the right one. Design is “everything that happens before the microscope: power, sample size, plate layout, controls and replicates”, and it already holds Power / Design. A dose–response series is the other pre-experiment calculation a screening lab runs: nobody fits an EC50 to admire it, they fit it to pick the concentration the actual screen will use, exactly as they run a power calculation to pick n. The output of this screen is an input to the next experiment, which is what the section means.

The alternative reading — Explore, “ask the numbers a question you did not plan for” — is the weaker one because a concentration series is planned: the doses were chosen in advance and the curve is the thing the experiment was for. The cap made the choice concrete: MAX_APPS_PER_SECTION is 13, Explore stood at 12 before this batch, and Outliers — which really is an open question asked of a finished table — is the one that belongs there.

Returns:

True if this call is what registered it.