diff --git a/.github/workflows/cli-test.yml b/.github/workflows/cli-test-linux.yml similarity index 73% rename from .github/workflows/cli-test.yml rename to .github/workflows/cli-test-linux.yml index 4c0f67a..4d42813 100644 --- a/.github/workflows/cli-test.yml +++ b/.github/workflows/cli-test-linux.yml @@ -1,4 +1,4 @@ -name: Test CLI +name: Linux on: push: branches: [ main ] @@ -6,15 +6,16 @@ on: branches: [ main ] jobs: Test-CLI: + runs-on: ubuntu-latest strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Cache sample WSI uses: actions/cache@v3 with: path: ~/wsi/ + key: sample-wsi - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 @@ -24,14 +25,17 @@ jobs: 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] + python -m pip install . - name: Download sample WSI run: | + mkdir -p ~/wsi cd ~/wsi - wget -nc https://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1.svs + 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. + # Test it twice so the second time we get cache hits. run: | cd /tmp 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 diff --git a/.github/workflows/cli-test-macos.yml b/.github/workflows/cli-test-macos.yml new file mode 100644 index 0000000..968e5ca --- /dev/null +++ b/.github/workflows/cli-test-macos.yml @@ -0,0 +1,41 @@ +name: macOS +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] +jobs: + Test-CLI: + runs-on: macos-latest + strategy: + matrix: + python-version: ["3.10"] + steps: + - name: Cache sample WSI + uses: actions/cache@v3 + with: + path: ~/wsi/ + key: sample-wsi + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install WSInfer-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 + python -m pip install . + - name: Download sample WSI + run: | + mkdir -p ~/wsi + cd ~/wsi + 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. + # Test it twice so the second time we get cache hits. + run: | + cd /tmp + 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 diff --git a/.github/workflows/cli-test-windows.yml b/.github/workflows/cli-test-windows.yml new file mode 100644 index 0000000..4edc3ec --- /dev/null +++ b/.github/workflows/cli-test-windows.yml @@ -0,0 +1,44 @@ +name: Windows +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] +jobs: + Test-CLI: + runs-on: windows-latest + strategy: + matrix: + python-version: ["3.10"] + steps: + - name: Cache sample WSI + id: cache-wsi + uses: actions/cache@v3 + with: + path: ~/wsi/ + key: sample-wsi + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install WSInfer-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 + python -m pip install . + - name: Download sample WSI + if: steps.cache-wsi.outputs.cache-hit != 'true' + run: | + mkdir -p ~/wsi + cd ~/wsi + 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. + # Test it twice so the second time we get cache hits. + run: | + mkdir -p ~/foobar + cd ~/foobar + 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 diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml new file mode 100644 index 0000000..f584be6 --- /dev/null +++ b/.github/workflows/mypy.yml @@ -0,0 +1,25 @@ +name: Static Analysis +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] +jobs: + MyPy: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install WSInfer-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 + python -m pip install .[dev] mypy + - name: Run mypy + run: python -m mypy --install-types --non-interactive wsinfer_mil diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index b268138..f79f5af 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -1,5 +1,9 @@ name: Ruff -on: [push, pull_request] +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] jobs: ruff: runs-on: ubuntu-latest diff --git a/pyproject.toml b/pyproject.toml index 1e3aa82..f312504 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ dependencies = [ "click>=8.0,<9,!=8.1.4,!=8.1.5", "h5py", "huggingface_hub", + "jsonschema", # This is when numpy.typing.NDArray was introduced. "numpy>=1.21.0", "opencv-python-headless>=4.0.0", @@ -64,7 +65,6 @@ dynamic = ["version"] [project.optional-dependencies] dev = [ "geojson", - "jsonschema", "mypy", "pre-commit", "pytest", diff --git a/wsinfer_mil/__main__.py b/wsinfer_mil/__main__.py index b26dfba..9834c0b 100644 --- a/wsinfer_mil/__main__.py +++ b/wsinfer_mil/__main__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from wsinfer_mil.cli import cli if __name__ == "__main__": diff --git a/wsinfer_mil/cli.py b/wsinfer_mil/cli.py index 460900b..921855b 100644 --- a/wsinfer_mil/cli.py +++ b/wsinfer_mil/cli.py @@ -1,5 +1,7 @@ """Command line interface for WSInfer MIL.""" +from __future__ import annotations + import logging import os from pathlib import Path @@ -80,9 +82,10 @@ def cli() -> None: @click.option( "-j", "--num-workers", - help="Number of workers to use during patch feature extraction", - type=click.IntRange(min=0, max=os.cpu_count()), - default=4, + help="Number of workers to use during patch feature extraction. -1 (default) uses" + " all cores.", + type=click.IntRange(min=-1, max=os.cpu_count()), + default=-1, show_default=True, ) @click.option( @@ -104,6 +107,8 @@ def run( json: bool, ) -> None: model = load_torchscript_model_from_hf(hf_repo_id, hf_repo_revision) + if num_workers == -1: + num_workers = os.cpu_count() or 0 _run_impl( wsi_path=wsi_path, model=model, diff --git a/wsinfer_mil/client/__init__.py b/wsinfer_mil/client/__init__.py index e69de29..9d48db4 100644 --- a/wsinfer_mil/client/__init__.py +++ b/wsinfer_mil/client/__init__.py @@ -0,0 +1 @@ +from __future__ import annotations diff --git a/wsinfer_mil/defaults.py b/wsinfer_mil/defaults.py index c9fa900..f74d6a1 100644 --- a/wsinfer_mil/defaults.py +++ b/wsinfer_mil/defaults.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path from platformdirs import user_cache_dir diff --git a/wsinfer_mil/errors.py b/wsinfer_mil/errors.py index 94aa81d..45c694c 100644 --- a/wsinfer_mil/errors.py +++ b/wsinfer_mil/errors.py @@ -1,5 +1,7 @@ """Errors for WSInfer MIL.""" +from __future__ import annotations + class WSInferMILException(Exception): """Base exception for WSInfer MIL.""" diff --git a/wsinfer_mil/wsi_utils.py b/wsinfer_mil/wsi_utils.py index 8f64ee0..56fbe3a 100644 --- a/wsinfer_mil/wsi_utils.py +++ b/wsinfer_mil/wsi_utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import tiffslide from tiffslide.tiffslide import PROPERTY_NAME_MPP_X from tiffslide.tiffslide import PROPERTY_NAME_MPP_Y