spacr.doctor¶
spacr-doctor — diagnose a spaCR installation and say how to fix it.
One command, one line per check, and for every line that is not PASS a
concrete command the user can copy and run. A diagnostic that says
“GPU not available” without saying what to do about it is not a diagnostic,
so Result makes fix a first-class field rather than an optional
afterthought, and format_report() always prints it.
The checks exist because these failures actually happened:
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. Editing checkout A while
import spacrresolves to checkout B costs hours before anyone thinks to printspacr.__file__.check_running_checkout()is the single most valuable function in this module.A broken GUI dependency install. PySide6 ships with the core package, but a missing or incomplete wheel can still make the
spacrcommand fail on import.spacr.qthas a friendly path for that;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
CellposeModelcall, 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, which
spacr.validate.validate_settings()already knows how to name.
Design rules, all of them load-bearing:
Every check is an independent module-level function taking a
Contextand returning aResult(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 anERRORrow rather than taking down the report. A diagnostic tool that crashes while diagnosing is worse than no diagnostic tool.Nothing heavy is imported at module scope.
spacr-doctor --helpmust not pay for torch.
Classes¶
Functions¶
|
Return the |
|
The installed Cellpose is the 4.x / SAM API spaCR calls. |
|
The |
|
Exactly one metadata directory claims to be spaCR. |
|
Every installed |
|
Every core dependency imports. |
|
A project database is readable, intact, unlocked, and the right schema. |
|
A checkout's environment.yaml does not contradict its setup.py. |
|
A GUI can actually open a window here. |
|
Exactly one importable |
|
CUDA is not merely reported as present but is actually usable. |
|
Report the accelerator choice recorded by the desktop installer. |
|
No optional extra is half-installed. |
|
The running interpreter is one spaCR supports. |
|
The GUI dependencies import in this interpreter. |
|
The checkout you are standing in is the one that actually runs. |
|
A settings file names a combination that can actually run. |
|
|
|
torch imports, and says whether it was built with CUDA at all. |
|
|
|
Render the rows as the text the command prints. |
|
Run every check, print the report, and return a shell exit code. |
|
Run the selected checks and return their rows. |
|
Count rows by verdict, always returning every key. |
Module Contents¶
- class spacr.doctor.Context[source]¶
Everything the checks are allowed to know about the invocation.
- Parameters:
checkout – the directory the user believes they are editing. Defaults to the current working directory, which is the whole point: “am I running the code I am looking at” is a question about here.
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 prove it really works. A driver/runtime mismatch is invisible 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 actually carry out. Required in spirit for every non-
PASSrow;format_report()prints it verbatim, including newlines.details – supporting facts worth showing but not worth a verdict.
- spacr.doctor.build_parser() argparse.ArgumentParser[source]¶
Return the
spacr-doctorargument parser.
- spacr.doctor.check_cellpose(ctx: Context) Result[source]¶
The installed Cellpose is 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_SPECIFIERwhen that cannot be read, and the verdict does not rest on the version string alone: the API itself is probed formodels.CellposeModel, for the absence of the 3.xmodels.Cellposewrapper, and forcpsaminMODEL_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]¶
The
spacryou type belongs to the Python you are running.A second environment earlier on
PATHis 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
PATHandsys.prefixas 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 outsidesys.prefixis a FAIL; nospacrcommand anywhere onPATHis only a WARN, sincepython -m spacr.doctorstill works.
- spacr.doctor.check_conflicting_distributions(ctx: Context) Result[source]¶
Exactly one metadata directory claims to be spaCR.
Two shapes of the same problem.
spacrandspacr-nightlyinstalled together share one package directory and overwrite each other’s files. A leftoverspacr.egg-infoinside a checkout shadows the real install whenever the checkout is onsys.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.pathis enumerated, so — likecheck_duplicate_installs()— the row can change with the directory the process was launched from and not withctx.checkout. Two distinct distribution names (SPACR_DISTRIBUTION_NAMES) are a FAIL; two copies of the same name, typically a stalespacr.egg-info, are only a WARN.
- spacr.doctor.check_console_scripts(ctx: Context) Result[source]¶
Every installed
spacr-*command points at a module that exists.sim=spacr.app_sim:gui_simoutlived the file it named, so the installedsimcommand 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 ofmodule:functionis 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]¶
Every core dependency imports.
- Parameters:
ctx – not read. The list is the fixed
CORE_MODULEStable, 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]¶
A project database is readable, intact, unlocked, and the right schema.
- Parameters:
ctx – only
ctx.dbis 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]¶
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.yamlis 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.checkoutis read, and only to walk up to the enclosing checkout root the waycheck_running_checkout()does. Everything after that is read from files on disk —setup.pyandenvironment.yamlin 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. Pointctx.checkoutat another clone to cross-check that clone instead of the running one.
- spacr.doctor.check_display(ctx: Context) Result[source]¶
A GUI can actually open a window here.
- Parameters:
ctx – not read. The inputs are
sys.platformand this process’sQT_QPA_PLATFORM,DISPLAYandWAYLAND_DISPLAYenvironment 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 withQT_QPA_PLATFORMset 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]¶
Exactly one importable
spacrpackage directory exists.- Parameters:
ctx – not read, and deliberately so: the candidates come from the import machinery and
sys.pathof the running process, which is what an actualimport spacrconsults. That makes the row depend on the directory this process was launched from — an emptysys.pathentry means the current directory — rather than onctx.checkout.
- spacr.doctor.check_gpu(ctx: Context) Result[source]¶
CUDA is not merely reported as present but is actually usable.
- Parameters:
ctx –
probe_gpu=Truepermits 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]¶
No optional extra is half-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 fromcheck_core_dependencies().
- spacr.doctor.check_python(ctx: Context) Result[source]¶
The running interpreter is one spaCR supports.
- Parameters:
ctx – not read. The verdict comes from
sys.version_infoand theRequires-Pythonfield of the installed metadata, falling back toFALLBACK_REQUIRES_PYTHONwhen spaCR is not installed at all. Accepted only so every check shares one signature, so anyContext— including a default-constructed one — gives the same row.
- spacr.doctor.check_qt_extra(ctx: Context) Result[source]¶
The GUI dependencies import in this interpreter.
Reuses
spacr.qt’s own diagnosis —_missing_qt_extraand_QT_MISSING_MESSAGE— rather than restating which distributions the GUI needs. The legacyqt extrareport 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]¶
The checkout you are standing in is the one that actually runs.
The failure this exists for: two clones of spaCR on one machine, an editable install pointing at the first, and a developer editing the second. Every test passes, every edit does nothing, and nothing in the output says so.
- Parameters:
ctx – only
ctx.checkoutis read, and it need not be the checkout root: the check walks up from it for the first directory holding bothspacr/__init__.pyand 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 whatimport spacractually resolves to, and passes if there is nothing to contradict.
- spacr.doctor.check_settings(ctx: Context) Result | List[Result][source]¶
A settings file names a combination that can actually run.
Delegates to
spacr.validate.validate_settings(), which already knows every combination this project has seen fail —normalize=Truewithmeasure, acrop_modenaming an object with no mask dimension, a mask run with all four object channels unset.- Parameters:
ctx –
ctx.settingsandctx.appare both read, and both are needed.ctx.settingsis a.csvor.jsonsettings file;Noneis a SKIP and a path that is not a file is a FAIL.ctx.appselects 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 thatspacr.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]¶
import spacrworks, and reports where from.- Parameters:
ctx – not read. The row describes the interpreter this process is running in — it imports
spacrand reports__file__and__version__— so it answers “does spaCR import” and never “is it the copy inctx.checkout”; that second question belongs tocheck_running_checkout().
- spacr.doctor.check_torch(ctx: Context) Result[source]¶
torch imports, and says whether it was built with CUDA at all.
- Parameters:
ctx – not read — in particular
ctx.probe_gpubelongs tocheck_gpu(), not here. This row only imports torch and reportstorch.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]¶
0when the installation is healthy,1otherwise.- Parameters:
results – diagnostic rows to inspect.
FAILandERRORalways make the result non-zero; other statuses remain healthy unless strict warning handling applies.strict – also fail on
WARN, for CI that wants a clean bill.
- 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()inCHECKSorder. 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.fixis printed only for non-PASSrows, 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;Nonereadssys.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 plainspacr-doctordoes.
- 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
ERRORrow and the run continues: the whole value of this command is the rows it does produce, and losing all of them because one probe hit an unexpected filesystem would be absurd.KeyboardInterruptis the one exception — the user asking to stop is not a diagnostic finding.- 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;
Noneruns every check registered inCHECKS.
- 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 whenresultsis empty, so callers can index them withoutget; a row carrying some other status string adds a sixth key rather than being dropped.
Nested helpers¶
- _importable_spacr_dirs.add(path: Path) None¶
Resolve and append
pathonce, ignoring unresolvable paths.spacr/doctor.py:457
- _register.decorate(function: Callable) Callable¶
Label and register
function, then return it unchanged.spacr/doctor.py:146
- check_gpu._result(status, message, *, fix='', details=())¶
Append shared task evidence without changing the GPU diagnosis.
spacr/doctor.py:1293