Skip to content

Commit

Permalink
Feat/qupath (#209)
Browse files Browse the repository at this point in the history
* added qupath flag in CLI tool, create project if flag is true

* added paquo dependency, formatted code

* moved qupath output directory under results_dir, moved paquo to optional dependencies

* created qupath extra in optional dependencies

* Update pyproject.toml

* removed type QuPathProject

* handled paquo import using global variable HAS_PAQUO

* included paquo in mypy ignored modules

* checck if qupath_proj is object

* ignore qupath_proj type

* reformatted code

* made paquo import global

---------

Co-authored-by: Jakub Kaczmarzyk <[email protected]>
  • Loading branch information
swaradgat19 and kaczmarj authored Jan 5, 2024
1 parent 9f77580 commit 2abd323
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,9 @@ cython_debug/
# Version
/wsinfer/_version.py

#QuPath Project
model-outputs-qupath/
*.backup

# Extras
.DS_Store
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ docs = [
"sphinx-copybutton",
]
openslide = ["openslide-python"]
qupath = ["paquo"]

[project.urls]
Homepage = "https://wsinfer.readthedocs.io"
Expand Down Expand Up @@ -125,6 +126,7 @@ module = [
"skimage.morphology",
"tifffile",
"zarr.storage",
"paquo.*"
]
ignore_missing_imports = "True"

Expand Down
11 changes: 11 additions & 0 deletions wsinfer/cli/infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from ..modellib.run_inference import run_inference
from ..patchlib import segment_and_patch_directory_of_slides
from ..write_geojson import write_geojsons
from ..qupath import make_qupath_project


def _num_cpus() -> int:
Expand Down Expand Up @@ -252,6 +253,13 @@ def get_stdout(args: list[str]) -> str:
help="JIT-compile the model and apply inference optimizations. This imposes a"
" startup cost but may improve performance overall.",
)
@click.option(
"--qupath",
is_flag=True,
default=False,
show_default=True,
help="Create a QuPath project containing the inference results",
)
def run(
ctx: click.Context,
*,
Expand All @@ -263,6 +271,7 @@ def run(
batch_size: int,
num_workers: int = 0,
speedup: bool = False,
qupath: bool = False,
) -> None:
"""Run model inference on a directory of whole slide images.
Expand Down Expand Up @@ -378,3 +387,5 @@ def run(

csvs = list((results_dir / "model-outputs-csv").glob("*.csv"))
write_geojsons(csvs, results_dir, num_workers)
if qupath:
make_qupath_project(wsi_dir, results_dir)
71 changes: 71 additions & 0 deletions wsinfer/qupath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from __future__ import annotations

import sys
import json
from pathlib import Path

try:
from paquo.projects import QuPathProject

HAS_PAQUO = True
except Exception:
HAS_PAQUO = False


def add_image_and_geojson(
qupath_proj: QuPathProject,
*,
image_path: Path | str,
geojson_path: Path | str,
) -> None:
with open(geojson_path) as f:
try:
geojson_features = json.load(f)["features"]
except Exception as e:
print(f"Unable to find features key:: {e}")

entry = qupath_proj.add_image(image_path)
try:
entry.hierarchy.load_geojson(geojson_features)
except Exception as e:
print(f"Failed to run load_geojson function with error:: {e}")


def make_qupath_project(wsi_dir: Path, results_dir: Path) -> None:
if not HAS_PAQUO:
print(
"""Cannot find QuPath.
QuPath is required to use this functionality but it cannot be found.
If QuPath is installed, please use define the environment variable
PAQUO_QUPATH_DIR with the location of the QuPath installation.
If QuPath is not installed, please install it from https://qupath.github.io/."""
)
sys.exit(1)
else:
print("Found QuPath successfully!")
QUPATH_PROJECT_DIRECTORY = results_dir / "model-outputs-qupath"

csv_files = list((results_dir / "model-outputs-csv").glob("*.csv"))
slides_and_geojsons = []

for csv_file in csv_files:
file_name = csv_file.stem

json_file = results_dir / "model-outputs-geojson" / (file_name + ".json")
image_file = wsi_dir / (file_name + ".svs")

if json_file.exists() and image_file.exists():
matching_pair = (image_file, json_file)
slides_and_geojsons.append(matching_pair)
else:
print(f"Skipping CSV: {csv_file.name} (No corresponding JSON)")

with QuPathProject(QUPATH_PROJECT_DIRECTORY, mode="w") as qp:
for image_path, geojson_path in slides_and_geojsons:
try:
add_image_and_geojson(
qp, image_path=image_path, geojson_path=geojson_path
)
except Exception as e:
print(f"Failed to add image/geojson with error:: {e}")
print("Successfully created QuPath Project!")

0 comments on commit 2abd323

Please sign in to comment.