spacr.qt.screens.batch

Batch Runner — the Tools module that spends the night for you.

The Plate Queue chains plates through one pipeline. This screen is the other axis: arbitrary (module, settings) jobs in any order — Mask → Measure → Classify (CV) → Classify (ML), then the same four again with a different diameter, then a fifth plate’s Mask — built, validated, saved to a file and run unattended.

The screen is deliberately thin. Everything it knows about a queue it learns from spacr.batch, which is headless, torch-free and tested without Qt. This file is only the part that has to be a GUI: pick a module and a settings file, duplicate a job and edit it, reorder, validate, save/load, run, and watch per-job status, progress and log.

Three decisions are visible to the user:

  • Jobs are validated when they are added, not when they run. A job whose settings file is missing, whose src is misspelled or whose module is GUI-only is refused at the Add button, with the reason inline. Finding that out at 3 a.m. from job 9 of 12 is what this whole module exists to prevent.

  • No modal dialogs, ever. Every failure lands in the inline problems pane and in BatchScreen.last_error. A QMessageBox hangs a headless run (it did, in MakeMasksScreen), and this screen is exercised headlessly.

  • The run happens off the GUI thread, and its completion handler comes back onto it. PipelineWorker.finished is emitted in the worker thread; every widget-mutating receiver therefore uses an explicit queued connection to a bound method of this GUI-thread widget.

Jobs run one at a time — they compete for one GPU — and each one is its own spacr-run process, so a segfault in cellpose kills that job rather than this window.

Classes

BatchScreen

Build a queue of module+settings jobs, validate it, and run it overnight.

Module Contents

class spacr.qt.screens.batch.BatchScreen(parent=None, threaded: bool = True, runner: Callable[[Any, str, str], int] | None = None)[source]

Bases: PySide6.QtWidgets.QWidget

Build a queue of module+settings jobs, validate it, and run it overnight.

Parameters:
  • parent – parent widget.

  • threaded – run the queue on a worker thread (the default). Tests pass False for deterministic, synchronous behaviour.

  • runner – (job, settings_path, log_path) -> exit_code, forwarded to spacr.batch.run_queue(). Defaults to spacr.batch.subprocess_runner() — one child process per job. Tests inject a fake so nothing real is ever segmented.

Variables:
  • last_error – text of the most recent failure, "" when the last operation succeeded. Errors are only ever reported here and in the inline panes — never in a modal dialog.

  • settled_thread – the thread the completion handler last ran on. Exists so a test can prove it was the GUI thread.

Build the screen, arm its drop zone and start the elapsed-time tick.

Parameters:
  • parent – parent widget, or None.

  • threaded – run the queue on a worker thread. Set False in tests so run finishes before it returns.

  • runner – callable invoked per job as (job, settings, log_path) returning an exit code; None uses the module’s own runner.

active_jobs() → int[source]

How many worker threads are still winding down.

add_job(module: str = '', settings: str = '', label: str = '', depends_on: Sequence[str] = (), overrides: Sequence[str] = ()) → bool[source]

Add one job, validating it now. Problems land inline, never modally.

Parameters:
  • module – module key; the combo’s current value when empty.

  • settings – settings file path; the settings box when empty.

  • label – display label; derived from the module and src when empty.

  • depends_on – ids of jobs that must succeed first.

  • overrides – key=value strings, exactly like --set.

Returns:

True when the job was added.

duplicate_selected() → bool[source]

Copy the selected job, never-run, and select the copy.

Building a night’s work means one job and eleven variations of it, so this is the button that actually gets used.

has_errors() → bool[source]

True when the last validation found something that blocks the run.

is_busy() → bool[source]

True while the queue is running.

load_queue_from(path: str) → bool[source]

Replace the queue with the one in path. Errors land inline.

Parameters:

path – queue JSON file read with spacr.batch.load_queue(); on success it also becomes the file the run keeps up to date.

log_text() → str[source]

The log pane (test/introspection helper).

move_selected(offset: int) → bool[source]

Move the selected job offset places (negative is earlier).

Parameters:

offset – number of queue places to move the selected job; negative moves it earlier, and the new position is clamped to the queue.

problems_text() → str[source]

The inline problems pane (test/introspection helper).

queue() → spacr.batch.Queue[source]

The live spacr.batch.Queue this screen is editing.

queue_path() → str[source]

The queue file this screen saves to and resumes from, or ''.

remove_selected() → bool[source]

Remove the selected job, and any dependency other jobs had on it.

result() → spacr.batch.QueueResult | None[source]

The spacr.batch.QueueResult of the last run, or None.

row_status(row: int) → str[source]

Status text shown in row (test/introspection helper).

Parameters:

row – zero-based table row; an empty string is returned when it has no Status cell.

row_values(row: int) → List[str][source]

Text of every cell in row (test/introspection helper).

Parameters:

row – zero-based table row; a missing cell reads as an empty string.

run() → bool[source]

Validate, then run the queue off the GUI thread.

Returns:

True when the run started (or, unthreaded, completed).

save_queue_to(path: str) → bool[source]

Write the queue to path atomically. Errors land inline.

Parameters:

path – destination queue JSON file, written by spacr.batch.save_queue(); on success it also becomes the file the run keeps up to date.

select_job(job_id: str) → bool[source]

Select the row for job_id.

Parameters:

job_id – identifier of the queued job; False is returned when no row shows it.

selected_job() → spacr.batch.Job | None[source]

The spacr.batch.Job for the selected row, or None.

By the job’s id, never by the row number: the table sorts, so the third row is not the third job once the user has clicked a header.

set_runner(runner: Callable[[Any, str, str], int] | None) → None[source]

Replace the per-job runner. None restores the subprocess default.

Parameters:

runner – callable runner(job, settings_path, log_path) returning the job’s exit code, passed to spacr.batch.run_queue() for every job; None lets run_queue() use its subprocess default.

status_text() → str[source]

Current inline status message (test/introspection helper).

stop() → bool[source]

Ask the queue to stop after the job that is currently running.

Killing a job mid-write would leave exactly the half-written artifact the skip rules exist to avoid, so the running job is always allowed to finish.

validate_now(quiet: bool = False) → List[spacr.batch.Problem][source]

Validate the whole queue and show every problem inline at once.

Parameters:

quiet – do not overwrite the status line when the queue is clean.

Returns:

the problems, errors and warnings mixed.