Skip to content

Commit

Permalink
Rename package to SpinPath (#7)
Browse files Browse the repository at this point in the history
* replace wsinfer_mil with spinpath

* rename wsinfer_mil directory to spinpath

* rename to spinpath

* rename to spinpath

* update copyright year

* rename to spinpath

* rm files related to zoo
  • Loading branch information
kaczmarj authored Jan 13, 2025
1 parent b6b49a8 commit 361c056
Show file tree
Hide file tree
Showing 42 changed files with 111 additions and 299 deletions.
24 changes: 12 additions & 12 deletions .github/workflows/cli-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install WSInfer-CLI
- name: Install SpinPath CLI
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/cpu
Expand All @@ -38,12 +38,12 @@ jobs:
wget -nc -q https://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1.svs
cd -
- name: Run tests
# Go to a different directory to test that WSInfer-MIL still finds everything it needs.
# Go to a different directory to test that SpinPath still finds everything it needs.
# Test it twice so the second time we get cache hits.
run: |
cd /tmp
SPINPATH_FORCE_CPU=1 wsinfer-mil run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
SPINPATH_FORCE_CPU=1 wsinfer-mil run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
SPINPATH_FORCE_CPU=1 spinpath run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
SPINPATH_FORCE_CPU=1 spinpath run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
macOS:
runs-on: macos-latest
Expand All @@ -62,7 +62,7 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install WSInfer-CLI
- name: Install SpinPath CLI
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/cpu
Expand All @@ -75,12 +75,12 @@ jobs:
wget -nc -q https://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1.svs
cd -
- name: Run tests
# Go to a different directory to test that WSInfer-MIL still finds everything it needs.
# Go to a different directory to test that SpinPath still finds everything it needs.
# Test it twice so the second time we get cache hits.
run: |
cd /tmp
SPINPATH_FORCE_CPU=1 wsinfer-mil run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
SPINPATH_FORCE_CPU=1 wsinfer-mil run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
SPINPATH_FORCE_CPU=1 spinpath run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
SPINPATH_FORCE_CPU=1 spinpath run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
Windows:
runs-on: windows-latest
Expand All @@ -99,7 +99,7 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install WSInfer-CLI
- name: Install SpinPath CLI
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/cpu
Expand All @@ -112,11 +112,11 @@ jobs:
Invoke-WebRequest -URI https://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1.svs -OutFile CMU-1.svs
cd -
- name: Run tests
# Go to a different directory to test that WSInfer-MIL still finds everything it needs.
# Go to a different directory to test that SpinPath still finds everything it needs.
# Test it twice so the second time we get cache hits.
run: |
mkdir -p ~/foobar
cd ~/foobar
set SPINPATH_FORCE_CPU=1
wsinfer-mil run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
wsinfer-mil run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
spinpath run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
spinpath run -m kaczmarj/pancancer-tissue-classifier.tcga -i ~/wsi/CMU-1.svs
4 changes: 2 additions & 2 deletions .github/workflows/mypy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install WSInfer-CLI
- name: Install SpinPath
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/cpu
python -m pip install .[dev] mypy
- name: Run mypy
run: python -m mypy --install-types --non-interactive wsinfer_mil
run: python -m mypy --install-types --non-interactive spinpath
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/scratch/

# Generated version information
wsinfer_mil/_version.py
spinpath/_version.py

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2024 Jakub Kaczmarzyk
Copyright 2025 Jakub Kaczmarzyk

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
42 changes: 21 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# WSInfer Multiple Instance Learning (MIL)
# SpinPath

[![Continuous Integration](https://github.com/SBU-BMI/wsinfer-mil/actions/workflows/cli-test.yml/badge.svg)](https://github.com/SBU-BMI/wsinfer-mil/actions/workflows/cli-test.yml)
[![Version on PyPI](https://img.shields.io/pypi/v/wsinfer-mil.svg)](https://pypi.org/project/wsinfer-mil/)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/wsinfer-mil)](https://pypi.org/project/wsinfer-mil/)
[![Continuous Integration](https://github.com/SBU-BMI/spinpath/actions/workflows/cli-test.yml/badge.svg)](https://github.com/SBU-BMI/spinpath/actions/workflows/cli-test.yml)
[![Version on PyPI](https://img.shields.io/pypi/v/spinpath.svg)](https://pypi.org/project/spinpath/)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/spinpath)](https://pypi.org/project/spinpath/)

WSInfer-MIL is a command line tool to run pre-trained MIL models on whole slide images. It is the slide-level companion to [WSInfer](https://wsinfer.readthedocs.io/en/latest/), which provides patch-level classification.
SpinPath is a command line tool to run pre-trained MIL models on whole slide images. It is the slide-level companion to [WSInfer](https://wsinfer.readthedocs.io/en/latest/), which provides patch-level classification.

> [!CAUTION]
> WSInfer-MIL is intended _only_ for research purposes.
> SpinPath is intended _only_ for research purposes.
# Install

WSInfer-MIL can be installed using `pip`. WSInfer-MIL will install PyTorch automatically
SpinPath can be installed using `pip`. SpinPath will install PyTorch automatically
if it is not installed, but this may not install GPU-enabled PyTorch even if a GPU is available.
For this reason, _install PyTorch before installing WSInfer-MIL_.
For this reason, _install PyTorch before installing SpinPath_.

## Install PyTorch first

Expand All @@ -35,10 +35,10 @@ To test whether PyTorch can detect your GPU, check that this code snippet prints
python -c 'import torch; print(torch.cuda.is_available())'
```

## Install WSInfer-MIL with pip
## Install SpinPath with pip

```
pip install wsinfer-mil
pip install spinpath
```

# Examples
Expand All @@ -55,37 +55,37 @@ The models are available at https://huggingface.co/kaczmarj
### TP53 mutation prediction

```
wsinfer-mil run -m kaczmarj/pancancer-tp53-mut.tcga -i slide.svs
spinpath run -m kaczmarj/pancancer-tp53-mut.tcga -i slide.svs
```

### Cancer tissue classification

```
wsinfer-mil run -m kaczmarj/pancancer-tissue-classifier.tcga -i slide.svs
spinpath run -m kaczmarj/pancancer-tissue-classifier.tcga -i slide.svs
```

### Metastasis prediction in axillary lymph nodes

```
wsinfer-mil run -m kaczmarj/breast-lymph-nodes-metastasis.camelyon16 -i slide.svs
spinpath run -m kaczmarj/breast-lymph-nodes-metastasis.camelyon16 -i slide.svs
```

### Survival prediction in GBM-LGG

```
wsinfer-mil run -m kaczmarj/gbmlgg-survival-porpoise.tcga -i slide.svs
spinpath run -m kaczmarj/gbmlgg-survival-porpoise.tcga -i slide.svs
```

### Survival prediction in kidney renal papillary cell carcinoma

```
wsinfer-mil run -m kaczmarj/kirp-survival-porpoise.tcga -i slide.svs
spinpath run -m kaczmarj/kirp-survival-porpoise.tcga -i slide.svs
```


## With a local (potentially private) model

You can use WSInfer-MIL with a local MIL model. The model must be saved to TorchScript format, and a model configuration file must also be written.
You can use SpinPath with a local MIL model. The model must be saved to TorchScript format, and a model configuration file must also be written.

Here is an example of a configuration JSON file:

Expand All @@ -103,30 +103,30 @@ Here is an example of a configuration JSON file:
}
```

There is a JSON schema in `wsinfer_mil/schemas/model-config.schema.json` for reference.
There is a JSON schema in `spinpath/schemas/model-config.schema.json` for reference.

Once you have the model in TorchScript format and the configuration JSON file, you can run the model on slides. For example:

```
wsinfer-mil runlocal -m model.pt -c model.config.json \
spinpath runlocal -m model.pt -c model.config.json \
-i slides/TCGA-3L-AA1B-01Z-00-DX1.8923A151-A690-40B7-9E5A-FCBEDFC2394F.svs
```

# How it works from 30,000 feet

The pipeline for attention-based MIL methods is rather standardized. Here are the steps that WSInfer-MIL takes. In the future, we would like to incorporate inference using graph-based methods, so this workflow will likely have to be modified.
The pipeline for attention-based MIL methods is rather standardized. Here are the steps that SpinPath takes. In the future, we would like to incorporate inference using graph-based methods, so this workflow will likely have to be modified.

1. Segment the tissue in the image.
2. Create patches of the tissue regions.
3. Run a feature extractor on these patches.
4. Run the pre-trained model on the extracted features.
5. Save the results of the extracted features.

WSInfer-MIL caches steps 1, 2, and 3, as those can be reused among MIL models. Step 3 (feature extraction) is often the bottleneck of the workflow, and reusing extracted features can reduce runtime considerably.
SpinPath caches steps 1, 2, and 3, as those can be reused among MIL models. Step 3 (feature extraction) is often the bottleneck of the workflow, and reusing extracted features can reduce runtime considerably.

# Developers

Clone and install `wsinfer-mil`:
Clone and install `spinpath`:

Clone the repository and make a virtual environment for it. Then install the dependencies, with `dev` extras.

Expand Down
14 changes: 7 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"]
build-backend = "setuptools.build_meta"

[project]
name = "wsinfer-mil"
name = "spinpath"
description = "Run specimen-level inference on whole slide images."
readme = "README.md"
requires-python = ">= 3.8"
Expand Down Expand Up @@ -77,17 +77,17 @@ docs = [
]

[project.urls]
Repository = "https://github.com/SBU-BMI/wsinfer-mil"
"Bug Tracker" = "https://github.com/SBU-BMI/wsinfer-mil/issues"
Repository = "https://github.com/SBU-BMI/SpinPath"
"Bug Tracker" = "https://github.com/SBU-BMI/SpinPath/issues"

[project.scripts]
wsinfer-mil = "wsinfer_mil.cli:cli"
spinpath = "spinpath.cli:cli"

[tool.setuptools.package-data]
wsinfer_mil = ["py.typed", "schemas/*.json"]
spinpath = ["py.typed", "schemas/*.json"]

[tool.setuptools.packages.find]
include = ["wsinfer_mil*"]
include = ["spinpath*"]

[tool.mypy]
disallow_untyped_defs = true
Expand Down Expand Up @@ -117,7 +117,7 @@ module = [
ignore_missing_imports = true

[tool.setuptools_scm]
write_to = "wsinfer_mil/_version.py"
write_to = "spinpath/_version.py"

[tool.ruff]
extend-exclude = ["_version.py"]
Expand Down
18 changes: 18 additions & 0 deletions spinpath/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""SpinPath is a toolkit for specimen-level inference on whole slide images."""

from __future__ import annotations

try:
from ._version import __version__
except ImportError:
__version__ = "0.0.unknown"

from spinpath.client.hfmodel import load_torchscript_model_from_hf
from spinpath.client.localmodel import load_torchscript_model_from_filesystem
from spinpath.inference import infer_one_slide

__all__ = [
"infer_one_slide",
"load_torchscript_model_from_filesystem",
"load_torchscript_model_from_hf",
]
2 changes: 1 addition & 1 deletion wsinfer_mil/__main__.py → spinpath/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from wsinfer_mil.cli import cli
from spinpath.cli import cli

if __name__ == "__main__":
cli()
4 changes: 2 additions & 2 deletions wsinfer_mil/cache.py → spinpath/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import numpy.typing as npt
from PIL import Image

from wsinfer_mil.defaults import WSINFER_MIL_CACHE_DIR
from spinpath.defaults import SPINPATH_CACHE_DIR

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -47,7 +47,7 @@ def __init__(
self.slide_quickhash = slide_quickhash
# self.patch_size_um = patch_size_um
if cache_dir is None:
self.cache_dir = WSINFER_MIL_CACHE_DIR
self.cache_dir = SPINPATH_CACHE_DIR
else:
self.cache_dir = Path(cache_dir)

Expand Down
10 changes: 5 additions & 5 deletions wsinfer_mil/cli.py → spinpath/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Command line interface for WSInfer MIL."""
"""Command line interface for SpinPath."""

from __future__ import annotations

Expand All @@ -9,10 +9,10 @@
import click
from PIL import Image

from wsinfer_mil.client.hfmodel import load_torchscript_model_from_hf
from wsinfer_mil.client.localmodel import Model
from wsinfer_mil.client.localmodel import load_torchscript_model_from_filesystem
from wsinfer_mil.inference import infer_one_slide
from spinpath.client.hfmodel import load_torchscript_model_from_hf
from spinpath.client.localmodel import Model
from spinpath.client.localmodel import load_torchscript_model_from_filesystem
from spinpath.inference import infer_one_slide

logger = logging.getLogger(__name__)

Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions wsinfer_mil/client/hfmodel.py → spinpath/client/hfmodel.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""API to interact with WSInfer MIL models on HuggingFace Hub."""
"""API to interact with SpinPath models on HuggingFace Hub."""

from __future__ import annotations

Expand All @@ -7,8 +7,8 @@

from huggingface_hub import hf_hub_download

from wsinfer_mil.client.localmodel import Model
from wsinfer_mil.client.localmodel import ModelConfiguration
from spinpath.client.localmodel import Model
from spinpath.client.localmodel import ModelConfiguration

HF_CONFIG_NAME = "config.json"
HF_TORCHSCRIPT_NAME = "torchscript_model.pt"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import jsonschema

from wsinfer_mil.errors import InvalidModelConfiguration
from spinpath.errors import InvalidModelConfiguration


@dataclasses.dataclass
Expand Down
File renamed without changes.
11 changes: 11 additions & 0 deletions spinpath/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from __future__ import annotations

from pathlib import Path

from platformdirs import user_cache_dir

# Where we keep all files related to SpinPath.
SPINPATH_DIR = Path(user_cache_dir(appname="spinpath"))

# Cache for feature embeddings.
SPINPATH_CACHE_DIR = SPINPATH_DIR / "cache"
15 changes: 15 additions & 0 deletions spinpath/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Errors for SpinPath."""

from __future__ import annotations


class SpinPathException(Exception):
"""Base exception for SpinPath."""


class InvalidModelConfiguration(SpinPathException):
"""Invalid model configuration."""


class InvalidRegistryConfiguration(SpinPathException):
"""Invalid model zoo registry configuration."""
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 361c056

Please sign in to comment.