Skip to content

Commit

Permalink
ADD: pytest tests and run in CI (#45)
Browse files Browse the repository at this point in the history
* add pytest for tests

* add tests and run in ci

* add tifffile and imagecodecs to dev deps

* patch resolution unit for py 3.7

* fix for python 3.7 tifffile

* fix version_info check to be false for 3.7

* remove the --help cmds in tests

* add stubs for tests of converters

* add ci status badge

* add tests of geojson conversion

* use np.allclose instead of == for floats
  • Loading branch information
kaczmarj authored Dec 23, 2022
1 parent 04922ee commit 907dc9f
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 3 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,5 @@ jobs:
python -m pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/cpu
python -m pip install --editable .[dev] --find-links https://girder.github.io/large_image_wheels
- name: Run tests
# TODO: add more tests...
run: |
wsinfer --help
wsinfer run --help
python -m pytest --verbose tests/
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Original H&E | Heatmap of Tumor Probability

🔥 🚀 Blazingly fast pipeline to run patch-based classification models on whole slide images.

![Continuous Integration](https://github.com/kaczmarj/patch-classification-pipeline/actions/workflows/ci.yml/badge.svg)

# Table of contents

- [Available models](#available-models)
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ install_requires =
dev =
black
flake8
imagecodecs # for tifffile
mypy
pytest
tifffile

[options.entry_points]
console_scripts =
Expand Down
141 changes: 141 additions & 0 deletions tests/test_all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import json
from pathlib import Path
import sys

from click.testing import CliRunner
import numpy as np
import pandas as pd
import pytest
import tifffile


@pytest.fixture
def tiff_image(tmp_path: Path) -> Path:
x = np.empty((4096, 4096, 3), dtype="uint8")
x[...] = [160, 32, 240] # rgb for purple
path = Path(tmp_path / "images" / "purple.tif")
path.parent.mkdir(exist_ok=True)

if sys.version_info >= (3, 8):
tifffile.imwrite(
path,
data=x,
compression="zlib",
tile=(256, 256),
# 0.25 micrometers per pixel.
resolution=(40000, 40000),
resolutionunit=tifffile.RESUNIT.CENTIMETER,
)
else:
# Earlier versions of tifffile do not have resolutionunit kwarg.
tifffile.imwrite(
path,
data=x,
compression="zlib",
tile=(256, 256),
# 0.25 micrometers per pixel.
resolution=(40000, 40000, "CENTIMETER"),
)

return path


def test_cli_list():
from wsinfer.cli.cli import cli

runner = CliRunner()
result = runner.invoke(cli, ["list"])
assert "resnet34" in result.output
assert "TCGA-BRCA-v1" in result.output
assert result.exit_code == 0


def test_cli_run_and_convert(tiff_image: Path, tmp_path: Path):
from wsinfer.cli.cli import cli

runner = CliRunner()
results_dir = tmp_path / "inference"
result = runner.invoke(
cli,
[
"run",
"--wsi_dir",
tiff_image.parent,
"--model",
"resnet34",
"--weights",
"TCGA-BRCA-v1",
"--results_dir",
results_dir,
],
)
assert result.exit_code == 0
assert (results_dir / "model-outputs").exists()
df = pd.read_csv(results_dir / "model-outputs" / "purple.csv")
assert df.columns.tolist() == [
"slide",
"minx",
"miny",
"width",
"height",
"prob_notumor",
"prob_tumor",
]
assert (df.loc[:, "slide"] == str(tiff_image)).all()
assert (df.loc[:, "width"] == 350).all()
assert (df.loc[:, "height"] == 350).all()
assert (df.loc[:, "width"] == 350).all()
assert np.allclose(df.loc[:, "prob_notumor"], 0.9525967836380005)
assert np.allclose(df.loc[:, "prob_tumor"], 0.04740329459309578)

# Test conversion scripts.
geojson_dir = results_dir / "geojson"
result = runner.invoke(cli, ["togeojson", str(results_dir), str(geojson_dir)])
assert result.exit_code == 0
with open(geojson_dir / "purple.json") as f:
d = json.load(f)
assert len(d["features"]) == 144

for geojson_row in d["features"]:
assert geojson_row["type"] == "Feature"
assert geojson_row["id"] == "PathTileObject"
assert geojson_row["geometry"]["type"] == "Polygon"

# Check the probability values.
assert all(
np.allclose(dd["properties"]["measurements"][0]["value"], 0.9525967836380004)
for dd in d["features"]
)
assert all(
np.allclose(dd["properties"]["measurements"][1]["value"], 0.0474032945930957)
for dd in d["features"]
)

# Check the names.
assert all(
dd["properties"]["measurements"][0]["name"] == "prob_notumor"
for dd in d["features"]
)
assert all(
dd["properties"]["measurements"][1]["name"] == "prob_tumor"
for dd in d["features"]
)

# Check the coordinate values.
for df_row, geojson_row in zip(df.itertuples(), d["features"]):
maxx = df_row.minx + df_row.width
maxy = df_row.miny + df_row.height
df_coords = [
[maxx, df_row.miny],
[maxx, maxy],
[df_row.minx, maxy],
[df_row.minx, df_row.miny],
[maxx, df_row.miny],
]
assert [df_coords] == geojson_row["geometry"]["coordinates"]


@pytest.mark.xfail
def test_convert_to_sbu():
# TODO: create a synthetic output and then convert it. Check that it is valid.
assert False

0 comments on commit 907dc9f

Please sign in to comment.