Skip to content

Commit

Permalink
handle symlinked slides dirs
Browse files Browse the repository at this point in the history
fixes #214
  • Loading branch information
kaczmarj committed Feb 22, 2024
1 parent 73dd4ea commit 95e3b06
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 36 deletions.
25 changes: 25 additions & 0 deletions tests/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,3 +532,28 @@ def test_issue_203(tiff_image: Path) -> None:
img = oslide.read_region((w, h), level=0, size=(256, 256))
assert img.size == (256, 256)
assert np.allclose(np.array(img), 0)


def test_issue_214(tmp_path: Path, tiff_image: Path) -> None:
"""Test that symlinked slides don't mess things up."""
link = tmp_path / "forlinks" / "arbitrary-link-name.tiff"
link.parent.mkdir(parents=True)
link.symlink_to(tiff_image)

runner = CliRunner()
results_dir = tmp_path / "inference"
result = runner.invoke(
cli,
[
"run",
"--wsi-dir",
str(link.parent),
"--results-dir",
str(results_dir),
"--model",
"breast-tumor-resnet34.tcga-brca",
],
)
assert result.exit_code == 0
assert (results_dir / "patches" / link.with_suffix(".h5").name).exists()
assert (results_dir / "model-outputs-csv" / link.with_suffix(".csv").name).exists()
10 changes: 3 additions & 7 deletions wsinfer/cli/convert_csv_to_sbubmi.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,21 +249,17 @@ def get_color(row: pd.Series) -> tuple[float, float, float]:
@click.command()
@click.argument(
"results_dir",
type=click.Path(
exists=True, file_okay=False, dir_okay=True, path_type=Path, resolve_path=True
),
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
)
@click.argument(
"output",
type=click.Path(exists=False, path_type=Path, resolve_path=True),
type=click.Path(exists=False, path_type=Path),
)
@click.option(
"--wsi-dir",
required=True,
help="Directory with whole slide images.",
type=click.Path(
exists=True, file_okay=False, dir_okay=True, path_type=Path, resolve_path=True
),
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
)
@click.option("--execution-id", required=True, help="Unique execution ID for this run.")
@click.option("--study-id", required=True, help="Study ID, like TCGA-BRCA.")
Expand Down
11 changes: 4 additions & 7 deletions wsinfer/cli/infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,15 @@ def get_stdout(args: list[str]) -> str:
@click.option(
"-i",
"--wsi-dir",
type=click.Path(exists=True, file_okay=False, path_type=Path, resolve_path=True),
type=click.Path(exists=True, file_okay=False, path_type=Path),
required=True,
help="Directory containing whole slide images. This directory can *only* contain"
" whole slide images.",
)
@click.option(
"-o",
"--results-dir",
type=click.Path(file_okay=False, path_type=Path, resolve_path=True),
type=click.Path(file_okay=False, path_type=Path),
required=True,
help="Directory to store results. If directory exists, will skip"
" whole slides for which outputs exist.",
Expand All @@ -212,7 +212,7 @@ def get_stdout(args: list[str]) -> str:
@click.option(
"-c",
"--config",
type=click.Path(exists=True, dir_okay=False, path_type=Path, resolve_path=True),
type=click.Path(exists=True, dir_okay=False, path_type=Path),
help=(
"Path to configuration for the trained model. Use this option if the"
" model weights are not registered in wsinfer. Mutually exclusive with"
Expand All @@ -222,7 +222,7 @@ def get_stdout(args: list[str]) -> str:
@click.option(
"-p",
"--model-path",
type=click.Path(exists=True, dir_okay=False, path_type=Path, resolve_path=True),
type=click.Path(exists=True, dir_okay=False, path_type=Path),
help=(
"Path to the pretrained model. Use only when --config is passed. Mutually "
"exclusive with --model."
Expand Down Expand Up @@ -349,9 +349,6 @@ def run(
"--config and --model-path must both be set if one is set."
)

wsi_dir = wsi_dir.resolve()
results_dir = results_dir.resolve()

if not wsi_dir.exists():
raise FileNotFoundError(f"Whole slide image directory not found: {wsi_dir}")

Expand Down
40 changes: 20 additions & 20 deletions wsinfer/cli/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
@click.option(
"-i",
"--wsi-dir",
type=click.Path(exists=True, file_okay=False, path_type=Path, resolve_path=True),
type=click.Path(exists=True, file_okay=False, path_type=Path),
required=True,
help="Directory containing whole slide images. This directory can *only* contain"
" whole slide images.",
)
@click.option(
"-o",
"--results-dir",
type=click.Path(file_okay=False, path_type=Path, resolve_path=True),
type=click.Path(file_okay=False, path_type=Path),
required=True,
help="Directory to store patch results. If directory exists, will skip"
" whole slides for which outputs exist.",
Expand All @@ -32,41 +32,41 @@
help="Physical spacing of the patch in micrometers per pixel.",
)
@click.option(
"--thumbsize",
"--seg-thumbsize",
default=(2048, 2048),
type=(int, int),
help="The size of the slide thumbnail (in pixels) used for tissue segmentation."
" The aspect ratio is preserved, and the longest side will have length"
" max(thumbsize).",
)
@click.option(
"--median-filter-size",
"--seg-median-filter-size",
default=7,
type=click.IntRange(min=3),
help="The kernel size for median filtering. Must be greater than 1 and odd.",
)
@click.option(
"--binary-threshold",
"--seg-binary-threshold",
default=7,
type=click.IntRange(min=1),
help="The threshold for image binarization.",
)
@click.option(
"--closing-kernel-size",
"--seg-closing-kernel-size",
default=6,
type=click.IntRange(min=1),
help="The kernel size for binary closing (morphological operation).",
)
@click.option(
"--min-object-size-um2",
"--seg-min-object-size-um2",
default=200**2,
type=click.FloatRange(min=0),
help="The minimum size of an object to keep during tissue detection. If a"
" contiguous object is smaller than this area, it replaced with background."
" The default is 200um x 200um. The units of this argument are microns squared.",
)
@click.option(
"--min-hole-size-um2",
"--seg-min-hole-size-um2",
default=190**2,
type=click.FloatRange(min=0),
help="The minimum size of a hole to keep as a hole. If a hole is smaller than this"
Expand All @@ -78,23 +78,23 @@ def patch(
results_dir: str,
patch_size_px: int,
patch_spacing_um_px: float,
thumbsize: tuple[int, int],
median_filter_size: int,
binary_threshold: int,
closing_kernel_size: int,
min_object_size_um2: float,
min_hole_size_um2: float,
seg_thumbsize: tuple[int, int],
seg_median_filter_size: int,
seg_binary_threshold: int,
seg_closing_kernel_size: int,
seg_min_object_size_um2: float,
seg_min_hole_size_um2: float,
) -> None:
"""Patch a directory of whole slide iamges."""
segment_and_patch_directory_of_slides(
wsi_dir=wsi_dir,
save_dir=results_dir,
patch_size_px=patch_size_px,
patch_spacing_um_px=patch_spacing_um_px,
thumbsize=thumbsize,
median_filter_size=median_filter_size,
binary_threshold=binary_threshold,
closing_kernel_size=closing_kernel_size,
min_object_size_um2=min_object_size_um2,
min_hole_size_um2=min_hole_size_um2,
thumbsize=seg_thumbsize,
median_filter_size=seg_median_filter_size,
binary_threshold=seg_binary_threshold,
closing_kernel_size=seg_closing_kernel_size,
min_object_size_um2=seg_min_object_size_um2,
min_hole_size_um2=seg_min_hole_size_um2,
)
4 changes: 2 additions & 2 deletions wsinfer/patchlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ def segment_and_patch_one_slide(
None
"""

save_dir = Path(save_dir).resolve()
slide_path = Path(slide_path).resolve()
save_dir = Path(save_dir)
slide_path = Path(slide_path)
slide_prefix = slide_path.stem

logger.info(f"Segmenting and patching slide {slide_path}")
Expand Down

0 comments on commit 95e3b06

Please sign in to comment.