Campanella G, Kumar N, Nanda S, Singi S, Fluder E, Kwan R, Muehlstedt S, Pfarr N, Schüffler PJ, Häggström I, Neittaanmäki N, Akyürek LM, Basnet A, Jamaspishvili T, Nasr MR, Croken MM, Hirsch FR, Elkrief A, Yu H, Ardon O, Goldgof GM, Hameed M, Houldsworth J, Arcila M, Fuchs TJ, Vanderbilt C. Real-world deployment of a fine-tuned pathology foundation model for lung cancer biomarker detection. Nat Med. 2025 Sep;31(9):3002-3010. doi: 10.1038/s41591-025-03780-x. Epub 2025 Jul 9. PMID: 40634781; PMCID: PMC12443599.
-
Clone the Repository:
git clone https://github.com/chadvanderbilt/EAGLE.git
-
Create and Activate a Conda Environment:
conda create -n irt_env python=3.9 --file requirements.txt conda activate irt_env
We fine-tuned the tile-level encoder of Prov-GigaPath and used Gated MIL attention (Ilse et al. 2019) as slide-level aggregator to predict EGFR mutational status in lung adenocarcinoma. The fine-tuning algorithm is described in detail in Campanella et al. 2024. Training was parallelized over 24 H100 GPUs. We used the LSF scheduler for job submission. The training submission script that we used can be found at training/train.lsf. Note: the script was redacted to remove cluster-specific details. LSF parameters may need to be modified. The training can be performed by
cd training
bsub < train.lsfwith lsf or for slurm hpc:
cd training
sbatch train.slurmTo run correctly, the pipeline expects the path of two files to be included in the training/datasets.py script:
slide_data.csv: a file containing the name, path, target, and data split for each slide in the dataset.tile_data.csv: a file containing the slide name, x, and y coordinates as well as the level (within the slide file) and a rescale factor for each tile in the dataset. The rescale factor may be necessary if the right magnification is not available in the slide.
To finetune GigaPath, access to their weights is necessary via HuggingFace. Package requirements in conda irt_env.
This section explains how to select the model thresholds for deployment in a new institution. We provide an example csv file: calibration/test_data.csv.
The script calibration/pretrial_tuning.py illustrates the analysis we performed. To simulate the deployment of EAGLE one can do:
import numpy as np
import pandas as pd
import utils
df = pd.read_csv('example_data.csv')
utils.simulation(df)To evaluate the performance of the assisted workflow after selecting the thresholds one can do:
import numpy as np
import pandas as pd
import utils
df = pd.read_csv('example_data.csv')
threshold_npv = 0.023
threshold_ppv = 0.997
utils.get_performance_assisted_bootstrapped(df, th0=[threshold_npv], th1=[threshold_ppv], n=1000, target_col='target', rapid_col='rapid', eagle_col='score')More details about parameters of these functions can be found by:
import utils
help(utils.simulation)
help(utils.plot_simulation)
help(utils.get_performance_assisted_bootstrapped)This section explains how to set up and run the IRT Pipeline for monitoring and processing scanned slides in real-time. The repository is hosted at EAGLE GitHub Repository and the relevant shell scripts are located in the IRT_Pipeline subfolder.
The run.sh script monitors newly scanned slides every hour and generates manifests for downstream processing.
Cron Job Setup:
To schedule this script to run every hour, offset by 30 minutes from the run_full_gigapath_pipeline.sh, add the following to your crontab:
crontab -eAdd this line to schedule run.sh:
30 * * * * /path/to/EAGLE/IRT_Pipeline/run.shThe run_full_gigapath_pipeline.sh script processes all slides identified by the monitoring script.
Cron Job Setup:
To run the full pipeline every hour on the hour, add the following to your crontab:
0 * * * * /path/to/EAGLE/IRT_Pipeline/run_full_gigapath_pipeline.shThe scripts assume the following directory structure for outputs and logs:
/your/production/
├── logs/
│ └── irt_monitor/ # Logs generated by the monitoring script
├── slide_data/ # Manifests and slide data outputs
└── run_EGFR/ # Directory where the pipeline outputs. BASE_DIR is run_EGFR
├── molecular_watcher/ # Directory for monitoring molecular data
├── slides_to_run/ # Directory containing slides to be processed
├── EGFR_results/ # Directory for EGFR test results
├── tmp_files/ # Temporary files directory
└── checkpoints/ # Model checkpoints directory
Ensure these directories exist or are correctly specified in the shell scripts. Adjust paths as needed for your environment. Our laboratory has API endpoints available for Real Time data. If such data is available via database then adjust as needed.
Each execution of the run.sh script will generate a log file in /your/production/logs/irt_monitor/, with filenames formatted as YYYY-MM-DD_HH-MM-SS.log.
To manually execute the monitoring script and generate manifests:
bash /path/to/EAGLE/IRT_Pipeline/run.shTo manually execute the full pipeline script:
bash /path/to/EAGLE/IRT_Pipeline/run_full_gigapath_pipeline.shYou can find the new paths in https://github.com/chadvanderbilt/EAGLE/tree/main/quick_inference
This module provides a minimal interface for running Gigapath-based inference using fine-tuned EAGLE model weights.
It is designed for rapid evaluation or testing of pre-trained pathology foundation models without running the full IRT pipeline.
The quick inference workflow performs the following:
- Reads a directory of whole-slide images (
.svs,.tif,.ndpi, etc.) - Extracts tiles and generates embeddings using the fine-tuned tile-level encoder
- Aggregates features using the slide-level MIL attention model
- Produces slide-level predictions saved to a
.csvfile
quick_inference/
├── run_eagle_full.py # Standalone inference pipeline (dir of .svs → CSV)
├── run_eagle_full.sh # Bash wrapper
├── checkpoints/ # Place weights here (see below)
└── outputs/ # Thumbnails + results CSV
conda activate irt_env
pip install -r ../requirements.txt
export CUDA_VISIBLE_DEVICES=0 # optionalWeights are hosted at: https://huggingface.co/MCCPBR/EAGLE/tree/main
mkdir -p quick_inference/checkpoints
cd quick_inference/checkpoints
wget https://huggingface.co/MCCPBR/EAGLE/resolve/main/gigapath_ft_checkpoint_tile_020.pth
wget https://huggingface.co/MCCPBR/EAGLE/resolve/main/gigapath_ft_checkpoint_slide_020.pthOr via CLI:
pip install -U "huggingface_hub>=0.21"
huggingface-cli download MCCPBR/EAGLE gigapath_ft_checkpoint_tile_020.pth --local-dir checkpoints
huggingface-cli download MCCPBR/EAGLE gigapath_ft_checkpoint_slide_020.pth --local-dir checkpointsEdit quick_inference/run_eagle_full.sh:
PYTHON_BIN="~/anaconda3/envs/EAGLE/bin/python"
SCRIPT_PATH="~/EAGLE/quick_inference/run_eagle_full.py"
SLIDES_DIR="/path/to/slides"
TILE_CKPT="~/EAGLE/quick_inference/checkpoints/gigapath_ft_checkpoint_tile_020.pth"
SLIDE_CKPT="~/EAGLE/quick_inference/checkpoints/gigapath_ft_checkpoint_slide_020.pth"
OUTDIR="~/EAGLE/quick_inference/outputs"
OUTNAME="results.csv"cd quick_inference
bash run_eagle_full.shPass-through arguments to the Python script are supported, e.g.:
bash run_eagle_full.sh --batch_size 32 --workers 8 --recursiverun_eagle_full.py automatically selects the best device and remains robust under tight memory:
-
GPU selection by free VRAM
At runtime, the script queries each visible CUDA device and picks the GPU with the most free memory.
The minimum required free memory is configurable with--gpu_min_free_gb(default2.0).- If the best GPU has ≥
gpu_min_free_gb, it’s used. - If no GPU meets the threshold, it still uses the best available GPU unless a strict mode is enforced (not default).
- If no CUDA is available at all, it falls back to CPU automatically.
- If the best GPU has ≥
-
CUDA OOM auto-retry on CPU
If a CUDA out-of-memory error happens mid-slide, the script retries that slide on CPU with conservative settings (batch size 1, workers 0).
This is not recommended for performance, but ensures the pipeline completes and produces a score. -
Conservative CUDA + cuCIM settings
The script configuresPYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:64and disables cuCIM caches to reduce allocator pressure. -
Pinned memory / workers
DataLoader usespin_memory=True,persistent_workers=Truewhen on GPU, and you can set--workers(default 0; if GPU is used and workers=0, it promotes to 2). -
Flags you may care about
--gpu_min_free_gb FLOAT(default 2.0) – lower if your GPU is small, raise if you want to be stricter.--batch_size INT– tile embedding batch size.--workers INT– dataloader workers.
TL;DR: it prefers the roomiest GPU; if CUDA isn’t present or memory is too tight, it gracefully runs on CPU so the job still finishes.
- Slide discovery: by default it scans
slides_dirfor*.svs; add--recursiveto search subfolders. - Thumbnails & tile-grid overlays: a clean thumbnail and a red-box tile grid are saved under
outputs/thumbnails/. - CSV schema (append-only, resumes automatically if already present):
slide, slide_path, target, score, inference_time, n_tiles, level, thumbnail_path, thumbnail_clean_path
- Skip logic: if a slide already has a valid row (
scorepresent andn_tiles>0), it is skipped on subsequent runs. - Hugging Face tokens: if the backbone is gated, pass
--hf_tokenor exportHF_TOKEN/HUGGING_FACE_HUB_TOKEN.
Local run:
bash quick_inference/run_eagle_full.shForce small GPU threshold and more workers:
bash quick_inference/run_eagle_full.sh --gpu_min_free_gb 1.0 --batch_size 32 --workers 8Recursive search:
bash quick_inference/run_eagle_full.sh --recursiveCreate run_inference.slurm:
#!/usr/bin/env bash
#SBATCH --job-name=eagle_infer
#SBATCH --gres=gpu:1
#SBATCH --cpus-per-task=8
#SBATCH --mem=64G
#SBATCH --time=4:00:00
#SBATCH --output=logs/%x_%j.out
#SBATCH --error=logs/%x_%j.err
module load anaconda
source activate irt_env
cd $SLURM_SUBMIT_DIR/quick_inference
bash run_eagle_full.sh --batch_size 32 --workers 8Submit:
sbatch run_inference.slurm- Prefer absolute paths on HPC.
- Log stdout/stderr:
bash run_eagle_full.sh > logs/inference_$(date +%Y%m%d_%H%M%S).log 2>&1
- Recommended GPU: ≥24 GB VRAM (A100/H100).
- Record the Hugging Face commit hash of weights for reproducibility.
References
Campanella et al., Fine-Tuning Pathology Foundation Models for EGFR Prediction in Lung Adenocarcinoma, 2024.
Prov-GigaPath pretrained model: https://huggingface.co/prov-gigapath/prov-gigapath