spacr.qt.widgets.measure_preview¶
Interactive Measure crop preview with a pipeline-compatible settings panel.
The compact card is intentionally image-first. Its Crop settings… dialog
contains the Measure settings that can be evaluated on one merged array:
general mask/channel controls, object-crop output controls, measurement
filters, and preview-only display controls. With propagation enabled, every
pipeline setting is copied to the main Measure form as it changes.
Cell crops are grouped by three independent companion-object dimensions: nucleated/unnucleated, infected/uninfected, and with/without organelles. This keeps all cells visible while making it explicit which categories would be retained by the current filter settings.
Classes¶
Tabbed live settings dialog for |
|
Preview Measure crops and propagate a faithful run configuration. |
Functions¶
|
Tag each crop with its phenotype category and whether filters keep it. |
|
Crop the objects out of |
|
Read one merged |
|
A concrete merged |
Module Contents¶
- class spacr.qt.widgets.measure_preview.CropSettingsDialog(panel: MeasurePreviewPanel)[source]¶
Bases:
PySide6.QtWidgets.QDialogTabbed live settings dialog for
MeasurePreviewPanel.- Parameters:
panel – the preview panel this dialog edits. It is also the dialog’s PARENT, and the widgets the dialog lays out belong to the panel rather than to it – the dialog only knows which rows they sit on, which is what lets a morphology change re-gate them.
Build the crop-settings dialog over one preview panel.
- Parameters:
panel – the preview these settings belong to.
- closeEvent(event)[source]¶
Remember the dialog’s geometry before it goes.
- Parameters:
event – the Qt close event.
- refresh_organelle_slots() None[source]¶
Show one organelle slot per slot the run declares.
The rows are HIDDEN, not removed: the widgets keep their values, so lowering the count and raising it again finds the old answers still there – the same promise
spacr.settings._set_organelle_defaultsmakes for the settings themselves.IT LAYS OUT AS WELL AS GATING. The panel builds a slot’s controls when the count reaches it rather than building all 702 up front, so a count that rises while this dialog is open arrives as controls with no rows;
_adopt_new_slot_controlsputs them in the block before the gate below decides which are shown.An unset count shows every slot that exists, which is what this dialog did before the count reached it: better to offer a field too many than to hide one a run is using.
- class spacr.qt.widgets.measure_preview.MeasurePreviewPanel(parent=None, *, threaded: bool = True)[source]¶
Bases:
spacr.qt.widgets.preview_contract.LivePreviewContract,PySide6.QtWidgets.QWidgetPreview Measure crops and propagate a faithful run configuration.
A live view like the other three, and since the shared contract (
LivePreviewContract) it wears their vocabulary: the same Run preview button, the same Cancel beside it, and a sentence on the status line whenever it cannot preview. It used to be alone in every one of those columns — its button said “Refresh crops”, nothing could be cancelled, and a press with no array loaded did nothing and said nothing.- Parameters:
parent – parent widget.
threaded – whether the panel’s jobs run off the GUI thread. False runs each one inline, emitting the same signals in the same order, so a test can drive the panel synchronously without the behaviour diverging.
Build the preview: its controls, its grid and its drop target.
- Parameters:
parent – parent widget.
- apply_settings(settings: dict) None[source]¶
Seed the panel from the main Measure settings dict.
The inverse of
settings_for_propagation(), and tested as one. This panel had noapply_settingsat all, so the crop preview – opened to decide whether a crop size will cut the cell in half, or which stain lands in which colour – always answered for its own defaults rather than for the run about to happen.Every field is copied independently: a settings file carrying one unusable value must not cost the panel every field after it.
- Parameters:
settings – the Measure settings dict, or
None; keys that are absent orNoneleave their controls unchanged, and a value that cannot be applied is skipped.
- closeEvent(event)[source]¶
Stop any preview work before going away.
- Parameters:
event – the Qt close event.
- current_params() dict[source]¶
The parameters the preview is using right now.
- Returns:
the parameters as a plain dict.
- dragEnterEvent(event)[source]¶
Accept a drag carrying something this preview can measure.
- Parameters:
event – the Qt drag event.
- dragMoveEvent(event)[source]¶
Keep accepting while droppable input stays over the panel.
- Parameters:
event – the Qt drag event.
- dropEvent(event)[source]¶
Take the dropped input and preview it.
- Parameters:
event – the Qt drop event.
- load_array(path: str) bool[source]¶
Synchronously read and install one merged array.
The sibling of
LivePreviewPanel.load_image: for programmatic callers and tests. The GUI usesload_array_async().- Parameters:
path – merged
.npyfile, run folder ormerged/folder, asload_merged_array()accepts; a read error is shown in the status line and returnsFalse.
- load_array_async(path: str, *, enumerate_sets: bool = True) bool[source]¶
Read
pathon a worker, then install it on the GUI thread.Every GUI entry point – the drop handler, the Choose-array dialog and the FOV dropdown – comes through here. A 17 MB merged array is not a cheap read, and the crop pass that follows it is far worse.
- Parameters:
path – merged
.npyfile, run folder ormerged/folder, asload_merged_array()accepts; an empty value submits nothing.- Returns:
Truewhen a job was submitted.
- refresh() None[source]¶
Re-crop the loaded array and redraw the grid.
Dispatches: the crop pass runs on a worker and the grid is rebuilt when it lands. Every knob in the Crop settings dialog is wired to this, so it used to freeze the window for 1441 ms per spinbox step on a 1024x1024x8 array. Re-cropping supersedes by token, so dragging a spinbox through ten values draws the last one rather than all ten.
Says why when it cannot: returning in silence left the button doing nothing with nothing on the status line, which is the one thing no live view may do.
Spinbox drags call refresh repeatedly in one event-loop turn, so the old grid is cleared once for the latest token and only its result is drawn.
- run_preview() None[source]¶
Re-crop on demand — the shared name for the shared action.
Every live view answers to
run_preview; this panel’s ownrefresh()stays as the internal path the crop knobs drive, which supersedes rather than refusing.
- set_organelle_count(count) None[source]¶
How many organelle slots the crop settings should offer.
The same rule Mask and the Mask live preview follow: the run declares
number_of_organelles, and every panel shows that many. Without it the crop settings offered a fixed four – so a one-organelle run had three mask-slice and three minimum-area fields for objects it does not have, and each of them propagates into the settings the run reads.IT BUILDS THEM AS WELL AS SHOWING THEM. This used to change only what was SHOWN, over 702 slots’ worth of controls that
_build_controlshad already made – so the answer to “a run with no organelle” was 2,117 controls hidden behind a gate. The count is now what brings a slot’s controls into existence, which is why raising it is the only route that has to work.- Parameters:
count – number of organelle slots, clamped to 0-
MAX_ORGANELLES; a valueint()rejects is ignored.
- set_propagate_callback(callback) None[source]¶
Set what to call when the user pushes these settings to the run.
- Parameters:
callback – called with the settings dict.
- spacr.qt.widgets.measure_preview.annotate_crops(crops: List[Dict[str, Any]], data: numpy.ndarray | None, params: Dict[str, Any]) None[source]¶
Tag each crop with its phenotype category and whether filters keep it.
paramsis a snapshot of the widget values taken on the GUI thread – seeMeasurePreviewPanel._category_params(). Passing a snapshot rather than reading the widgets is what makes this safe to call from a worker.- Parameters:
crops – crop dicts from
spacr.measure.crop_objects_from_array(), each with alabel; updated in place withcategory,includedand, for cells,phenotype.data – the merged
(H, W, C)array the crops came from, orNone, in which case every crop is simply included.params – widget snapshot with
object,cell_dim,dims,minimaanduninfected; onlyobject == "cell"is categorised by phenotype.
- spacr.qt.widgets.measure_preview.compute_crops(data: numpy.ndarray, crop_kwargs: Dict[str, Any], category_params: Dict[str, Any]) Dict[str, Any][source]¶
Crop the objects out of
dataand categorise them. Worker-safe.The whole of what
refreshused to do inline, minus the drawing:QPixmapis a GUI object and building one off the GUI thread is undefined behaviour, so the pixmaps stay in_render_grid().- Parameters:
data – merged
(H, W, C)array of image channels then mask slices.crop_kwargs – keyword arguments for
spacr.measure.crop_objects_from_array(), e.g.mask_dim; a crop failure is returned aserrorrather than raised.category_params – widget snapshot passed to
annotate_crops().
- Returns:
{crops, error}.
- spacr.qt.widgets.measure_preview.load_merged_array(path: str, enumerate_sets: bool = True) Dict[str, Any][source]¶
Read one merged
(H, W, C)array. No Qt, so it runs on a worker.Also lists the sibling arrays, by name only, for the same reason
live_preview.load_source_payloaddoes:_refresh_source_selectorsenumerates the folder on every load, and doing that on the GUI thread cost 124 ms of the 2469 ms freeze this replaced.- Parameters:
path – NumPy array file to open; a usable payload must decode to one merged
(height, width, channels)array.enumerate_sets –
Falsereuses the sampler’s cached listing – the FOV dropdown hands out a path it already enumerated.
- Returns:
{path, data, directory, sets, channels, error}.dataisNonewhenevererroris set or the file is not a merged array.
- spacr.qt.widgets.measure_preview.resolve_merged_source(path, rng=None)[source]¶
A concrete merged
.npyfrom whatever the user gave us.THREE THINGS ARE A VALID ANSWER TO “where are the crops”, and only one of them used to be accepted:
a merged
.npy– what the Choose dialog offered, and nothing else;a RUN FOLDER, which is what
srcholds. A Measure run is pointed at the plate directory and findsmerged/itself, so the preview asking for a file meant hunting through a folder for one of fifty-two arrays whose names carry a well and a field and nothing about which is interesting;a
merged/folder directly.
A field is picked at RANDOM rather than taking the first. Sorted first is always the same well and the same field, so a preview that always opens on
E01field 1 tells the user about one corner of one condition – and if that field happens to be clean, a crop size that cuts cells in half everywhere else looks fine.No Qt and no widgets: this runs on the worker with the load it feeds.
- Parameters:
path – a file, a run folder, or a merged folder.
rng – something with
choice; defaults torandom.
- Returns:
a
Pathto one array, orNone.
Nested helpers¶
- MeasurePreviewPanel.apply_settings._set(fn, key, cast=None)¶
Apply one setting, skipping keys that are absent or None.
Absent and None are LEFT ALONE rather than applied as a default: the preview is showing what the run will do, and filling a gap here would show a value the run does not have.
spacr/qt/widgets/measure_preview.py:2017
- _unmixed_crop_source.checkpoint()¶
Stop between controls and before expensive display processing.
spacr/qt/widgets/measure_preview.py:438
- _unmixed_crop_source.load_control(filename)¶
Read each control without changing it or accepting a wrong layout.
spacr/qt/widgets/measure_preview.py:453