spacr.qt.widgets.preview_contract

One contract for every live view, written down once.

Four modules ship a live view — Mask (live_preview), Timelapse (timelapse_preview), Measure (measure_preview) and Motility (motility_preview) — and each of them grew its own answer to the same handful of questions. The inventory taken before this module was written, one column per live view:

question

Mask

Timelapse

Measure

Motility

what the run button says

Run preview

Run preview

Refresh crops

Run preview

public entry point

run_preview

run_preview

refresh

run_preview

can the user cancel it

method only

no

no

no

does it say why it cannot

yes

yes

NO — silent

yes

what “busy” reads as

“Preview already running.”

“Preview already running.”

n/a

“Preview already running.”

worker freed on finished

yes

no — deleteLater

n/a

no — deleteLater

announces its result

ready

ready

NO — none

ready

work runs off the GUI thread

QThread

QThread

JobRunner

QThread

what a knob change costs

nothing — explicit run

a re-link, cached masks

a re-crop, superseded by token

a rescore from the cache

where the picture goes

twin zoom canvases

twin zoom canvases

crop grid

matplotlib plot

The last three rows are differences of substance — a crop grid is not a pair of zoom canvases, and a preview that re-links from a cache should not be made to demand a button press for the sake of symmetry. They stay. The rows above them are the ones that had no reason to differ, and every one of them is now a single implementation.

Three of those rows were bugs rather than differences of taste. Measure returned from its re-crop without a word when no array was loaded, so the button did nothing and said nothing. Timelapse and Motility wired QThread.finished to worker.deleteLater and dropped their own reference in the result slot — the pattern spacr.qt.bridge.make_thread() documents as a race, and the one the Mask panel had already been fixed away from — which leaves a running QThread owned by nobody when the user closes the screen mid-pass. And Measure never told anyone its pass had landed, so no screen could react to a crop preview the way it reacts to the other three.

LivePreviewContract is what the four panels now inherit. It owns the vocabulary (PREVIEW_RUN_TEXT and the message constants), the run guard, the cancellation token and the busy/idle button state, so a change to any of them is one change rather than four.

The words

A live view is a panel that re-renders the module’s own output from the current settings, on real input, before a run. That is Mask, Timelapse, Measure and Motility, and it is what the Live toggle opens. The Image UMAP explorer is not one: it makes an already-computed embedding clickable, and no setting changes what it draws. Its toggle therefore says Interactive, not Live — the same word had been on both.

Cancel and Stop are also two words on purpose. A live view’s Cancel throws the pass away: nothing it produced is kept, because a half -segmented field is not a result. A hyperparameter search’s Stop (spacr.qt.screens.hyperparam.HyperparamPanel) finishes the trial in flight and keeps every trial already scored, marking the result partial. Same button position, deliberately different verbs, because a user who presses one expecting the other loses either nothing or hours.

The search panel’s progress is already reported the way this contract wants a live view’s to be — on_trial is called with idx + 1 after a trial has been scored, so “12 of 40 configurations evaluated” counts work done rather than work started.

Cellpose

preview_cellpose_model() is the other half of the sharing. model_type= is accepted and IGNORED by Cellpose 4, so a preview that passed it silently segmented with cpsam — including for a user who had just trained their own checkpoint in spaCR’s Train Cellpose module. That defect was written twice, in two preview files, and fixed twice. One constructor here means the next Cellpose API change is one fix.

Classes

LivePreviewContract

What every live-view panel promises, implemented once.

Functions

preview_cellpose_model(model_name[, gpu])

Build the Cellpose model a live view segments with.

preview_failure_message(→ str)

Phrase one failed preview pass the same way in every module.

Module Contents

class spacr.qt.widgets.preview_contract.LivePreviewContract[source]

What every live-view panel promises, implemented once.

A panel mixes this in beside QWidget and supplies three things: a _status label, a _run_btn button, and _preview_blocked_reason(). It gets the run guard, the cancel token, the busy/idle button state and the shared wording for free.

