Source code for ecotyper.builtin

"""The prebuilt EcoTyper models (Carcinoma, Lymphoma) by name.

The models live in the R repository (``digitalcytometry/ecotyper``, folder
``EcoTyper/<name>/<name>_Fractions``). Recovery only needs a few small files
per cell type (about 5 MB per model instead of ~440 MB for the whole folder),
so these are downloaded on first use from a pinned commit and cached.

Lookup order for :func:`builtin_model_path`:

1. ``$ECOTYPER_MODELS``: a local ``EcoTyper`` folder of the R repository
   (or any folder containing ``<name>/<name>_Fractions``);
2. the cache ``$ECOTYPER_CACHE`` (default ``~/.cache/ecotyper``);
3. download into the cache.
"""
from __future__ import annotations

import os
import urllib.request
from pathlib import Path

#: EcoTyper R repository commit the models are taken from (the one validated against).
COMMIT = "11408e71900e15829cf4f33e1331ce47c053472d"
REPO_RAW = "https://raw.githubusercontent.com/digitalcytometry/ecotyper"
BUILTIN = ("Carcinoma", "Lymphoma")

#: files of ``<fractions folder>`` that recovery reads (heatmap_data.txt is not
#: needed: its row names equal the genes of gene_info.txt in every shipped model)
MODEL_FILES = ("Analysis/rank_selection/rank_data.txt", "Ecotypes/discovery/ecotypes.txt",
               "Ecotypes/discovery/mapping_all_states.txt")
STATE_FILES = ("W.txt", "mapping_to_initial_states.txt", "initial_gene_info.txt", "gene_info.txt")


[docs] def cache_dir() -> Path: return Path(os.environ.get("ECOTYPER_CACHE", Path.home() / ".cache" / "ecotyper"))
def _local(name: str) -> Path | None: root = os.environ.get("ECOTYPER_MODELS") if not root: return None for cand in (Path(root) / name / f"{name}_Fractions", Path(root) / "EcoTyper" / name / f"{name}_Fractions"): if (cand / "Analysis").is_dir(): return cand return None def _fetch(url: str, dest: Path) -> None: dest.parent.mkdir(parents=True, exist_ok=True) tmp = dest.with_name(dest.name + ".part") with urllib.request.urlopen(url, timeout=120) as r, open(tmp, "wb") as fh: while chunk := r.read(1 << 20): fh.write(chunk) tmp.replace(dest)
[docs] def builtin_model_path(name: str, *, download: bool = True, verbose: bool = True) -> Path: """Folder ``.../<name>/<name>_Fractions`` of a prebuilt model, downloading it if needed.""" if name not in BUILTIN: raise ValueError(f"Unknown built-in model {name!r}; available: {', '.join(BUILTIN)}") local = _local(name) if local is not None: return local base = cache_dir() / "models" / COMMIT[:12] / name / f"{name}_Fractions" done = base / ".complete" if done.exists(): return base if not download: raise FileNotFoundError(f"Built-in model {name!r} is not cached in {base}") url = f"{REPO_RAW}/{COMMIT}/EcoTyper/{name}/{name}_Fractions" if verbose: print(f"Downloading the {name} model from github.com/digitalcytometry/ecotyper into {base}") for f in MODEL_FILES: if not (base / f).exists(): _fetch(f"{url}/{f}", base / f) from .rcompat.io import read_delim rank = read_delim(base / MODEL_FILES[0]) for ct, n in zip(rank.iloc[:, 0], rank.iloc[:, 1]): rel = f"Cell_States/discovery/{ct}/{int(n)}" for f in STATE_FILES: if not (base / rel / f).exists(): _fetch(f"{url}/{rel}/{f}", base / rel / f) done.write_text(COMMIT + "\n") return base