spacr.qt.screens.plate_view¶
Plate Viewer — any measurement as a plate heatmap, with the edge-effect test sitting right next to it.
The heatmap is the delivery mechanism; the statistic beside it is the point. A screen hit in row A or column 24 is far more likely to be an evaporation artefact than biology, and until now spaCR gave nobody a way to notice that before the follow-up experiment failed.
Layout:
┌───────────────────────────────────────────────────────────────────┐
│ /data/plate1/measurements/measurements.db [DB…] [Run folder…] │
│ Table [cell ▾] Value [cell_area ▾] Plate [plate1 ▾] [mean ▾] │
│ Colour [2–98 % ▾] Min objects/well [20] [Render] │
├───────────────────────────────────┬───────────────────────────────┤
│ 1 2 3 4 … 24 │ Plate plate1 — mean cell_area │
│ A ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ │ 384-well, 384 wells tested │
│ B ▓▓ ░░ ░░ ░░ ░░ ░░ ░░ ░░ ░░ ▓▓ │ │
│ C ▓▓ ░░ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ░░ ░░ ▓▓ │ Edge effect: the outer ring │
│ … │ reads +31.2 % vs the interior │
│ P ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ │ (δ = 0.78, p < 1e-4). │
├───────────────────────────────────┴───────────────────────────────┤
│ C07 · 142 objects · mean cell_area = 1204.53 · ring 2 (interior) │
│ scale 812.4 → 1655.9 (viridis, 2–98 %) [Export well grid CSV…]│
└───────────────────────────────────────────────────────────────────┘
Design notes:
Read-only, structurally. Every query goes through
spacr.plate_qc. It opens the file withfile:…?mode=roandPRAGMA query_only = ON, the same approach the Database Browser takes.The statistics live outside the GUI. All of the analysis is in
spacr.plate_qc, which imports neither torch nor cellpose, so it is testable headless and the screen stays a view.An empty well is drawn as empty. Wells with no objects, or fewer than
min_count, are hatched — never coloured as if they measured zero. The count of dropped wells is on screen, because a heatmap quietly missing a third of its wells looks exactly like data.Only the columns needed are read. A spaCR feature table is 500 columns wide; the query pulls the well identifiers plus the one measurement being plotted.
No modal dialogs on any error path. “That column isn’t in this table”, “that file isn’t a database”, “this plate has no interior” — all of it lands in the inline status label. A QMessageBox would hang a headless run.
Classes¶
A plate drawn as coloured wells, with row letters and column numbers. |
|
Plate heatmap + edge-effect QC for a spaCR measurements database. |
Module Contents¶
- class spacr.qt.screens.plate_view.PlateGridWidget(parent=None)[source]¶
Bases:
PySide6.QtWidgets.QWidgetA plate drawn as coloured wells, with row letters and column numbers.
Painted directly rather than embedded as a matplotlib canvas for one reason that matters: a click has to map back to a well, exactly, and a hand-rolled grid gives that for free instead of via a coordinate round-trip through a figure’s data transform.
- Parameters:
parent – parent widget.
- Variables:
well_clicked – emitted with
(row_index, column_index)(1-based) whenever the user clicks inside the grid.
Create an empty plate grid.
- Parameters:
parent – parent widget, or
None.
- cell_rect(row_index: int, column_index: int) PySide6.QtCore.QRectF[source]¶
Return the rectangle a well occupies, in widget coordinates.
Public because it is what makes a click testable: a test can aim at the centre of
cell_rect(3, 7)instead of guessing at pixel arithmetic that would have to be kept in sync by hand.- Parameters:
row_index – 1-based plate row.
column_index – 1-based plate column.
- mousePressEvent(event) None[source]¶
Select the well under the pointer.
- Parameters:
event – the Qt mouse event.
- paintEvent(event) None[source]¶
Draw the plate: one cell per well, coloured by its value.
- Parameters:
event – the Qt paint event.
- select(row_index: int | None, column_index: int | None = None) None[source]¶
Highlight a well (or clear the highlight with
None).- Parameters:
row_index – 1-based plate row, or
Noneto clear.column_index – 1-based plate column;
Nonealso clears.
- set_placeholder(text: str) None[source]¶
Text shown when there is no plate to draw.
- Parameters:
text – the message painted in place of the grid.
- set_plate(layout: pandas.DataFrame | None, vmin: float = 0.0, vmax: float = 1.0, cmap: str = DEFAULT_CMAP, n_rows: int | None = None, n_cols: int | None = None) None[source]¶
Show
layout(aspacr.plate_qc.plate_layout()frame).- Parameters:
layout – tidy well frame, or
Noneto clear.vmin – value mapped to the bottom of the colormap.
vmax – value mapped to the top.
cmap – matplotlib colormap name.
n_rows – nominal plate rows; taken from
layout.attrswhen omitted.n_cols – nominal plate columns; likewise.
- well_at(point) Tuple[int, int] | None[source]¶
Map a widget-coordinate point to a 1-based
(row, column).- Parameters:
point –
QPoint/QPointFin widget coordinates.- Returns:
the well under the point, or
Nonewhen the point is in the margins or off the grid.
- class spacr.qt.screens.plate_view.PlateViewScreen(parent=None, threaded: bool = True)[source]¶
Bases:
spacr.qt.linked_selection.LinkedView,PySide6.QtWidgets.QWidgetPlate heatmap + edge-effect QC for a spaCR measurements database.
Joins the shared population through
LinkedView: narrowing the Local Data Filter anywhere narrows the heatmap too. It subscribes for the filter only — a selection highlights individual objects, and a well is an aggregate of many, so there is nothing here for one to light up.- Parameters:
parent – parent widget.
threaded – run database work on a worker thread (the default). Tests pass
Falsefor deterministic, synchronous behaviour.
- Variables:
last_error – text of the most recent failure,
""when the last operation succeeded. Errors are only ever reported here and in the inline status label — never in a modal dialog.
Build the screen, arm its drop zone and join the shared selection.
- Parameters:
parent – parent widget, or
None.threaded – run reads and aggregations on a worker thread. Set
Falsein tests, which also removes the recompute coalescing timer, so an option change recomputes on the spot.
- closeEvent(event)[source]¶
Stop listening to the process-wide filter before going away.
The
exceptis the one thingLinkedView.unlink_selection()does not do for us: it is flag-guarded, so a double close is already silent, but during interpreter teardown the C++ side of the process-wideLinkedSelectioncan be gone before this widget’scloseEventruns, and PySide raises on the disconnect then.- Parameters:
event – the close event; passed on unchanged to the base class.
- export_csv(out_path: str) bool[source]¶
Write the well grid to
out_pathas CSV.The tidy grid is exported — one row per well with its object count, value, ring index and edge flag — because that is what anybody re-analysing the plate outside spaCR needs.
- Parameters:
out_path – destination file.
- Returns:
True on success; on failure the reason lands in the inline status label and
last_error.
- on_linked_filter_changed(data_filter: spacr.selection.DataFilter) None[source]¶
Re-draw for a new filter, without re-reading the database.
Silent when nothing is loaded: a filter change is not a reason to show an error on a screen the user has not pointed at a database yet.
- Parameters:
data_filter – the filter that was published; not read here, as the redraw reads the link’s current filter itself.
- open_database(path: str) bool[source]¶
Open
pathread-only and list the tables it holds.- Parameters:
path – a
measurements.dbor a run folder containing one.- Returns:
True when the database opened.
- recompute() bool[source]¶
Rebuild the well grid + report from the frame already in memory.
The aggregation runs off the GUI thread.
pqc.plate_layoutis a full-frame groupby anddetect_edge_effecta second pass over the result: 487 ms on a real measurement table, measured, and it fired on every tick of the min-objects spin box. Dragging that box was a sequence of half-second freezes.THE CONTRACT.
recompute()returns a bool that a caller reads, and it now means “a plate was drawn or is being drawn” rather than “a plate was drawn”. The refusals – no frame yet, and a failed aggregation – are unchanged and still synchronous, because the first does no work and the second is reported by the completion handler in exactly the place it was reported before. So the only caller-visible difference is thatTruemay arrive before the grid is painted.That was made safe rather than assumed safe:
threaded=False(which every test intest_plate_view.pyandtest_plate_view_linked_filter.pyconstructs the screen with) runs the job inline through_run_job(), so those callers still get the old meaning exactly. A caller that needs to know the grid is up waits onactive_jobs(), asrender_plate’s callers already do –render_platehas returned “started, not finished” since the database read was threaded, and this is the same promise.- Returns:
True when a plate was drawn, or a draw was started.
- render_plate() bool[source]¶
Read the chosen measurement and draw the plate.
The database is only touched when the table or measurement changed; otherwise this is the same recompute the option controls trigger.
- Returns:
True when the render was started/completed.
- select_well(row_index: int, column_index: int) str[source]¶
Report what is behind a well, and return the text shown.
- Parameters:
row_index – 1-based plate row.
column_index – 1-based plate column.
- Returns:
the readout line.
- set_table(table: str) None[source]¶
Select
tableand reload its numeric columns.- Parameters:
table – name of a table in the table box, converted with
str.
- set_value_column(column: str) None[source]¶
Select
columnas the measurement to draw.A name that is not in the current table is accepted and selected anyway — it is exactly what happens when a user picks a column and then switches to a table that does not have it. Rendering then fails with an inline explanation rather than the combo silently snapping back to something the user did not choose.
- Parameters:
column – the measurement column name, converted with
str.
Nested helpers¶
- PlateViewScreen._on_table_changed._done(columns: List[str]) None¶
Offer the columns, guarded so refilling does not re-trigger this.
spacr/qt/screens/plate_view.py:1038
- PlateViewScreen._on_table_changed._job()¶
Read the table’s numeric columns. Off the GUI thread.
spacr/qt/screens/plate_view.py:1034
- PlateViewScreen._run_job._job(payload: Dict[str, Any]) None¶
Call the wrapped function, stashing its result in the payload.
The payload is how a value crosses back from the worker: a return would be swallowed by the runner.
spacr/qt/screens/plate_view.py:1378
- PlateViewScreen.recompute._job()¶
Recompute the plate summary. Off the GUI thread.
spacr/qt/screens/plate_view.py:1185
- PlateViewScreen.render_plate._done(frame: pd.DataFrame) None¶
Keep the frame and draw it, remembering what it was keyed on.
spacr/qt/screens/plate_view.py:1116
- PlateViewScreen.render_plate._job()¶
Load the plate frame. Off the GUI thread.
spacr/qt/screens/plate_view.py:1112