spacr.doctor

spacr-doctor — check a spaCR installation and print a fix for each problem found.

One command, one line per check, and for every line that is not PASS a concrete command the user can copy and run. Result carries fix as a field of its own, and format_report() prints it for every non-PASS row.

The checks cover failures that have occurred in practice:

  • A stale editable install. This repository is checked out more than once on the same machine, and an editable install points at exactly one of them. Edits to checkout A have no effect while import spacr resolves to checkout B, and nothing reports it until spacr.__file__ is printed. check_running_checkout() detects this case.

  • A broken GUI dependency install. PySide6 ships with the core package, but a missing or incomplete wheel can still make the spacr command fail on import. spacr.qt reports that case with an install message; check_qt_extra() reuses its logic so the two cannot drift.

  • A GPU that is present but unusable. A CPU-only torch build on a machine with an NVIDIA card, or a driver older than the CUDA runtime torch was built against, both present as “cuda not available” and have entirely different fixes.

  • Cellpose version drift. spaCR migrated to the Cellpose 4.x / SAM API. Cellpose 3 lingering in an environment breaks at the first CellposeModel call, deep inside a run that has already spent an hour on masks.

  • A project database that is corrupt, locked, or from a newer spaCR.

  • Settings whose combination cannot work, as reported by spacr.validate.validate_settings().

Design rules:

  • Every check is an independent module-level function taking a Context and returning a Result (or a sequence of them), so each one is callable and assertable on its own.

  • run_checks() wraps every call, so a check that raises becomes an ERROR row rather than ending the report.

  • Nothing heavy is imported at module scope, so spacr-doctor --help does not import torch.

Attributes

WGPU_MACHINES

The CPUs setup.py installs wgpu on by default.

WGPU_MIN_PYTHON

The first Python version setup.py installs wgpu on by default.

Classes

Context

Invocation inputs passed to every check.

Result

One check's verdict.

Functions

build_parser(→ argparse.ArgumentParser)

Return the spacr-doctor argument parser.

check_3d_backends(→ Result)

Report what can draw the True 3D view: wgpu, OpenGL and the CPU.

check_cellpose(→ Result)

Check that the installed Cellpose provides the 4.x / SAM API spaCR calls.

check_command_on_path(→ Result)

Check that the spacr command on PATH belongs to the running Python environment.

check_conflicting_distributions(→ Result)

Check that exactly one installed metadata directory claims to be spaCR.

check_console_scripts(→ Result)

Check that every installed spacr-* command points at a module that exists.

check_core_dependencies(→ Result)

Check that every core dependency imports.

check_database(→ Union[Result, List[Result]])

Check that a project database is readable, intact, unlocked and of a supported schema.

check_declared_pins(→ Result)

Check that a checkout's environment.yaml does not contradict its setup.py.

check_display(→ Result)

Check whether the environment provides a display for the GUI.

check_duplicate_installs(→ Result)

Check that exactly one importable spacr package directory exists.

check_gpu(→ Result)

Check that CUDA is usable, not only reported as available.

check_installer_backend(→ Result)

Report the accelerator choice recorded by the desktop installer.

check_optional_extras(→ Result)

Check that no optional extra is partly installed.

check_python(→ Result)

Check that the running Python version is one spaCR supports.

check_qt_extra(→ Result)

Check that the GUI dependencies import in this interpreter.

check_running_checkout(→ Result)

Check that the checkout containing ctx.checkout is the one import spacr loads.

check_settings(→ Union[Result, List[Result]])

Check that a settings file describes a combination of settings that can run.

check_spacr_package(→ Result)

Check that import spacr works and report its path and version.

check_torch(→ Result)

Check that torch imports and report whether it was built with CUDA.

exit_code(→ int)

0 when the installation is healthy, 1 otherwise.

format_report(→ str)

Render the rows as the text the command prints.

main(→ int)

Run every check, print the report, and return a shell exit code.

run_checks(→ List[Result])

Run the selected checks and return their rows.

summarize(→ Dict[str, int])

Count rows by verdict, always returning every key.

Module Contents

class spacr.doctor.Context[source]

Invocation inputs passed to every check.

Parameters:
  • checkout – the directory the user believes they are editing. Defaults to the current working directory, because the checkout check compares the code that runs with the code in that directory.

  • db – optional project database to inspect.

  • settings – optional settings file (csv or json) to validate.

  • app – app key the settings file is for (mask, measure, …).

  • probe_gpu – allocate a tensor on the GPU to confirm it can be used. A driver/runtime mismatch is not detected until something is allocated.

