spacr.qt.widgets.measurement_scan_panel¶
Compare gene effect sizes across measured features and attached databases.
This panel renders spacr.measurement_scan beside regression runs. Model
settings remain fixed while the dependent measurement changes. Each row shows
both the within-measurement q-value and the correction across all scanned
measurements; verdicts use the across-scan value, while ranking uses effect
size. In the recorded permuted-label check, within-measurement correction
flagged 83.5% of scans and across-scan correction flagged 5.0%.
DatabaseMergePanel also exposes the measurement databases attached to
each regression plate. spacr.multi_database prevents pooling colliding
plate identities, and spacr.merge_tables chooses aggregation and join
behavior per table from measurement type and object cardinality. The panel
reports the selected sources, dropped columns, aggregation policy, collisions,
and other merge consequences rather than applying one global join rule.
Exceptions¶
The user stopped the queue between fits. |
Classes¶
One plate of the regression input table, seen from this tab. |
|
What one fit of the queue did. |
|
Run one regression per selected column of a merged measurement table. |
|
The databases attached to the input table, and the join offered. |
|
The scan's result table, and the two numbers behind every row. |
|
One numbered step of the Measurements workflow: a fold and a body. |
|
The numbered-step half of a Measurements panel, shared by both of them. |
Functions¶
|
The subset of |
|
The input table's rows as |
|
The settings for one fit of the queue: this column, this score file. |
|
The columns no |
|
Whether two frames' wells meet, and one example from each side if not. |
|
Plate ids as the plates are called, in their given order. |
|
The object tables every one of these databases has, in table order. |
|
Every chosen table of every chosen database, on one anchor. |
|
The lists behind |
|
The whole statement: |
|
What the merge cost, as counts. This is what fits in the box. |
|
|
|
Describe noncanonical plate identifiers that remain in a merge plan. |
|
The columns of a merged frame a regression could take as its response. |
|
Give |
|
Fit response columns sequentially while isolating per-column failures. |
|
Create a numbered heading for one database-merge workflow step. |
|
One phrase per measurement, from both corrections. |
|
|
|
Stage the merged frame: offer it in memory, write the durable copy. |
Module Contents¶
- exception spacr.qt.widgets.measurement_scan_panel.QueueCancelled[source]¶
Bases:
ExceptionThe user stopped the queue between fits.
Not an error and not a refusal, for the same reason
spacr.multi_database.MergeCancelledis neither: a cancel is not reported as a fit that failed.Initialize self. See help(type(self)) for accurate signature.
- class spacr.qt.widgets.measurement_scan_panel.AttachedDatabase[source]¶
One plate of the regression input table, seen from this tab.
- Parameters:
plate – the plate the input-table row names.
path – its measurements database, or
"". A plate with no database is legal – the regression runs on the score and count CSVs, and the database is only what makes this tab possible for that plate – so an empty path is listed and disabled rather than refused.screen – the screen the plate belongs to, when the project has more than one. Carried through to
spacr.multi_database.read_merged()asscreens=, which is what keeps two screens sharingplate1apart as two identities instead of one collision.
- property label: str[source]¶
A readable name for the file, disambiguated by its folder.
Every plate’s database is usually called
measurements.db, so the stem alone names all of them the same thing – the reasonspacr.multi_database.describe_merge()folds the parent directory into its own labels.
- property present: bool[source]¶
Whether the attached database is on disk right now.
Checked here rather than at run time: the design asks that a row whose database has gone missing says so before the run, not four minutes into it.
Asked of
spacr.qt.path_proberather than ofos.path. This property is read once per plate row by_fill_table, and again bypaths,screensanddescribe– so a project with eight plates on a sleepingautofsmount was eight twenty-second stats on the GUI thread before the tab had drawn anything. The probe answers from a cache, reports a path it has not seen as present, and stats it on its own bounded worker;DatabaseMergePanel._follow_path_probesis the half that corrects the row when the real answer lands.
- class spacr.qt.widgets.measurement_scan_panel.ColumnFit[source]¶
What one fit of the queue did.
- Parameters:
column – the response it was asked to fit.
ok – whether it produced results.
folder – where it wrote, when it wrote anywhere.
error – why it did not, in the words the fit used.
n_results – how many coefficients came back.
- class spacr.qt.widgets.measurement_scan_panel.ColumnRegressionPanel(frame_provider=None, settings_provider=None, parent=None, *, score_provider=None, threaded: bool = True, fit=None)[source]¶
Bases:
WorkflowSteps,PySide6.QtWidgets.QWidgetRun one regression per selected column of a merged measurement table.
Each column produces an independent run folder and Runs-tab entry. Jobs execute sequentially through
JobRunner, can be stopped between fits, and continue after individual fit failures.- Parameters:
frame_provider (callable or None, optional) – Zero-argument callable returning the merged measurement frame.
settings_provider (callable or None, optional) – Zero-argument callable returning the regression screen’s current model, correction, and count settings. Each queued fit copies these settings and changes only the response column.
parent (QWidget or None, optional) – Parent widget.
score_provider (callable or None, optional) – Zero-argument callable returning the path to the saved merged frame. Every queued fit reads this same file.
threaded (bool, default=True) – Run fits through the background job runner. Set to
Falsefor synchronous tests or headless callers.fit (callable or None, optional) – Function called with one fit’s settings.
Noneuses the standard regression implementation. Supplying a callable keeps the queue independently testable and avoids importing the full statistics stack while the first window is constructed.
- Initialize the sequential column-regression queue.
- cancel(*_args) bool[source]¶
Stop the queue after the fit that is running.
- Returns:
whether there was one to stop.
Not mid-fit: a regression stopped half-way has written part of a results folder, and there is no way to say what that folder means. The fits that finished are complete runs and stay in the Runs tab.
- closeEvent(event)[source]¶
Do not let a queue outlive the widget it reports to.
- Parameters:
event – the close event; it is passed on to the base class after the worker queue is stopped.
- refresh() int[source]¶
Re-read the merged frame and offer its columns.
- Returns:
how many columns can be regressed on.
The selection survives a refresh for every column that still exists. Re-merging with one more database must not silently empty a queue the user has just built.
- selected_columns() Tuple[str, ...][source]¶
The columns the user picked, in the list’s order.
The list’s order and not the click order, so the queue is read the same way the picker is – and two users who picked the same three columns get the same three runs in the same order.
- class spacr.qt.widgets.measurement_scan_panel.DatabaseMergePanel(database_provider=None, parent=None, *, threaded: bool = True, destination_provider=None)[source]¶
Bases:
WorkflowSteps,PySide6.QtWidgets.QWidgetThe databases attached to the input table, and the join offered.
One row per plate of the regression input table, whether or not it has a database – a plate with none is listed and disabled here, because it still runs in the regression and the user needs to see why it is absent from this tab.
There is no join-type control: the join follows object cardinality per table through
spacr.merge_tables.MergePolicy.how_for(), and a blankethowis the finding the design raised. The two checkboxes here are the two settings that policy actually reads.The merge runs off the GUI thread. Run inside the click handler, four databases, 226,467 cell rows and three joined tables kept Qt from painting, showing a spinner or accepting a cancel until the merge returned.
JobRunnerruns it, as it runs every other long job here.- Variables:
databases_changed – emitted with the number of readable databases whenever the list is re-read.
merged – emitted with the merged frame.
merge_progress – emitted with
(stage, rows done, rows total)as the merge moves. Always on the GUI thread – see_relay_progress().merge_finished – emitted with the frame when a merge completes, or
Nonewhen it was refused, failed or cancelled.
- Parameters:
database_provider – called with no arguments for the input table’s rows. A callable rather than a stored list, for the same reason
frame_provideris one: the tab must not go on showing the previous run’s inputs.threaded – whether
start_merge()runs off the GUI thread.Falseruns it inline through the same code path, emitting the same signals in the same order, so a test can drive the button synchronously without the behaviour diverging.destination_provider – called with no arguments for the folder the merged frame is written into. Without one the merge still happens and simply leaves no artefact – the panel is used headless and in tests where there is nowhere to write.
parent – parent widget; ownership only.
- cancel_merge(*_args) bool[source]¶
Stop a running merge. Nothing half-written survives it.
- Returns:
whether there was one to stop.
- closeEvent(event)[source]¶
Do not let a worker outlive the widget it reports to.
- Parameters:
event – the close event; it is passed on to the base class after the workers and path probes are stopped.
- describe() str[source]¶
State what the merge would do, before it is done.
Called on every click, so it cannot read the databases each time. What it needs is sqlite metadata and the distinct plate ids, a millisecond on a local disk, but the same read on a share that is asleep may not return at all. So it draws from what
refreshasked for, waits at mostREAD_BUDGET_Sfor anything not back yet, and says “reading…” for the rest until_on_read_landed()corrects it.The counts go in the box and the names behind the disclosure, so the summary is not buried under a long list of column names.
- Returns:
the whole statement, summary and evidence, as one string – what the panel says, wherever it puts it.
- merge(**kwargs)[source]¶
Merge the chosen tables synchronously and report what it cost.
This blocks the calling thread until the whole join is done. On four databases that is minutes, so the GUI must not call it: the Merge button goes through
start_merge(), which runs this same work on aJobRunner. It is kept as the synchronous entry point for a headless caller and for a test that wants the frame back on the next line.- Returns:
the merged frame, or
Nonewhen nothing was merged. A refusal is shown in full rather than summarised, including what to do about it.
- merged_frame_path() str[source]¶
Where the merged frame was written, or
"".The artefact the design asks for: written once when the merge finishes, named, and read by every fit in the column queue rather than the merge being redone per fit.
- plan_evidence() str[source]¶
The column names behind
plan_summary()’s counts.
- plan_text() str[source]¶
The whole pre-merge statement: the summary and then its evidence.
Kept whole for a caller that wants everything. What the panel shows is
plan_summary()in the box andplan_evidence()behind the disclosure.
- policy() spacr.merge_tables.MergePolicy[source]¶
The merge policy the controls describe.
Note what is not here: a join type.
how_forderives it per table from cardinality, and these two checkboxes are the only settings that change it.
- refresh() int[source]¶
Re-read the provider and describe what is attached.
A re-read, so the previous paint’s answers are not reused: a measure run may have rewritten a database at a path this panel already knows, and
_prepare_mergerefreshes precisely to catch that. It is bounded rather than blocking – see_read_off_thread()– so a database that has not answered within the budget is drawn as it was, or as “reading…”, and corrected when it does.- Returns:
the number of readable databases.
- screens() Dict[str, str] | None[source]¶
{path: screen}for the rows that named one, elseNone.Nonerather than a dict of defaults: naming a screen for every database says the user is working in screens, andscreens_were_namedreads that to decide how a refusal is worded.
- set_anchor(name: str) None[source]¶
Choose the anchor, if it is on offer.
- Parameters:
name – name of the table to anchor on, set as the anchor box’s current text; a name not offered there leaves the choice unchanged.
- set_database_provider(provider) None[source]¶
Take a new source of input-table rows and re-read it.
- Parameters:
provider – zero-argument callable returning the input table’s rows (see
attached_databases()for the accepted shapes); it is called on every refresh, starting immediately.
- set_selected_tables(names: Sequence[str]) None[source]¶
Tick exactly
names.- Parameters:
names – table names to tick, compared as strings; every other listed table is unticked.
- show_aggregation_rules() None[source]¶
The per-column rules, for the columns actually about to be merged.
Reuses the Gate Editor’s dialog rather than growing a second one: the rules are the same rules, so one editor keeps them consistent.
The preview read is off the GUI thread. Everything else here reads sqlite metadata; this reads rows –
read_mergedopens every attached database and pullsPREVIEW_ROWSrows out of each – which on a sleepingautofsmount would freeze a click handler.So the click either opens the dialog straight away or says the button is reading and opens it from
_on_read_landed()when the databases answer.
- start_merge(*_args, **kwargs) bool[source]¶
Merge off the GUI thread, reporting progress and accepting a cancel.
Everything that touches a widget – re-reading the input table, printing the plan, showing the result – happens here on the GUI thread; the join itself happens on a worker, and the only thing that crosses back is a Signal.
- Returns:
whether a merge was started.
Falsewhen there is nothing to merge, or when one is already running – a second Merge click must not start a second join over the same databases.
- statement() str[source]¶
Everything the panel is saying: the box and the disclosure.
The box holds the counts and the disclosure holds the names, so a caller that wants to know whether the panel said something has to read both. Reading only the box would report a column as unnamed when it is one click away.
- step_states() Dict[int, str][source]¶
Return status text for the first three database-merge steps.
Step 1 reports attached databases, step 2 reports selected object tables and the anchor, and step 3 reports merge progress, output shape, and destination state.
- Returns:
Mapping from step number to user-facing status text.
- property databases: Tuple[AttachedDatabase, ...][source]¶
Every plate row, attached or not, in the input table’s order.
- class spacr.qt.widgets.measurement_scan_panel.MeasurementScanPanel(frame_provider=None, parent=None, database_provider=None, *, threaded: bool = True, destination_provider=None, settings_provider=None, fit=None)[source]¶
Bases:
PySide6.QtWidgets.QWidgetThe scan’s result table, and the two numbers behind every row.
- Variables:
measurement_selected – emitted with the measurement name of the selected row, so a host can draw it.
scanned – emitted with the number of measurements scanned.
Initialize the measurement scan and database-merge panel.
- Parameters:
frame_provider (callable or None, optional) – Zero-argument callable returning the current well-level frame to scan. It is evaluated when needed so a newly loaded run replaces the previous frame.
parent (QWidget or None, optional) – Parent widget.
database_provider (callable or None, optional) – Zero-argument callable returning the regression input rows and their attached measurement databases.
threaded (bool, default=True) – Run database merges outside the GUI thread. Set to
Falsefor synchronous use in tests or headless callers.destination_provider (callable or None, optional) – Zero-argument callable returning the directory where the merged measurement frame is written.
settings_provider (callable or None, optional) – Zero-argument callable returning the current regression settings. Column fits copy these settings and vary only the response.
fit (callable or None, optional) – Function used to fit one selected measurement column.
Noneuses the standard regression implementation.
- add_section(widget, title: str = '') None[source]¶
Put
widgetin the tab as its own resizable, foldable section.Anything added to this tab goes here and not into the layout: a widget appended to the layout takes its height out of the others, which is how the sections came to overlap.
- Parameters:
widget – the widget to add as a foldable section;
Nonedoes nothing.
- databases_frame()[source]¶
The merged frame step 3 produced, or
None.A method rather than the attribute, so step 4 reads the current frame every time instead of a copy taken when it was built.
- is_section_expanded(title: str) bool[source]¶
Whether one result section is open.
- Parameters:
title – the section’s title.
- Returns:
True when expanded.
- refresh_databases() int[source]¶
Re-read the attached databases. Called when the tab is opened.
- Returns:
how many readable databases are attached.
- remember_section_layout() None[source]¶
Store the folds and divider positions. Called when the tab closes.
- restore_section_layout() bool[source]¶
Put back the folds and divider positions from last time.
Called once the sections exist, including any added later by
add_section(), which is why the host calls it rather than the constructor.- Returns:
whether anything was restored.
- scan(frame, **kwargs) bool[source]¶
Scan
frameand show the result.- Parameters:
frame – the measurement frame passed to
spacr.measurement_scan.scan_measurements()together withkwargs.
- section_is_shown(title: str) bool[source]¶
Whether a section is on the tab at all – folded or not.
Two different questions:
_show_sectionhides a section that has nothing to show, while a fold only closes one that does. Testing the content’s visibility would answer both at once, because a folded section’s content is hidden too.- Parameters:
title – the section’s title; an unknown title returns
False.
- sections() tuple[source]¶
Return the folding sections in display order.
The expanding layout filler is excluded from the returned tuple.
- set_database_provider(provider) None[source]¶
Take a new source for the input table’s attached databases.
The same shape as
set_frame_provider(), and for the same reason: the tab re-reads the rows rather than holding a copy of them.- Parameters:
provider – zero-argument callable returning the input table’s rows (see
attached_databases()for the accepted shapes); it is called on every refresh by the databases section.
- set_frame_provider(provider) None[source]¶
Take a new source for the frame the scan runs on.
- Parameters:
provider – zero-argument callable returning the frame the scan runs on, called each time the frame is needed.
- set_result(result) bool[source]¶
Show an already-computed
ScanResult.- Parameters:
result – the
ScanResultto show; itsframe()androwsfill the table, with a verdict per row.
- set_section_expanded(title: str, expanded: bool) None[source]¶
Fold or open one section by name. The hook a preference needs.
- Parameters:
title – the section’s title; an unknown title is ignored.
expanded –
Trueto open the section,Falseto fold it; coerced withbool().
- what_is_available() str[source]¶
One line naming both halves, and whether their wells meet.
Appended to a refusal, because “no ‘gene’ column” is true and does not say that the measurements next to it cannot be reached either.
- class spacr.qt.widgets.measurement_scan_panel.WorkflowStep(number: int, title: str, parent=None, expanded: bool = True)[source]¶
Bases:
PySide6.QtWidgets.QWidgetOne numbered step of the Measurements workflow: a fold and a body.
The third level of nesting on this tab. Its three panels are splitter children that fold (see
MeasurementScanPanel), and each numbered step inside them folds too, so a step that is not in use can be collapsed.The heading is still the
WorkflowStepQLabelstep_header()makes, inside the header row rather than replacing it: it is what the stylesheet selects on and what a reader recognises. What is new beside it is a checkable arrow that hides the body, focusable so a keyboard reaches it, with an accessible name that says which step it folds.- Parameters:
number – one-based step number, as the tab counts them.
title – the step’s title, translated by
step_header().parent – parent widget; ownership only.
expanded – whether it starts open. Steps start open: unlike the tab’s three panels, a step is a stage of one procedure and hiding all of them would leave a panel that says nothing about what it does.
One numbered, collapsible step of the scan workflow.
- Parameters:
number – the step’s position.
title – its caption.
parent – parent widget.
expanded – whether it starts open.
- eventFilter(watched, event)[source]¶
Fold when the heading beside the arrow is clicked.
- Parameters:
watched – the object the event is for.
event – the event.
- Returns:
True when the click was consumed as a fold.
- is_expanded() bool[source]¶
Whether the step’s controls are showing.
Read off the button, not a flag beside it, for the reason
CollapsibleSection.is_expanded()gives: a cached copy can disagree with what the user sees.
- class spacr.qt.widgets.measurement_scan_panel.WorkflowSteps[source]¶
The numbered-step half of a Measurements panel, shared by both of them.
A mixin, not a base class:
DatabaseMergePanelandColumnRegressionPanelare bothQWidgetalready, and the thing they share is bookkeeping rather than a widget.step_folds_changedstays declared on each of them – aSignalon a non-QObjectmixin is not connectable.- box_heights() dict[source]¶
The dragged heights, in px at 100 % font scale.
Divided by the scale on the way out and multiplied on the way back in (
set_box_heights()), so a height chosen at 200 % is the same number of lines when it is restored at 100 %.
- register_box(key: str, grip) None[source]¶
Record a height handle so its drag can be stored and restored.
- Parameters:
key – stable, untranslated name for the box.
grip – the
HeightGripunder it.
- set_box_heights(heights) None[source]¶
Put back what
box_heights()returned.A key this panel does not have is ignored, for the reason
set_step_folds()gives.- Parameters:
heights – mapping of box key to height in pixels at 100 % font scale, as
box_heights()returns; unknown keys are ignored andNonechanges nothing.
- set_step_folds(folds) None[source]¶
Put back what
step_folds()returned.A number this panel does not have is ignored rather than an error: a layout stored by a version with five steps must not stop this one from starting.
- Parameters:
folds – mapping of step number to
True(open) orFalse(folded), asstep_folds()returns; unknown numbers are ignored andNonechanges nothing.
- spacr.qt.widgets.measurement_scan_panel.anchor_tables(tables: Sequence[str]) Tuple[str, ...][source]¶
The subset of
tablesthat can be an anchor.One row per cell, from
spacr.object_roles.ONE_ROW_PER_CELL. Anchoring on a many-per-cell table would make a row of the merged frame mean one nucleus or one pathogen, with the cell’s own measurements repeated across its children – the fan-out the roll-up prevents.- Parameters:
tables – measurement table names; those holding one row per cell are kept, in their given order.
- spacr.qt.widgets.measurement_scan_panel.attached_databases(rows: Any) Tuple[AttachedDatabase, ...][source]¶
The input table’s rows as
AttachedDatabaseentries.- Parameters:
rows – what the host’s database provider returned. The shape the input table emits is a list of
{"plate", "score", "count", "database"}dicts; a(plate, path)pair, a(plate, path, screen)triple and a bare path are accepted too, so a caller with a plainer list does not have to build dicts to be understood.- Returns:
one entry per row, in row order, including the rows with no database – they are the plates this tab has to disable rather than drop, and dropping them here would make them invisible instead.
- spacr.qt.widgets.measurement_scan_panel.column_run_settings(base: Dict[str, Any] | None, column: str, score_path: str) Dict[str, Any][source]¶
The settings for one fit of the queue: this column, this score file.
- Parameters:
base – the regression screen’s own settings, or
None.column – the response to fit.
score_path – the merged frame written by
write_merged_frame().
A copy, never the caller’s dict. Twelve fits built by mutating one dict are twelve fits of whatever the last one asked for – and the base is the live settings panel, which the user may be editing while the queue runs.
The count side is left unchanged. Only the response varies between these runs, which is what makes them comparable in the Runs tab; the guides each well got are the same guides.
- spacr.qt.widgets.measurement_scan_panel.default_aggregation_columns(columns: Sequence[str], *, overrides: Dict[str, str] | None = None) Tuple[str, ...][source]¶
The columns no
AGGREGATION_RULESrule names.A measurement no rule covers is listed by name. These fall through to
DEFAULT_AGGREGATION, which is the mean – right more often than not for an unrecognised number, and silently wrong for a total.Computed by re-walking the rule table, so it cannot drift from it: a list written here would go stale the first time a rule was added.
- Parameters:
columns – the column names about to be aggregated.
overrides – the user’s explicit choices, which win over every rule and are therefore not fall-throughs.
- spacr.qt.widgets.measurement_scan_panel.describe_key_overlap(left_name: str, left, right_name: str, right) str[source]¶
Whether two frames’ wells meet, and one example from each side if not.
The sentence is computed from the keys.
""when the two do overlap, because then the join is not the problem.- Parameters:
left_name – how the first frame is named in the sentence, such as
'merged measurements'.left – the first frame; its well keys come from
well_keys().right_name – how the second frame is named in the sentence.
right – the second frame; its well keys come from
well_keys().
- spacr.qt.widgets.measurement_scan_panel.displayed_plates(plates: Sequence[str]) Tuple[str, ...][source]¶
Plate ids as the plates are called, in their given order.
- Parameters:
plates – plate ids, each passed through
canonical_plate_id.
- spacr.qt.widgets.measurement_scan_panel.joinable_tables(paths: Sequence[str]) Tuple[str, ...][source]¶
The object tables every one of these databases has, in table order.
The intersection, not the union:
spacr.multi_database.describe_merge()reads a row count from every path, so asking it for a table one database lacks raises a baresqlite3.OperationalErrornaming the table and nothing else. Offering only what all of them have means the user never picks that.- Parameters:
paths – measurement databases.
- Returns:
names from
spacr.merge_tables.OBJECT_TABLES– the object-role registry, cell/nucleus/pathogen/cytoplasm and every organelle slot, rather than four names typed here – followed bypng_listwhere every database has it.- Raises:
sqlite3.Error – a path that is not a readable database.
- spacr.qt.widgets.measurement_scan_panel.merge_across_databases(paths: Sequence[str], tables: Sequence[str], *, policy: spacr.merge_tables.MergePolicy | None = None, screens: Any = None, columns: str = 'common', report=None, limit_per_source: int | None = None, progress=None, cancelled=None, on_ambiguous_identifier: str = 'refuse')[source]¶
Every chosen table of every chosen database, on one anchor.
This composes the two existing merges and adds nothing else.
spacr.multi_database.read_merged()is many databases, one table;spacr.merge_tables.merge_tables()is one database, many tables and takes a path, so it cannot be handed a frame that already spans databases. This runs the first per chosen table and then joins them with the second’s ownroll_up()andhow_for(). There is no sum and no mean written here; every number comes from the rules.The roll-up keys carry the screen and the source. Omit them and two screens legitimately sharing
plate1– the casedescribe_merge()deliberately permits – would collapse into one parent, reintroducing one layer up the exact poolingspacr.multi_databaseexists to prevent.- Parameters:
paths – measurement databases. Repeats are read once.
tables – the object tables to join. The anchor is added if absent.
policy – how each measurement combines and what happens to a cell with no children.
policy.primaryis the anchor and defaults toDEFAULT_ANCHOR.screens – screen label per database – a sequence parallel to
pathsor a mapping from path. Passed to both the plan and the read.columns –
'common'(default) or'union', asread_merged.report – called with one line per thing the merge cost.
limit_per_source – row cap per database, for a preview.
progress – called
progress(stage, done, total)as the merge moves.stagenames the table and the database;done/totalare rows, against the same total the plan prints. Runs on whatever thread the merge does, so a GUI caller relays it rather than touching a widget in it.cancelled – called between stages; a true answer raises
MergeCancelled. Nothing is written anywhere until this function returns, so a cancelled merge leaves the previous result exactly where it was.on_ambiguous_identifier –
'refuse'(default) leaves out a text identifier that differs within a roll-up group and names it;'first'restores the old silent pick. Seespacr.plate_measurements.ambiguous_identifiers().
- Returns:
one row per anchor object, with
frame.attrscarrying what the merge cost – seemerge_report(), which renders it.- Raises:
MergeError – the anchor is not one row per cell, or carries no object label.
spacr.multi_database.MergeRefused – a plate id appears twice within one screen.
spacr.multi_database.MergeCancelled – the caller asked it to stop.
- spacr.qt.widgets.measurement_scan_panel.merge_evidence(frame) str[source]¶
The lists behind
merge_summary()’s counts, shown behind a disclosure.Every name the summary counted, so that a user who wants to check the claim can, and one who does not is not made to read it.
- Parameters:
frame – the merged frame; the
default_aggregation,identifier_columnsanddropped_columnsentries of itsattrsare listed.
- spacr.qt.widgets.measurement_scan_panel.merge_report(frame) str[source]¶
The whole statement:
merge_summary()and then its evidence.Kept as one string for a caller that wants everything – a log line, a test, a headless script. The panel shows the two halves in two places.
- Parameters:
frame – the merged frame, passed to
merge_summary()andmerge_evidence().
- spacr.qt.widgets.measurement_scan_panel.merge_summary(frame) str[source]¶
What the merge cost, as counts. This is what fits in the box.
Column names are not listed inline, so the three key lines – what joined how, how many rows, what the anchor is – are not buried among names such as
nucleus_channel_2_channel_3_M2_correlation_85.The count is the summary. The list is the evidence, and goes behind a disclosure.
A refusal is the exception and stays here whatever its length: it is not evidence for a claim, it is the claim, and a user who never opens the disclosure still has to be told a column was left out.
- Parameters:
frame – the output of
merge_across_databases().
- spacr.qt.widgets.measurement_scan_panel.ordered_columns(frame) list[source]¶
PREFERRED_COLUMNSthis frame has, then everything else.Ordering, not filtering – the columns nobody thought to list are still the user’s own numbers.
- Parameters:
frame – the frame whose columns are ordered;
Nonereturns an empty list.
- spacr.qt.widgets.measurement_scan_panel.plate_id_notes(plan) List[str][source]¶
Describe noncanonical plate identifiers that remain in a merge plan.
Plate identifiers are normally canonicalized while each database is read. A value that still differs from
canonical_plate_id()at this stage was not repaired during import and may fail to match score or count CSVs, whose identifiers are normalized byspacr.utils.correct_metadata().- Parameters:
plan – Merge plan containing database sources and their plate IDs.
- Returns:
One warning per affected database, or an empty list when all identifiers are canonical.
- spacr.qt.widgets.measurement_scan_panel.regressable_columns(frame) Tuple[str, ...][source]¶
The columns of a merged frame a regression could take as its response.
- Parameters:
frame – a merged measurement frame, or
None.- Returns:
the numeric measurement columns, in the frame’s own order.
Numeric and not an identifier; both conditions are checked. A merged frame carries
plateID,object_label,source_databaseand the text identifiersmerge_across_databasescarries through; a picker that offered those would offer a fit onto a well name.A column of one value is left out too. It has no variance, so the fit is degenerate – and every backend reports that differently, which turns one unusable choice into N different-looking failures.
- spacr.qt.widgets.measurement_scan_panel.resizable_box(owner, widget, layout, *, key: str, minimum: int, default: int, maximum: int, name: str)[source]¶
Give
widgeta user-draggable height instead of a hard cap.This replaces fixed maximum heights. Every tall box on this tab was pinned with
setMaximumHeight(N)at a literal N chosen against a 100 % font. Measured offscreen at a 200 % font scale – a supported accessibility setting – each one lost about half its content and could not be dragged back:box
cap
@100 %
@200 %
attached databases
170 px
4 rows
3 rows
join list
72 px
4 rows
2 rows
merge report
190 px
11 lines
5 lines
merge evidence
220 px
12 lines
6 lines
column picker
180 px
10 rows
5 rows
outcomes
120 px
7 lines
3 lines
So the height now starts at
defaultscaled by the font preference – at 200 % the report opens twice as tall and keeps its eleven lines – and aHeightGripunder it drags it anywhere betweenminimumandmaximum. Home’s release-notes panel has had that handle for a while; this is the same class, moved tospacr.qt.widgets.height_griprather than written a second time.- Parameters:
owner – the panel the box belongs to. The handle is registered on it under
keyso the tab can store and restore the height.widget – the box to make resizable. Added to
layouthere.layout – the column the box and its handle go into.
key – stable name for the stored height. Not
name: that one is read aloud and may be translated, and a layout keyed on a translated string would be lost when the user switches language.minimum – floor in px at 100 % font scale.
default – opening height in px at 100 % font scale.
maximum – ceiling in px at 100 % font scale.
name – accessible name for the handle – which box it resizes.
- Returns:
the
HeightGripthat was installed.
- spacr.qt.widgets.measurement_scan_panel.run_column_fits(columns: Sequence[str], settings_for, fit, *, progress=None, cancelled=None, on_result=None) List[ColumnFit][source]¶
Fit response columns sequentially while isolating per-column failures.
- Parameters:
columns (sequence of str) – Response columns in execution order.
settings_for (callable) – Called with a column and returns settings for that fit.
fit (callable) – Called with the settings and returns the pipeline result.
progress (callable, optional) – Called as
progress(column, index, total)before each fit.cancelled (callable, optional) – Called between fits; a true result stops the queue.
on_result (callable, optional) – Called with each
ColumnFitwhen it completes or fails.
- Returns:
list of ColumnFit – One result for every attempted column. A failed fit does not prevent later columns from running.
- spacr.qt.widgets.measurement_scan_panel.step_header(number: int, title: str, parent=None)[source]¶
Create a numbered heading for one database-merge workflow step.
The title is translated before it is uppercased and prefixed with the step number. The returned label uses the
WorkflowStepobject name for shared stylesheet selection.- Parameters:
number – One-based workflow step number.
title – Source title to translate and display.
parent – Optional Qt parent.
- Returns:
Bold, word-wrapped
QLabelfor the step.
- spacr.qt.widgets.measurement_scan_panel.verdict_for(row) str[source]¶
One phrase per measurement, from both corrections.
- Parameters:
row – one scan row; its
survives_across_scanandsurvives_within_runattributes are read, and missing ones count as false.
- spacr.qt.widgets.measurement_scan_panel.well_keys(frame) Tuple[str, Tuple[str, ...]][source]¶
(what the key is called, the distinct well keys)for one frame.prcwhen the frame carries it, otherwise built fromplateID/rowID/columnID– which is whatprcis, and the reason a measurements table with noprccolumn is still comparable to a regression frame that has one.- Parameters:
frame – the frame to read; its
prccolumn is used when present, elseplateID,rowIDandcolumnIDjoined with underscores.Noneor a frame without columns gives("", ()).- Returns:
("", ())for a frame carrying no well identity at all, which explains why nothing joined.
- spacr.qt.widgets.measurement_scan_panel.write_merged_frame(frame, folder: str, name: str = MERGED_FRAME_NAME, *, report=print) str[source]¶
Stage the merged frame: offer it in memory, write the durable copy.
- Parameters:
frame – the merged frame.
folder – where to put it. Created if it is not there.
name – what the artefact is called. Only its stem is used – the suffix belongs to the writer, so the same call produces Parquet where an engine is installed and CSV where none is, and a reader dispatches on what it finds.
report – called with one line naming what was written and what it cost;
Noneto say nothing.
- Returns:
the path written, or
""when there was nothing to write.
The artefact is still written to disk. A user can open it, and every fit of a queue then reads the same numbers, which is what made it an artefact rather than a preview in the first place.
Nothing in this process parses it back. The frame is already in this process when it is written, and the fits run in this process too, so
spacr.frame_handoff.stage()offers it under the path it wrote before the write returns. A four-plate screen merges to about 2.75 GB: the CSV write cost around 160 seconds before anything read it, and each fit then parsed the whole file back out of the page cache – minutes of CPU with no disk I/O at all, which is what made a working run look hung.The offer is a weak reference, so it cannot keep a multi-gigabyte frame alive after the panel that merged it lets go, and a reader that was offered nothing reads the file exactly as before.
Nested helpers¶
- DatabaseMergePanel._follow_path_probes.corrected(path: str, _answer: bool) None¶
Redraw when
pathis one of ours, ignore every other.- Parameters:
path – the path whose answer just changed.
_answer – what it changed to; the rows are drawn from
paths(), which reads the probe’s cache itself.
spacr/qt/widgets/measurement_scan_panel.py:1971
- DatabaseMergePanel._follow_path_probes.let_go(*_args) None¶
Drop the probe connection as the panel is destroyed.
Only while still connected:
closeEventmay have unfollowed already, and disconnecting twice warns.- Parameters:
_args – whatever
destroyedsends; unused.
spacr/qt/widgets/measurement_scan_panel.py:1987
- DatabaseMergePanel.show_aggregation_rules.preview()¶
Read the preview. Worker thread; touches no widget.
- Returns:
what
read_merged()returns for the chosen table.
spacr/qt/widgets/measurement_scan_panel.py:2883
- describe_key_overlap._canonical(keys)¶
Keys with their plate id canonicalised, so two spellings match.
spacr/qt/widgets/measurement_scan_panel.py:2995