spacr.qt.widgets.live_preview¶
Live-preview segmentation widget — v2.
Interactive Cellpose tuning surface for the Mask app screen. Compared to v1, this rewrite adds every enhancement the user requested after their first run through the panel:
Zoomable canvases (Ctrl+scroll, in sync). Both the original and the mask overlay live in a shared
QGraphicsViewpair — pan and zoom on one and the other tracks pixel-for-pixel.Hover tooltip. Move the cursor over the original and a pinned status line shows the pixel intensity for every channel plus, when present, the object label at that position from the last segmenta- tion. Same tooltip regardless of which view holds the cursor.
Normalise toggle. Optional 2–98 % percentile stretch (per channel for RGB) so raw low-contrast tiles are legible.
Model-aware options. Every model shows the full segmentation set. Cellpose-SAM does not ignore
flow_threshold,cellprobordiameter— seeDIAMETER_TOOLTIPfor the measurement that killed that belief.Outline colour + thickness. Chosen from the toolbar; effect is live once a mask exists.
color (random)assigns a stable categorical colour to every object label so touching masks remain distinguishable.Multi-object segmentation. An “object type” combo picks between
cell,nucleus, andcell + nucleus. In cell+nucleus mode the panel runs two Cellpose passes and overlays both masks in distinct colours.Pre / Post filters. When the object type is
cell(or the combined mode) the panel routes pre / post-processing settings from the Mask app (cell_min_size,cell_max_size,remove_background_cell, background intensity, …) through the segmentation. Users toggle these on/off with dedicated “Pre” / “Post” clickable labels sitting next to “Run preview” in the same visual style as the LP / AI toggles.
The whole file stays safe to import without cellpose — every cellpose call is lazy-imported inside the worker thread.
Attributes¶
Classes¶
Interactive segmentation preview — Mask app only. |
|
Modal dialog that surfaces every live-preview setting. |
|
Everything the worker needs to run one segmentation pass. |
Functions¶
|
Return the first supported image at or below |
|
Read path into an (H, W) or (H, W, C) uint8/uint16 array. |
|
Max-project a field's planes, the way the ingest already does. |
|
Discover, enumerate and decode one preview source. Data in, data out. |
|
Convert an (H, W) or (H, W, C) array to a |
|
Legacy single-mask overlay retained for older imports. |
|
Return an RGB uint8 view of |
|
Return one vivid random RGB triple for the |
Module Contents¶
- class spacr.qt.widgets.live_preview.LivePreviewPanel(parent=None, *, threaded: bool = True)[source]¶
Bases:
PySide6.QtWidgets.QWidgetInteractive segmentation preview — Mask app only.
- apply_settings(settings: dict)[source]¶
Copy relevant values from a Mask-app
settingsdict, and cache the whole dict for the Pre / Post routes to read from.
- cancel_preview() bool[source]¶
Abandon the preview run in flight, if there is one.
Cellpose exposes no interrupt, so the thread is left to run itself out; bumping the run token is what makes its answer land as a no-op (
_on_worker_done()drops results carrying a stale token).- Returns:
True when a running worker was abandoned.
- load_image(path)[source]¶
Synchronously load one image.
Intended for explicit programmatic calls and tests, and for those only. Every GUI path — the drop handler, the FOV dropdown and the Choose-image dialog — goes through
load_source_async(), so that neither the decode nor the folder enumeration behind_refresh_source_selectorscan block the application thread. Three of them used to call this instead, which is what the docstring already claimed was not happening.
- load_source_async(source, *, enumerate_sets: bool = True) bool[source]¶
Discover and decode a file/folder source on a worker thread.
New requests supersede older ones by token. An old decoder is allowed to finish safely, but its result is ignored.
- Parameters:
source – direct supported image or directory containing images.
enumerate_sets –
Falsereuses the sampler’s cached listing instead of re-scanning. Seeload_source_payload().
- Returns:
Truewhen a worker was started.
- open_live_settings()[source]¶
Open (or focus) the Live Settings modal.
The dialog rehomes every hidden state widget into its form so the user’s edits go straight into
self._*— nothing to sync. On close, widgets are re-parented back toself(hidden again) so state persists across opens.
- propagate_settings() None[source]¶
Send the current live settings to the main panel (if a callback is registered). Called on any live-settings change while the dialog’s Propagate toggle is on.
- refresh_model_choices() None[source]¶
Re-read the Cellpose model list and add anything new.
spacr.settings.cellpose_model_choicesonly reads the API when Cellpose is already imported, because importing it costs ~2.5 s and this panel is built while a page is being laid out. That means the first build usually gets the shipped fallback — so ask again every time the panel is shown. After the first segmentation Cellpose is loaded and a checkpoint the user registered appears here.Additive on purpose: the current selection is never disturbed, and an entry is never removed, so a value the user picked cannot vanish under them because a probe came back thinner.
- set_propagate_callback(cb) None[source]¶
Register a callback(dict) used to push tuned live settings back to the main settings panel (wired by the AppScreen).
- settings_for_propagation() dict[source]¶
Map the live-preview widget values to main-panel settings keys.
- shutdown() None[source]¶
Abandon any load in flight and leave no QThread behind.
Called from
closeEvent(), and safe to call directly when a screen is torn down without one.
- class spacr.qt.widgets.live_preview.LiveSettingsDialog(panel: LivePreviewPanel)[source]¶
Bases:
PySide6.QtWidgets.QDialogModal dialog that surfaces every live-preview setting.
Re-parents the panel’s hidden state widgets into a QFormLayout so edits go straight into the panel’s canonical fields — nothing to sync manually. On close, widgets are returned to the panel hidden so their values persist across opens.
- Rows shown (per the user’s spec):
Normalisation upper + lower percentile
Outline colour
Outline thickness
Model
Flow threshold
Cell probability
Object type
Object channel (cell / nucleus depending on selection)
Pre (bool)
Post (bool)
- refresh_visibility()[source]¶
Grey out settings that don’t apply to the current selection.
- Rules (mirroring the pipeline’s own relevance):
Nothing in the Segmentation group greys out for the model. Cellpose 4 ships one set of weights and all three knobs (diameter / flow / cell-prob) still reach it — see
DIAMETER_TOOLTIPfor the measurement.The object type decides which channel spinners are live: the cell channel greys out for a nucleus-only object and vice-versa.
Pre-processing knobs (normalise + its two percentiles) are only relevant when the Pre step is enabled.
Overlay / post knobs (outline colour + thickness) are only relevant when the Post step is enabled.
- class spacr.qt.widgets.live_preview.PreviewRequest[source]¶
Everything the worker needs to run one segmentation pass.
Kept as a plain dataclass so tests can construct it directly; the panel builds one from its widget state on each Run.
- image: numpy.ndarray[source]¶
- spacr.qt.widgets.live_preview.first_supported_image(source: pathlib.Path) pathlib.Path | None[source]¶
Return the first supported image at or below
source.Direct image files are returned unchanged. Directory traversal stops as soon as the first sorted match is found instead of materialising and sorting every image in a potentially enormous plate.
- Parameters:
source – image path or directory to inspect.
- Returns:
the first supported image, or
None.
- spacr.qt.widgets.live_preview.load_preview_image(path: pathlib.Path) numpy.ndarray[source]¶
Read path into an (H, W) or (H, W, C) uint8/uint16 array.
Tifffile is used for TIFFs to preserve bit-depth; other formats fall back to PIL. Raises
FileNotFoundErrorif the path is bad.
- spacr.qt.widgets.live_preview.load_preview_mip(paths) numpy.ndarray[source]¶
Max-project a field’s planes, the way the ingest already does.
io._rename_and_organize_image_filesreduces every z-stack tonp.maxover its planes, per field and per channel, before anything reachesstack/. This is the preview’s copy of that, so what the user is looking at is what masking will actually run on.Planes are folded one at a time rather than stacked: a 60-plane field at 2048x2048 uint16 is 500 MB as one array and 8 MB folded, and the preview is on the GUI thread.
- Parameters:
paths – plane paths in acquisition order; one path is returned unchanged, so a flat 2-D field costs nothing.
- Raises:
FileNotFoundError – if no path can be read.
- spacr.qt.widgets.live_preview.load_source_payload(source, max_sets: int = DEFAULT_MAX_SETS, enumerate_sets: bool = True) Dict[str, Any][source]¶
Discover, enumerate and decode one preview source. Data in, data out.
This is the whole of a preview load, written so it touches no widget and no Qt object and can therefore be handed straight to
spacr.qt.job_runner.JobRunner. It used to be therunmethod of a hand-rolledQThreadthat emitted two signals, which kept the panel’s sampler warm by orderingenumeratedbeforeloaded; returning both halves in one dict gets the same ordering for free, because the caller adopts the enumeration and installs the image in a single GUI-thread call.The enumeration reads file names only — it never opens an image — so the single decode here stays the only file read for a folder of any size.
- Parameters:
source – image file or directory to load a preview from.
max_sets – cap for the sample drawn when
sourceis a directory.enumerate_sets –
Falseskips the folder scan entirely. The FOV dropdown hands out a path from a set the sampler already produced, so re-scanning for it would burn a full pass over a 98 000-file plate to rediscover what is already cached.
- Returns:
{path, array, directory, sets, channels, error}.setsisNonewhen no enumeration was done or it failed, which the caller reads as “leave the sampler alone”.
- spacr.qt.widgets.live_preview.numpy_to_qpixmap(arr: numpy.ndarray, normalise: bool = True, lo_pct: float = 2.0, hi_pct: float = 98.0) PySide6.QtGui.QPixmap[source]¶
Convert an (H, W) or (H, W, C) array to a
QPixmap.The result is always RGB888, so the caller cannot hand Qt a buffer whose real row length disagrees with the
w * 3stride below. Channel counts other than three are reconciled here — extra channels are dropped, missing ones are filled with black — because a mismatch madeQImagereadh * w * 3bytes out of a buffer that only heldh * w.
- spacr.qt.widgets.live_preview.overlay_mask(image: numpy.ndarray, mask: numpy.ndarray) numpy.ndarray[source]¶
Legacy single-mask overlay retained for older imports.
- spacr.qt.widgets.live_preview.overlay_masks(image: numpy.ndarray, masks: Dict[str, numpy.ndarray], outline_rgb: Tuple[int, int, int] | None = None, outline_thickness: int = 1, normalise: bool = True, lo_pct: float = 2.0, hi_pct: float = 98.0, random_outline: bool = False, outline_colors: Dict[str, Tuple[int, int, int]] | None = None) numpy.ndarray[source]¶
Return an RGB uint8 view of
imagewith every mask’s boundary drawn in the object’s colour (oroutline_rgbwhen supplied).- Parameters:
image – (H, W) or (H, W, C) source image.
masks –
{object_type: label_array}— one entry per object type currently visible on the panel.outline_rgb – overrides the per-object colour when the user picks a global outline colour from the toolbar.
outline_thickness – number of pixels the boundary is dilated by (1 = crisp, 3 = highlighter). Tops out at 5.
normalise – forwarded to
_to_uint8().random_outline – assign every positive object label a vivid, stable categorical colour. This takes precedence over
outline_rgband corresponds tocolor (random)in Mask Live.outline_colors – per-compartment colour overrides used when no global
outline_rgbis given. This is how the panel’sautomode reaches the renderer: it holds one random colour per compartment for the current run. Falls back toOBJECT_COLORSfor anything it does not name.
- spacr.qt.widgets.live_preview.random_outline_colour(rng: random.Random | None = None) Tuple[int, int, int][source]¶
Return one vivid random RGB triple for the
autooutline mode.Hue is uniform over the full circle while saturation and value stay high, so the colour is always legible on top of a micrograph — a uniform draw in RGB would regularly produce muddy near-grey outlines nobody can see.
- Parameters:
rng – optional generator, for reproducible tests.
- Returns:
(r, g, b)in 0..255.
- spacr.qt.widgets.live_preview.COMPARTMENT_FIELDS = (('min_area', 'Min area (px²)', 'int', (0, 100000000, 0)), ('max_area', 'Max area (px²)', 'int',...[source]¶
- spacr.qt.widgets.live_preview.DIAMETER_TOOLTIP = "(float, px) Expected object diameter. Cellpose-SAM uses it: the image is rescaled by...[source]¶