class spacr.doctor.Result[source]

One check’s verdict.

Parameters:
  • check – short label shown in the left column of the report.

  • status – one of PASS, WARN, FAIL, ERROR, SKIP.

  • message – what was found, in the user’s terms.

  • fix – a command or action the user can carry out. Expected for every non-PASS row; format_report() prints it verbatim, including newlines.

  • details – supporting facts worth showing but not worth a verdict.

property is_failure: bool[source]

True when this row should make the command exit non-zero.

spacr.doctor.build_parser() → argparse.ArgumentParser[source]

Return the spacr-doctor argument parser.

spacr.doctor.check_3d_backends(ctx: Context) → Result[source]

Report what can draw the True 3D view: wgpu, OpenGL and the CPU.

wgpu (Vulkan, Metal, Direct3D 12) is a default dependency, but a computer without it is a supported install: the view then draws with OpenGL (VisPy) or the CPU. A missing wgpu is therefore a PASS; only a wgpu that is installed and cannot be imported is a WARN.

Parameters:

ctx – probe_gpu=True lets wgpu list its adapters, which loads the graphics drivers but draws nothing; it is skipped when SPACR_NO_GPU_RENDER is set or CUDA_VISIBLE_DEVICES is empty, as the True 3D view skips the GPU then.

spacr.doctor.check_cellpose(ctx: Context) → Result[source]

Check that the installed Cellpose provides the 4.x / SAM API spaCR calls.

Parameters:

ctx – not read. The version bound comes from spaCR’s own installed metadata, falling back to FALLBACK_CELLPOSE_SPECIFIER when that cannot be read, and the verdict does not rest on the version string alone: the API itself is probed for models.CellposeModel, for the absence of the 3.x models.Cellpose wrapper, and for cpsam in MODEL_NAMES. A version string too odd to compare downgrades a finding to WARN rather than guessing.

spacr.doctor.check_command_on_path(ctx: Context) → Result[source]

Check that the spacr command on PATH belongs to the running Python environment.

A second environment earlier on PATH is the other half of “which spacr am I actually running”: the import can be right while the command is not.

Parameters:

ctx – not read. The comparison is between PATH and sys.prefix as this process sees them, so the row describes the shell that launched it: activate a different environment and the answer legitimately changes. A command found outside sys.prefix is a FAIL; no spacr command anywhere on PATH is only a WARN, since python -m spacr.doctor still works.

spacr.doctor.check_conflicting_distributions(ctx: Context) → Result[source]

Check that exactly one installed metadata directory claims to be spaCR.

Two shapes of the same problem. spacr and spacr-nightly installed together share one package directory and overwrite each other’s files. A leftover spacr.egg-info inside a checkout shadows the real install whenever the checkout is on sys.path, so version, dependency list and console-script table are all read from stale metadata.

Parameters:

ctx – not read. Every installed metadata directory on sys.path is enumerated, so — like check_duplicate_installs() — the row can change with the directory the process was launched from and not with ctx.checkout. Two distinct distribution names (SPACR_DISTRIBUTION_NAMES) are a FAIL; two copies of the same name, typically a stale spacr.egg-info, are only a WARN.

spacr.doctor.check_console_scripts(ctx: Context) → Result[source]

Check that every installed spacr-* command points at a module that exists.

sim=spacr.app_sim:gui_sim outlived the file it named, so the installed sim command died with ImportError. A partially upgraded install reproduces that for any command.

Parameters:

ctx – not read. The entry points come from the installed distribution’s metadata rather than from ctx.checkout’s setup.py, because it is the installed table that pip turned into scripts on PATH. Only the module half of module:function is resolved, so a command whose target function was renamed still passes here and fails when run.

spacr.doctor.check_core_dependencies(ctx: Context) → Result[source]

Check that every core dependency imports.

Parameters:

ctx – not read. The list is the fixed CORE_MODULES table, and each entry is genuinely imported rather than merely looked up in the installed metadata — a distribution present but broken (a torch whose shared libraries will not load, say) has to fail here rather than at hour one of a run. That makes this the slowest of the dependency checks.

spacr.doctor.check_database(ctx: Context) → Result | List[Result][source]

Check that a project database is readable, intact, unlocked and of a supported schema.

Parameters:

ctx – only ctx.db is read. None — the usual case — makes the whole check one SKIP row rather than a failure, since most invocations have no project to point at. It must name the SQLite file itself, which spaCR writes to <src>/measurements/measurements.db, not the plate directory; a path that is not a file is a FAIL. The integrity and schema rows open it read-only, but the locking row briefly takes a real write lock, so pointing this at a database a run or an open GUI is currently writing to reports that lock — which is the intended answer, not a false alarm.

