spacr.cli¶
spacr-run — run any spaCR module from a settings file, with no GUI.
Every spaCR pipeline has, until now, been reachable only through a GUI: the
PySide6 app (spacr / spacr-qt) or the classic Tk app (spacr-tk).
That is fine on a workstation and impossible on a cluster — importing either
entry point pulls Qt or Tk, and a compute node has no display to give them.
This module is the headless path. It is deliberately, testably light: importing
spacr.cli must not pull Qt, Tk, torch, cellpose, numpy or pandas. Everything
heavy is imported inside the command that needs it, so spacr-run --help and
spacr-run --list answer instantly on a login node, and --dry-run
validates a 40-plate settings file without touching a GPU.
Usage:
spacr-run <module> --settings settings.csv [--set key=value ...] [--dry-run]
spacr-run --list # every module that can run headless
spacr-run --describe measure # what it does, what it needs, what it writes
spacr-run validate --settings f --module mask # pre-flight only
The settings file is the one the GUI writes. Both spaCR CSV layouts are read —
Key,Value (what spacr.utils.save_settings() emits next to every run)
and setting_key,setting_value (the documented default of
spacr.utils.load_settings()) — plus the settings.json written into
each run-journal folder. So the round trip is: click through the GUI once on a
laptop, copy <src>/settings/gen_mask_settings.csv to the cluster, and
sbatch it unchanged.
Exit codes (a cluster job that exits 0 after failing is the classic headless footgun, so spaCR’s own codes are exact):
0 the module ran to completion, or the dry run / validation found no errors 1 the module raised 2 bad arguments, unreadable settings, or pre-flight found errors
A pipeline that raises SystemExit itself is the exception: its integer
code is passed through unchanged, so any value can reach the shell.
Matplotlib is forced to Agg when there is no display, and plt.show is
replaced by a close-the-figure shim for the duration of the run — the same
thing spacr.gui_utils.spacrFigShow() does inside the GUI — so a pipeline
that calls plt.show() neither blocks nor leaks figures.
Exceptions¶
A settings file, an override or a module name the user got wrong. |
Classes¶
One headless-runnable spaCR pipeline. |
Functions¶
|
Apply |
|
Return the |
|
|
|
|
|
|
|
|
|
Coerce a |
|
Import and return the pipeline callable for |
|
Load a settings file written by the GUI, a pipeline run or a run journal. |
|
|
|
Return a fresh defaults dict for |
|
Render the |
|
Render the |
|
Render a resolved settings dict as an aligned, sorted table. |
|
Return the |
|
Build the settings dict the pipeline will actually receive. |
|
Configure timestamped logging to stdout for a batch run. |
|
Force matplotlib's Agg backend when there is no display. |
Module Contents¶
- exception spacr.cli.SettingsError[source]¶
Bases:
ExceptionA settings file, an override or a module name the user got wrong.
Always maps to exit code 2: the run never started, so it is an argument problem rather than a runtime failure.
Initialize self. See help(type(self)) for accurate signature.
- class spacr.cli.Module[source]¶
One headless-runnable spaCR pipeline.
- Parameters:
key – name the user types, matching the GUI’s
app_key.summary – one line for
--list.entry –
"module:function"of the callable that does the work.defaults – name of the
spacr.settingshelper that fills the defaults, orNonewhen the pipeline has none there.validate_key – app key understood by
spacr.validate; empty when that module has no specific rules and only generic checks apply.requires – settings that must be supplied, phrased for a human.
writes – what lands on disk.
call_style –
"settings"forfn(settings_dict);"folder"forfn(settings["src"]). No built-in module uses"folder"; it reaches spaCR only through a plugin app that declares it.note – caveat worth printing in
--describe.defaults_entry –
"module:function"of a defaults helper that does not live inspacr.settings. Six built-in pipelines keep their own (foreign,external_masks,convert,illumination,barcode_qc,anndata_export), as does every plugin app, because their keys are theirs alone; without this the CLI would resolve an empty defaults dict for them, and--setwould then reject every one of their keys as a setting that does not exist.
- property func_name: str[source]¶
Attribute name of the callable inside
module_name.
- spacr.cli.apply_overrides(settings: Dict[str, Any], overrides: Sequence[str], module: Module | None = None) Dict[str, Any][source]¶
Apply
--set key=valueoverrides on top of a settings dict.An override naming a key spaCR does not know is an error, not a no-op: a typo’d override that quietly does nothing costs a whole run to discover, and the run looks like it succeeded.
So is an override naming a key spaCR does know but nothing reads. Those are worse, because they pass every “is this a real setting?” test there is:
remove_border_pathogensis typed, tooltipped and offered by the Pathogen category, andspacr-run mask --set remove_border_pathogens=Truewas accepted in silence and did nothing.spacr.settings.DEAD_SETTINGSnames every such key and the spelling that works instead.- Parameters:
settings – settings resolved from defaults plus file; mutated in place.
overrides – raw
key=valuestrings from the command line.module – the module being run, used only for the error message.
- Returns:
settings.- Raises:
SettingsError – on an unknown key, a key nothing reads, or an uncoercible value.
- spacr.cli.build_parser() argparse.ArgumentParser[source]¶
Return the
spacr-runargument parser.Building the parser imports nothing beyond the standard library, so
spacr-run --helpis instant even on a node with a cold NFS cache.- Returns:
the parser.
- spacr.cli.cmd_list(_args: argparse.Namespace) int[source]¶
--list— print every module that can run headless.
- spacr.cli.cmd_run(args: argparse.Namespace) int[source]¶
<module> --settings f— the real thing, or--dry-runfor the plan.
- spacr.cli.cmd_validate(args: argparse.Namespace) int[source]¶
validate --settings f— pre-flight only, nothing is executed.
- spacr.cli.coerce_value(key: str, text: str, current: Any, expected_types: Mapping[str, Any], app: str = '') Any[source]¶
Coerce a
--set key=valuestring into the type the setting expects.The type comes from
spacr.settings.expected_typeswhen the key is declared there, otherwise from the type of the value the key already holds. A value that cannot be coerced is an error rather than a silently-stored string:cell_mask_dim='4'is exactly the bug the settings CSV round trip keeps producing, and measure_crop only notices it an hour in.- Parameters:
key – settings key.
text – the raw text after the first
=.current – the value
keyholds before the override.expected_types –
spacr.settings.expected_types.app – module key, so a key two pipelines share is read as the module being run means it (see
_APP_TYPE_OVERRIDES).
- Returns:
the coerced value.
- Raises:
SettingsError – when
textis not a legal value forkey.
- spacr.cli.import_entry(module: Module) Callable[..., Any][source]¶
Import and return the pipeline callable for
module.Deliberately late: this is where torch, cellpose and the rest of the heavy stack finally load, long after
--helpand--listhave answered.- Parameters:
module – module whose entry point is wanted.
- Returns:
the callable.
- Raises:
SettingsError – when the module or attribute cannot be imported.
- spacr.cli.load_settings_file(path: Any) Dict[str, Any][source]¶
Load a settings file written by the GUI, a pipeline run or a run journal.
- Parameters:
path – path to a
.csvor.jsonsettings file.- Returns:
the settings dict.
- Raises:
SettingsError – when the path is missing, unreadable or malformed. Never a traceback — a cluster job should fail with a sentence.
- spacr.cli.main(argv: Sequence[str] | None = None) int[source]¶
spacr-runentry point.- Parameters:
argv – argument list;
sys.argv[1:]when None.- Returns:
process exit code — 0 success, 1 the module raised, 2 bad arguments or settings. Any other integer comes from a pipeline that raised
SystemExititself;cmd_run()passes that code through unchanged.
- spacr.cli.module_defaults(module: Module) Dict[str, Any][source]¶
Return a fresh defaults dict for
module.Calls the same helper the pipeline itself uses to canonicalize its settings, so the resolved dict the CLI prints is the one the pipeline will see. That is usually a
spacr.settingsfunction (Module.defaults); for the pipelines that keep their own it isModule.defaults_entry, imported here rather than at module load so--liststays instant.- Parameters:
module – the module whose defaults are wanted.
- Returns:
dict of defaults; empty when the pipeline has no helper.
- Raises:
SettingsError – when the defaults module will not import. This used to return
{}so that--describesurvived a missing optional dependency, butresolve_settings()is the run path, not just the describe path: convert, illumination, foreign, external_masks, barcode_qc, anndata_export and every plugin app then ran on a settings dict with no defaults in it, andspacr-run convert --set z_handling=maxwas rejected with “names a setting that does not exist for module ‘convert’” — pointing the user at their own command line instead of at the dependency that is actually missing.--describeis unaffected: it has its own guard around this call.
- spacr.cli.render_module_description(module: Module) str[source]¶
Render the
--describeblock for one module.- Parameters:
module – module to describe.
- Returns:
the description as one string, no trailing newline.
- spacr.cli.render_module_list() str[source]¶
Render the
--listtable of headless-runnable modules.- Returns:
the table as one string, no trailing newline.
- spacr.cli.render_settings(settings: Mapping[str, Any]) str[source]¶
Render a resolved settings dict as an aligned, sorted table.
- Parameters:
settings – the resolved settings.
- Returns:
the table as one string, no trailing newline.
- spacr.cli.resolve_module(name: Any) Module | None[source]¶
Return the
Modulefor a user-typed name, or None.- Parameters:
name – module key, alias, or the bare name of the pipeline function.
- Returns:
the matching
Module, or None when nothing matches.
- spacr.cli.resolve_settings(module: Module, settings_path: str | None, overrides: Sequence[str] = (), moved: List[Any] | None = None) Dict[str, Any][source]¶
Build the settings dict the pipeline will actually receive.
Layered lowest-to-highest: the module’s own defaults, the settings file, then the
--setoverrides. The file is read under today’s names first – see_under_todays_names(), without which a value the file set under an old name lost to the default already sitting under the new one.- Parameters:
module – module being run.
settings_path – path to the settings file, or None for defaults only.
overrides –
key=valuestrings.moved – list to receive the migration notices, passed straight to
_under_todays_names(); a caller that prints a pre-flight report hands these to_preflight()so the keys the fold consumed are still named there.
- Returns:
the fully-resolved settings dict.
- Raises:
SettingsError – on any unreadable file, unknown key or bad value.
- spacr.cli.setup_logging(verbose: bool = False) logging.Logger[source]¶
Configure timestamped logging to stdout for a batch run.
- Parameters:
verbose – raise the level from INFO to DEBUG.
- Returns:
the
spacr.clilogger.
- spacr.cli.use_agg_if_headless() bool[source]¶
Force matplotlib’s Agg backend when there is no display.
Called before the first spaCR import that could pull pyplot. An explicit
MPLBACKENDin the environment always wins, and interactive local use (a display is present) is left alone, so this only bites on a compute node.- Returns:
True when Agg was forced.
Nested helpers¶
- _NoShow.__enter__._close_instead(*args: Any, **kwargs: Any) None¶
Close the captured current figure instead of displaying it.
- Parameters:
args – ignored positional arguments accepted for
showsignature compatibility.kwargs – ignored keyword arguments accepted for compatibility.
- Returns:
None. Backend and close errors are deliberately swallowed so optional visualization cleanup cannot fail a headless run.
spacr/cli.py:1387