""" startup.py — automatic model bootstrap for HuggingFace Spaces (and local dev). Download strategy for MedSAM checkpoint: 1. MEDSAM_CHECKPOINT env var set and file exists → use it as-is (local / server) 2. HuggingFace Hub: wanglab/medsam-vit-base → hf_hub_download (same as MedGemma) 3. Zenodo fallback → urllib download, cached locally After downloading, os.environ["MEDSAM_CHECKPOINT"] is updated so the rest of the app always sees the correct resolved path. """ from __future__ import annotations import os import urllib.request from pathlib import Path # --------------------------------------------------------------------------- # Config (all overridable via env vars) # --------------------------------------------------------------------------- _MEDSAM_HF_REPO = os.getenv("MEDSAM_HF_REPO", "wanglab/medsam-vit-base") _MEDSAM_HF_FILENAME = os.getenv("MEDSAM_HF_FILENAME", "medsam_vit_b.pth") _MEDSAM_ZENODO_URL = ( "https://zenodo.org/records/10689643/files/medsam_vit_b.pth" ) # Cache inside HF_HOME so persistent storage on HF Spaces is respected _CACHE_DIR = ( Path(os.getenv("HF_HOME", str(Path.home() / ".cache" / "huggingface"))) / "medsam" ) # --------------------------------------------------------------------------- # MedSAM checkpoint resolution # --------------------------------------------------------------------------- def _resolve_medsam_checkpoint() -> str: """Return the local path to medsam_vit_b.pth, downloading if needed.""" # ── 1. Explicit path already provided and file exists ──────────────────── explicit = os.getenv("MEDSAM_CHECKPOINT", "") if explicit and Path(explicit).exists(): print(f"[startup] MedSAM checkpoint: using MEDSAM_CHECKPOINT={explicit}") return explicit _CACHE_DIR.mkdir(parents=True, exist_ok=True) # ── 2. HuggingFace Hub — wanglab/medsam-vit-base (same mechanism as MedGemma) ── try: from huggingface_hub import hf_hub_download print( f"[startup] Downloading MedSAM from HuggingFace Hub " f"({_MEDSAM_HF_REPO}/{_MEDSAM_HF_FILENAME})…" ) path = hf_hub_download( repo_id=_MEDSAM_HF_REPO, filename=_MEDSAM_HF_FILENAME, ) print(f"[startup] MedSAM cached at: {path}") os.environ["MEDSAM_CHECKPOINT"] = path return path except Exception as hf_err: print( f"[startup] HuggingFace download failed ({hf_err}), " f"falling back to Zenodo…" ) # ── 3. Zenodo fallback with local cache ─────────────────────────────────── cached = _CACHE_DIR / _MEDSAM_HF_FILENAME if cached.exists(): print(f"[startup] MedSAM checkpoint found in cache: {cached}") os.environ["MEDSAM_CHECKPOINT"] = str(cached) return str(cached) print(f"[startup] Downloading MedSAM checkpoint (~375 MB) from Zenodo…") def _progress(count: int, block: int, total: int) -> None: if total > 0 and count % 500 == 0: pct = min(100, count * block * 100 // total) print(f"\r[startup] {pct}%", end="", flush=True) urllib.request.urlretrieve(_MEDSAM_ZENODO_URL, cached, reporthook=_progress) print(f"\n[startup] Saved to: {cached}") os.environ["MEDSAM_CHECKPOINT"] = str(cached) return str(cached) # --------------------------------------------------------------------------- # Public entry-point # --------------------------------------------------------------------------- def initialize_all_models(store: dict) -> str: """ Load MedGemma and MedSAM into *store*. Returns an HTML string for the load_status component. """ from src.config.endpoints import MEDGEMMA_MODEL_ID, MEDSAM_DEVICE lines: list[str] = [] # ── MedGemma (Detection + Report generation) ────────────────────────────── try: from src.clients.medgemma_client import MedGemmaClient print("[startup] Loading MedGemma…") store["medgemma"] = MedGemmaClient(model_id=MEDGEMMA_MODEL_ID) lines.append( "✅ Detection model ready" ) print("[startup] MedGemma ready.") except Exception as exc: lines.append( f"❌ Detection model failed: {exc}" ) print(f"[startup] MedGemma failed: {exc}") # ── MedSAM (Segmentation) ───────────────────────────────────────────────── try: ckpt_path = _resolve_medsam_checkpoint() store["medsam_ckpt_path"] = ckpt_path # expose for lazy-load fallback from src.clients.medsam_client import MedSAMClient print(f"[startup] Loading MedSAM from {ckpt_path}…") store["medsam"] = MedSAMClient(checkpoint_path=ckpt_path, device=MEDSAM_DEVICE) lines.append( f"" f"✅ Segmentation model ready ({MEDSAM_DEVICE})" ) print("[startup] MedSAM ready.") except Exception as exc: lines.append( f"" f"❌ Segmentation model failed: {exc}" ) print(f"[startup] MedSAM failed: {exc}") return "
".join(lines)