spacr.doctor.check_declared_pins(ctx: Context) → Result[source]

Check that a checkout’s environment.yaml does not contradict its setup.py.

Only meaningful inside a source checkout, and it is there that it matters: conda env create -f environment.yaml is how a new user builds an environment, so a pin in that file that setup.py forbids produces an install that is broken before anyone runs anything.

Parameters:

ctx – only ctx.checkout is read, and only to walk up to the enclosing checkout root the way check_running_checkout() does. Everything after that is read from files on disk — setup.py and environment.yaml in that root — never from the installed environment, so this row says nothing about a wheel install and is a SKIP whenever the pair is absent. Point ctx.checkout at another clone to cross-check that clone instead of the running one.

spacr.doctor.check_display(ctx: Context) → Result[source]

Check whether the environment provides a display for the GUI.

Parameters:

ctx – not read. The inputs are sys.platform and this process’s QT_QPA_PLATFORM, DISPLAY and WAYLAND_DISPLAY environment variables, so the row follows the environment rather than anything the caller passes. Only Linux is examined; every other platform passes outright, as does Linux with QT_QPA_PLATFORM set to a deliberately headless value. A missing display is a WARN, never a FAIL, because the pipelines run headless.

spacr.doctor.check_duplicate_installs(ctx: Context) → Result[source]

Check that exactly one importable spacr package directory exists.

Parameters:

ctx – not read, and deliberately so: the candidates come from the import machinery and sys.path of the running process, which is what an actual import spacr consults. That makes the row depend on the directory this process was launched from — an empty sys.path entry means the current directory — rather than on ctx.checkout.

spacr.doctor.check_gpu(ctx: Context) → Result[source]

Check that CUDA is usable, not only reported as available.

Parameters:

ctx – probe_gpu=True permits explicit CUDA initialization and an 8x8 tensor allocation/multiplication probe. False reports metadata without initialization, device-name or dtype-allocation probes; it cannot prove that a reported device would accept a tensor. Explicitly hidden CUDA devices are reported as skipped, while other backends remain eligible. A forced CPU selection is also reported as skipped.

spacr.doctor.check_installer_backend(ctx: Context) → Result[source]

Report the accelerator choice recorded by the desktop installer.

spacr.doctor.check_optional_extras(ctx: Context) → Result[source]

Check that no optional extra is partly installed.

A missing extra is fine and expected. An extra with some of its distributions present and others not is a resolve that went wrong, and it fails at the moment the feature is used rather than at install time.

Parameters:

ctx – not read; the extras and the distributions in each come from OPTIONAL_EXTRAS. Presence is decided from installed metadata rather than by importing, which keeps the check cheap but means an extra that is installed and broken still counts as present — the opposite trade-off from check_core_dependencies().

spacr.doctor.check_python(ctx: Context) → Result[source]

Check that the running Python version is one spaCR supports.

Parameters:

ctx – not read. The verdict comes from sys.version_info and the Requires-Python field of the installed metadata, falling back to FALLBACK_REQUIRES_PYTHON when spaCR is not installed at all. Accepted only so every check shares one signature, so any Context — including a default-constructed one — gives the same row.

spacr.doctor.check_qt_extra(ctx: Context) → Result[source]

Check that the GUI dependencies import in this interpreter.

Reuses spacr.qt’s own diagnosis — _missing_qt_extra and _QT_MISSING_MESSAGE — rather than restating which distributions the GUI needs. The legacy qt extra report key stays stable for consumers.

Parameters:

ctx – not read. The check imports the real GUI entry point, which means it is the one check that pays for importing PySide6, and it reports what that import did in this interpreter. It says nothing about whether a window can be opened — see check_display() for that half.

spacr.doctor.check_running_checkout(ctx: Context) → Result[source]

Check that the checkout containing ctx.checkout is the one import spacr loads.

This detects two clones of spaCR on one machine with an editable install pointing at the first while the second is being edited: tests pass and edits have no effect, and no other output reports it.

Parameters:

ctx – only ctx.checkout is read, and it need not be the checkout root: the check walks up from it for the first directory holding both spacr/__init__.py and a setup.py or pyproject.toml, so any subdirectory of a clone works. When no such directory is found above it — the usual case when spaCR is installed from a wheel — the row falls back to comparing the editable-install target recorded by pip with what import spacr actually resolves to, and passes if there is nothing to contradict.

