spider-qupath
A QuPath menu that runs HistAI’s organ-specific SPIDER classifiers zero-shot on annotations — simpler and more honest than its sibling kaiko-qupath because it trains nothing, but it silently ignores the size and shape of every annotation it is given.
Purpose
Point one of three pretrained SPIDER models at a region and get a tissue class back, with no training step, no labelled data and no classifier to validate. That is a genuinely different proposition from kaiko-qupath: there the group trains a head on Midnight embeddings, here HistAI’s own head does the classifying and the group supplies only the region.
The trade-off is the one Foundation Models in Pathology states — the encoder and the label set are inherited, so the class vocabulary is HistAI’s and there is no way to adapt it or to measure it against local cases.
Public repository, last pushed 2025-07-13, no licence file.
Data used
No cohort. Three pretrained models, loaded from local paths: histai/SPIDER-colorectal-model (13 classes), histai/SPIDER-skin-model (24), histai/SPIDER-thorax-model (14). The accuracy figures quoted in the repository’s docs — 91.4%, 94.0%, 96.2% — are HistAI’s published numbers carried over from the paper (arXiv 2503.02876), not measurements made here. Nothing in this repository evaluates the models against local material, which is the gap worth closing before any of it is relied on.
Methods
scripts/spider_plugin_menu.groovy installs Extensions → SPIDER Analysis and is the only live entry point; it calls two Python scripts by subprocess with JSON in and predictions.json out. Several other scripts in scripts/ and python/ are orphaned alternates the menu never reaches, and scripts/spider_quick_installer.groovy embeds a second full copy of the same logic as Groovy string literals, free to drift from the originals.
Annotation size and shape are discarded. In python/spider_qupath_classifier.py, extract_region_with_context() reads the annotation’s x, y, width and height, uses them only to compute a centroid, and then reads a hardcoded 1120×1120 window at level 0. A 10-pixel annotation and a 10,000-pixel annotation with the same centre produce byte-identical model input; anything larger than 1120 px is silently centre-cropped. Nothing in the documentation says so. For QuPath Annotation Workflow this matters directly — a pathologist who draws carefully around a lesion gets the same answer as one who drops a dot in the middle of it, and the interface gives no hint of that.
Whole-slide analysis reloads the model for every patch. In whole_slide_analysis_spider_universal.py, process_patch() calls load_spider_model() on each call, with no cache. The single-threaded loop runs it up to max_patches (default 1000) times. The default is worse: num_workers defaults to 4, the module has no if __name__ == "__main__": guard, and all slide-opening and model-loading happens at module scope — so under Windows spawn, each worker re-executes the whole file, and process_patch() reads a module-global slide the parent closes before the pool starts. [unverified] whether this crashes at runtime or merely thrashes; the static structure says it cannot work as intended, and the Groovy caller already forces workers = 1 on Windows, which makes the parallel path dead code in the only environment the repository is set up for.
Model type comes from the folder name. detect_model_type() substring-matches "colorectal", "skin", "thorax" against the model path, two lines before the code opens the model’s own config.json and reads real class names from it. A path missing those substrings falls back to colorectal with a printed warning, after which every class renders grey in the heatmap legend and nothing errors.
Current state / open questions
Paused, and the least broken of the three foundation-model prototypes here — because it attempts less. The fixes are small and worth doing: honour the annotation bounds (or say in the UI that a fixed window is used), cache the model, add a __main__ guard, and read the model type from config.json rather than the path.
- The real open question is not a bug. Does a SPIDER class vocabulary trained on HistAI’s material mean the same thing on Memorial cases? That is an External Validation question and no answer exists here. A small blinded agreement study against a pathologist would settle it and would be more valuable than any of the code fixes above.
- No licence on a public repository — see the pattern on Digital Pathology Drafts.
Related: kaiko-qupath — the sibling taking the opposite approach; reading both together is what shows that the group has two incompatible foundation-model designs and has not chosen. Related: Foundation Models in Pathology — why an inherited label set is the binding constraint here rather than accuracy. Related: QuPath Annotation Workflow — the fixed-window behaviour changes what a careful annotation is worth. Related: Digital Pathology Drafts — the umbrella repository this sits in as a submodule.
Derived from: repository source read 2026-07-26 — scripts/spider_plugin_menu.groovy, python/spider_qupath_classifier.py, python/whole_slide_analysis_spider_universal.py, scripts/spider_quick_installer.groovy, CLAUDE.md, AGENTS.md, docs/; GitHub API for visibility and licence state, read 2026-07-26. Findings independently re-verified by a second reading instructed to refute them.