Source code for spacr

"""spaCR public package and version metadata."""

from __future__ import annotations

import os as _os
import warnings as _warnings
from importlib import import_module

_os.environ.setdefault("PYTORCH_ENABLE_MPS_FALLBACK", "1")

from ._version import __version__

_warnings.filterwarnings(
    "ignore",
    message=r"The pynvml package is deprecated\..*",
    category=FutureWarning,
)
_warnings.filterwarnings(
    "ignore",
    message=r"You are using a Python version.*google\.api_core.*",
    category=FutureWarning,
)
_warnings.filterwarnings(
    "ignore",
    message=r"You are using a Python version.*",
    category=FutureWarning,
    module=r"google\..*",
)

_warnings.filterwarnings(
    "ignore",
    message=r".*[Ss]parse invariant checks are implicitly disabled",
    category=UserWarning,
    module=r"cellpose(\.|$)",
)

_DOCUMENTED_SUBMODULES: tuple[str, ...] = (
    "api",
    "core",
    "schema",
    "database_schema",
    "database_concurrency",
    "io",
    "tabular",
    "utils",
    "errors",
    "settings",
    "setting_animations",
    "settings_spec",
    "settings_advisor",
    "plot",
    "measure",
    "measure_hooks",
    "roi",
    "illumination",
    "measurement_schema",
    "sequencing",
    "sequencing_qc",
    "read_background",
    "lineage",
    "timelapse",
    "tiff_io",
    "deep_spacr",
    "diameter",
    "feature_dict",
    "image_colors",
    "crops",
    "png_list",
    "regex_infer",
    "import_plan",
    "portable_paths",
    "picture_settings",
    "well_spec",
    "align",
    "convert",
    "foreign",
    "external_masks",
    "resume",
    "restart_state",
    "checkpoint",
    "normalization",
    "intensity_rescale",
    "install_profile",
    "umap_search",
    "cancellation",
    "zstack",
    "report",
    "train_compare",
    "hyperparam",
    "attribution",
    "attribution_columns",
    "agreement",
    "annotation_power",
    "annotation_umap_qc",
    "annotation_validation",
    "active_learning",
    "segmentation_uncertainty",
    "curation",
    "sudoku",
    "plate_qc",
    "seg_qc",
    "model_compare",
    "image_import",
    "image_stitch",
    "model_zoo",
    "batch",
    "batch_correction",
    "classifier_evaluation",
    "classifier_quality",
    "confusion",
    "submodules",
    "ml",
    "predictions",
    "toxo",
    "spacr_cellpose",
    "sp_stats",
    "sim",
    "object",
    "object_roles",
    "object_settings_table",
    "organelle_types",
    "ome_zarr",
    "omero",
    "cli",
    "cli_database",
    "cli_workspace",
    "doctor",
    "crashreport",
    "cli_leakage",
    "cli_download",
    "example_archives",
    "cli_plugins",
    "cli_remote",
    "cli_repro",
    "_v1_v2_bridge",
    "logger",
    "logging_util",
    "mask_io",
    "layers",
    "counting",
    "napari_bridge",
    "selection",
    "regression_qc",
    "regression_failure",
    "regression_summary",
    "localisation",
    "figure_sink",
    "control_names",
    "example_data_manifest",
    "example_data",
    "columns",
    "annotation",
    "regression_backends",
    "mixed_gpu",
    "rra",
    "group_lasso",
    "baseline",
    "cell_montage",
    "plate_measurements",
    "thresholds",
    "regression_spec",
    "refit",
    "power_simulate",
    "power_model",
    "hits",
    "profiler",
    "pipeline_v2",
    "plugins",
    "remote_execution",
    "runctx",
    "run_journal",
    "run_compare",
    "macro",
    "notebook_export",
    "methods_export",
    "custom_features",
    "umap_annotations",
    "row_exclusions",
    "torch_artifacts",
    "ports",
    "artifacts",
    "pipeline_graph",
    "chaining",
    "data_manager",
    "projects",
    "validate",
    "updater",
    "version",
    "classify",
    "classify_classes",
    "crop_source",
    "benchmark",
    "column_groups",
    "filters",
    "gate_library",
    "gpu_reduce",
    "merge_tables",
    "derived_tables",
    "condition_annotations",
    "original_filenames",
    "model_check",
    "openmp_guard",
    "surrogate",
    "guide_permutation",
    "hit_attribution",
    "hit_investigation",
    "training_basis",
    "multiple_testing",
    "volcano_style",
    "guide_concordance",
    "regression_diagnostics",
    "regression_search",
    "metadata_resolution",
    "multi_database",
    "measurement_scan",
    "gene_facts",
    "gene_tile",
    "gene_measurement_compare",
    "gene_measurement_sweep",
    "guide_attribution",
    "fit_resources",
    "parameter_sweep",
    "sweep_child",
    "trial_metrics",
    "workspace",
    "figure_style",
    "style_base",
    "dependent_join",
    "graph_types",
    "outlier_filter",
    "permutation_qc",
    "response_distribution",
    "run_recommendations",
    "stream_dataset",
    "well_scope",
)