spacr.doctor.check_settings(ctx: Context) → Result | List[Result][source]

Check that a settings file describes a combination of settings that can run.

Delegates to spacr.validate.validate_settings(), which checks the known invalid combinations — normalize=True with measure, a crop_mode naming an object with no mask dimension, a mask run with all four object channels unset.

Parameters:

ctx – ctx.settings and ctx.app are both read, and both are needed. ctx.settings is a .csv or .json settings file; None is a SKIP and a path that is not a file is a FAIL. ctx.app selects which pipeline’s rules apply (mask, measure, classify, …); an empty string is a WARN instead of a guess, because the same file is valid for one app and invalid for another, and an app name that spacr.validate.validate_settings() does not recognise runs only the generic checks. Unlike the other checks this one can return many rows — one per problem found.

spacr.doctor.check_spacr_package(ctx: Context) → Result[source]

Check that import spacr works and report its path and version.

Parameters:

ctx – not read. The row describes the interpreter this process is running in — it imports spacr and reports __file__ and __version__ — so it answers “does spaCR import” and never “is it the copy in ctx.checkout”; that second question belongs to check_running_checkout().

spacr.doctor.check_torch(ctx: Context) → Result[source]

Check that torch imports and report whether it was built with CUDA.

Parameters:

ctx – not read — in particular ctx.probe_gpu belongs to check_gpu(), not here. This row only imports torch and reports torch.version.cuda, and a CPU-only build still passes: whether that build is wrong for this machine is a question about the driver, which is why it is answered one row later.

spacr.doctor.exit_code(results: Iterable[Result], strict: bool = False) → int[source]

0 when the installation is healthy, 1 otherwise.

Parameters:
  • results – diagnostic rows to inspect. FAIL and ERROR always make the result non-zero; other statuses remain healthy unless strict warning handling applies.

  • strict – also fail on WARN, for CI that requires no warnings.

spacr.doctor.format_report(results: Sequence[Result]) → str[source]

Render the rows as the text the command prints.

Parameters:

results – rows in the order they should appear — nothing here sorts or groups them, so the printed order is whatever produced the sequence, normally run_checks() in CHECKS order. It must be a real sequence and not a generator: it is traversed three times, once to size the check column to the widest label in this sequence, once for the lines, and once for the trailing counts. fix is printed only for non-PASS rows, so a fix attached to a passing row never reaches the report.

spacr.doctor.main(argv: Sequence[str] | None = None) → int[source]

Run every check, print the report, and return a shell exit code.

Parameters:

argv – arguments without the program name, as argparse.ArgumentParser.parse_args() takes them; None reads sys.argv[1:]. Anything argparse rejects — including --help — exits the process rather than returning, which is the one way a caller embedding this does not get its exit code back. Passing [] runs the full check list against the current working directory with no database and no settings file, which is what plain spacr-doctor does.

spacr.doctor.run_checks(ctx: Context, checks: Sequence[Callable[[Context], Any]] | None = None) → List[Result][source]

Run the selected checks and return their rows.

A check that raises becomes an ERROR row and the run continues, so one failing probe does not discard the rows of the other checks. KeyboardInterrupt is re-raised and stops the run.

Parameters:
  • ctx – invocation context passed unchanged to every selected check, including the checkout, optional project inputs and GPU-probe choice.

  • checks – the checks to run, in order; None runs every check registered in CHECKS.

spacr.doctor.summarize(results: Iterable[Result]) → Dict[str, int][source]

Count rows by verdict, always returning every key.

Parameters:

results – rows to tally, iterated exactly once, so a generator is safe here (it is not in format_report()). The five standard verdicts are always present in the result even when results is empty, so callers can index them without get; a row carrying some other status string adds a sixth key rather than being dropped.

spacr.doctor.WGPU_MACHINES = ('x86_64', 'AMD64', 'aarch64', 'arm64', 'ARM64')[source]

The CPUs setup.py installs wgpu on by default.

spacr.doctor.WGPU_MIN_PYTHON = (3, 10)[source]

The first Python version setup.py installs wgpu on by default.

Nested helpers

_importable_spacr_dirs.add(path: Path) → None

Resolve and append path once, ignoring unresolvable paths.

spacr/doctor.py:457

_register.decorate(function: Callable) → Callable

Label and register function, then return it unchanged.

spacr/doctor.py:145

check_gpu._result(status, message, *, fix='', details=())

Append shared task evidence without changing the GPU diagnosis.

spacr/doctor.py:1355