The token is the cancellation mechanism. Neither Cellpose nor a numpy read exposes an interrupt, so a cancelled pass is left to run itself out and its answer is dropped on arrival: preview_token() is captured when a pass starts and compared with preview_stale() when it lands.

begin_preview() → bool[source]

The shared guard at the top of every run_preview.

Refuses out loud — a live view that declines in silence is the defect this contract exists to remove — and otherwise marks the panel busy and hands back a fresh token via preview_token().

Returns:

True when the caller should start a pass.

can_preview() → bool[source]

True when pressing run would start a pass.

cancel_preview() → bool[source]

Abandon the pass in flight, if there is one.

The work is not killed — neither Cellpose nor a numpy read can be interrupted — the token is bumped so its answer lands as a no-op, and the panel is returned to the idle state at once.

Returns:

True when a running pass was abandoned.

display_primaries() → str[source]

Which primaries this view draws channels in.

Read from the GLOBAL preference, never from a control on one panel. A user who needs the substitution needs it in every view and every session; a per-screen toggle is one they have to re-find, and the screen they forget to set is the one that misleads them.

A view MAY override this – a figure being prepared for publication wants cmy whatever the author’s vision – but every view starts here.

Returns:

one of spacr.crops.DISPLAY_PRIMARIES.

display_primaries_note() → str[source]

One sentence naming the mapping, or "" for plain RGB.

preview_blocked_reason() → str[source]

Say why the preview cannot run, or "" when it can.

Never raises: a panel that fails while explaining itself would take the status line down with it.

preview_running() → bool[source]

True while a preview pass is in flight.

preview_stale(token: Any) → bool[source]

True when a result carrying token has been superseded.

Parameters:

token – the preview generation a result was started under, as preview_token() returned it; None or a value that is not an integer is never stale.

preview_status() → str[source]

The panel’s current status line.

preview_token() → int[source]

The generation of the pass now current.

set_preview_busy(busy: bool) → None[source]

Enable exactly one of run / cancel.

Parameters:

busy – true while a preview pass runs (cancel enabled, run disabled), false otherwise; a panel without either button skips it.

set_preview_status(text: Any) → None[source]

Put one sentence on the panel’s status line.

The display-primaries note is appended here rather than at each call site, so no panel and no code path can recolour an image without saying so. When the preference is off – which it is for almost everybody – this changes nothing at all.

Parameters:

text – the sentence to show; converted to str and translated. A panel with no status label ignores it.

spacr.qt.widgets.preview_contract.preview_cellpose_model(model_name: Any, gpu: bool | None = None)[source]

Build the Cellpose model a live view segments with.

model_type= is accepted-and-IGNORED by Cellpose 4 — it logs “not used in v4.0.1+” and drops it, leaving pretrained_model at its cpsam default. Passing the user’s choice there therefore segmented with cpsam whatever they picked, including a checkpoint they had just trained in spaCR’s own Train Cellpose module. spacr.utils._resolve_cellpose_pretrained() is what the pipeline uses: it maps the legacy pre-SAM names onto cpsam and says so once, and returns a path unchanged when the name is a fine-tuned checkpoint.

Cellpose and torch are imported inside the call, so importing a preview module cold — as the test suite does — needs no CUDA-capable stack.

CPU previews set use_bfloat16=False, including an explicit gpu=False choice and the CPU fallback when accelerator detection fails. A Cellpose 3 checkpoint rejected by Cellpose 4 raises a preview-specific compatibility message while retaining the original exception as its cause.

Parameters:
  • model_name – the model name or checkpoint path the user picked.

  • gpu – force the device choice; None asks torch.

Returns:

a cellpose.models.CellposeModel.

spacr.qt.widgets.preview_contract.preview_failure_message(error: Any) → str[source]

Phrase one failed preview pass the same way in every module.

Parameters:

error – the exception, or the error string a worker emitted.

Returns:

the sentence for the panel’s status line.