def _submodules_on_disk() -> frozenset[str]:
    """Every ``spacr/*.py`` sitting beside this file, by module name.

    Returns nothing when the sources are not on a readable filesystem -- a
    PyInstaller bundle keeps the modules inside its archive, where there is
    no directory to scan -- which is why this widens the documented tuple
    rather than replacing it.
    """
    try:
        entries = _os.listdir(_os.path.dirname(_os.path.abspath(__file__)))
    except OSError:
        return frozenset()
    return frozenset(
        name[:-3] for name in entries
        if name.endswith(".py") and name not in ("__init__.py", "__main__.py")
    )


#: What ``getattr(spacr, name)`` will import. The documented tuple is the
#: floor; the directory is the authority. A hand-kept inventory of the files
#: in its own directory had drifted four separate times, each landing a
#: module that existed but could not be reached through the package, so the
#: names are taken from the directory whenever there is one to read.
_SUBMODULES: tuple[str, ...] = tuple(sorted(
    set(_DOCUMENTED_SUBMODULES) | _submodules_on_disk()
))

__all__ = [
    "__version__",
    "download_models",
    "MaskConfig",
    "MeasureConfig",
    "run_mask",
    "run_measure",
]

_FACADE_NAMES: frozenset[str] = frozenset({
    "MaskConfig", "MeasureConfig", "run_mask", "run_measure",
})


[docs] def download_models(repo_id="einarolafsson/models", retries=5, delay=5): """Download spaCR's optional model files on first use. The implementation is imported only when called, keeping ``import spacr`` and wildcard imports lightweight. """ from .utils import download_models as _download_models return _download_models(repo_id=repo_id, retries=retries, delay=delay)
def __getattr__(name: str): """Lazily import declared submodules and the ``download_models`` helper on first access. :param name: Attribute name requested on the ``spacr`` package. :returns: Imported submodule or the ``download_models`` callable. :raises AttributeError: If ``name`` is neither a known submodule nor ``download_models``. """ if name in _FACADE_NAMES: return getattr(import_module(".api", __name__), name) if name in _SUBMODULES: return import_module(f".{name}", __name__) raise AttributeError(f"module {__name__!r} has no attribute {name!r}") def __dir__() -> list[str]: """Include lazy submodule names in ``dir(spacr)`` for tab-completion.""" return sorted(set(globals()) | _FACADE_NAMES | set(_SUBMODULES)) def _silence_glyph_logging() -> None: """Pin fontTools at WARNING as soon as spaCR is imported. ``fontTools.subset`` emits about forty INFO lines for every figure saved -- each glyph name and glyph ID, twice, for MATH then GSUB then glyf, then one line per font table. A regression run saves a dozen figures, so thousands of lines of glyph inventory bury the run's own output, and the line the user is actually looking for scrolls past unread. ``logging_util.QUIET_LOGGERS`` lists it too, but that only applies when ``setup_logging()`` runs, it short-circuits on ``_INITIALISED`` if something configured logging first, and a script or notebook that never calls it gets no protection at all. Doing it at import means importing spaCR is sufficient, whatever the startup order. This sets a floor, not a lock: anyone who genuinely wants glyph traces can lower the level again after importing. """ import logging for name in ("fontTools", "fontTools.subset", "fontTools.ttLib"): logging.getLogger(name).setLevel(logging.WARNING) _silence_glyph_logging()