Skip to content

Commit 95e3b06

Browse files
committed
handle symlinked slides dirs
fixes #214
1 parent 73dd4ea commit 95e3b06

File tree

5 files changed

+54
-36
lines changed

5 files changed

+54
-36
lines changed

tests/test_all.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,3 +532,28 @@ def test_issue_203(tiff_image: Path) -> None:
532532
img = oslide.read_region((w, h), level=0, size=(256, 256))
533533
assert img.size == (256, 256)
534534
assert np.allclose(np.array(img), 0)
535+
536+
537+
def test_issue_214(tmp_path: Path, tiff_image: Path) -> None:
538+
"""Test that symlinked slides don't mess things up."""
539+
link = tmp_path / "forlinks" / "arbitrary-link-name.tiff"
540+
link.parent.mkdir(parents=True)
541+
link.symlink_to(tiff_image)
542+
543+
runner = CliRunner()
544+
results_dir = tmp_path / "inference"
545+
result = runner.invoke(
546+
cli,
547+
[
548+
"run",
549+
"--wsi-dir",
550+
str(link.parent),
551+
"--results-dir",
552+
str(results_dir),
553+
"--model",
554+
"breast-tumor-resnet34.tcga-brca",
555+
],
556+
)
557+
assert result.exit_code == 0
558+
assert (results_dir / "patches" / link.with_suffix(".h5").name).exists()
559+
assert (results_dir / "model-outputs-csv" / link.with_suffix(".csv").name).exists()

wsinfer/cli/convert_csv_to_sbubmi.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -249,21 +249,17 @@ def get_color(row: pd.Series) -> tuple[float, float, float]:
249249
@click.command()
250250
@click.argument(
251251
"results_dir",
252-
type=click.Path(
253-
exists=True, file_okay=False, dir_okay=True, path_type=Path, resolve_path=True
254-
),
252+
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
255253
)
256254
@click.argument(
257255
"output",
258-
type=click.Path(exists=False, path_type=Path, resolve_path=True),
256+
type=click.Path(exists=False, path_type=Path),
259257
)
260258
@click.option(
261259
"--wsi-dir",
262260
required=True,
263261
help="Directory with whole slide images.",
264-
type=click.Path(
265-
exists=True, file_okay=False, dir_okay=True, path_type=Path, resolve_path=True
266-
),
262+
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
267263
)
268264
@click.option("--execution-id", required=True, help="Unique execution ID for this run.")
269265
@click.option("--study-id", required=True, help="Study ID, like TCGA-BRCA.")

wsinfer/cli/infer.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,15 +188,15 @@ def get_stdout(args: list[str]) -> str:
188188
@click.option(
189189
"-i",
190190
"--wsi-dir",
191-
type=click.Path(exists=True, file_okay=False, path_type=Path, resolve_path=True),
191+
type=click.Path(exists=True, file_okay=False, path_type=Path),
192192
required=True,
193193
help="Directory containing whole slide images. This directory can *only* contain"
194194
" whole slide images.",
195195
)
196196
@click.option(
197197
"-o",
198198
"--results-dir",
199-
type=click.Path(file_okay=False, path_type=Path, resolve_path=True),
199+
type=click.Path(file_okay=False, path_type=Path),
200200
required=True,
201201
help="Directory to store results. If directory exists, will skip"
202202
" whole slides for which outputs exist.",
@@ -212,7 +212,7 @@ def get_stdout(args: list[str]) -> str:
212212
@click.option(
213213
"-c",
214214
"--config",
215-
type=click.Path(exists=True, dir_okay=False, path_type=Path, resolve_path=True),
215+
type=click.Path(exists=True, dir_okay=False, path_type=Path),
216216
help=(
217217
"Path to configuration for the trained model. Use this option if the"
218218
" model weights are not registered in wsinfer. Mutually exclusive with"
@@ -222,7 +222,7 @@ def get_stdout(args: list[str]) -> str:
222222
@click.option(
223223
"-p",
224224
"--model-path",
225-
type=click.Path(exists=True, dir_okay=False, path_type=Path, resolve_path=True),
225+
type=click.Path(exists=True, dir_okay=False, path_type=Path),
226226
help=(
227227
"Path to the pretrained model. Use only when --config is passed. Mutually "
228228
"exclusive with --model."
@@ -349,9 +349,6 @@ def run(
349349
"--config and --model-path must both be set if one is set."
350350
)
351351

352-
wsi_dir = wsi_dir.resolve()
353-
results_dir = results_dir.resolve()
354-
355352
if not wsi_dir.exists():
356353
raise FileNotFoundError(f"Whole slide image directory not found: {wsi_dir}")
357354

wsinfer/cli/patch.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
@click.option(
1212
"-i",
1313
"--wsi-dir",
14-
type=click.Path(exists=True, file_okay=False, path_type=Path, resolve_path=True),
14+
type=click.Path(exists=True, file_okay=False, path_type=Path),
1515
required=True,
1616
help="Directory containing whole slide images. This directory can *only* contain"
1717
" whole slide images.",
1818
)
1919
@click.option(
2020
"-o",
2121
"--results-dir",
22-
type=click.Path(file_okay=False, path_type=Path, resolve_path=True),
22+
type=click.Path(file_okay=False, path_type=Path),
2323
required=True,
2424
help="Directory to store patch results. If directory exists, will skip"
2525
" whole slides for which outputs exist.",
@@ -32,41 +32,41 @@
3232
help="Physical spacing of the patch in micrometers per pixel.",
3333
)
3434
@click.option(
35-
"--thumbsize",
35+
"--seg-thumbsize",
3636
default=(2048, 2048),
3737
type=(int, int),
3838
help="The size of the slide thumbnail (in pixels) used for tissue segmentation."
3939
" The aspect ratio is preserved, and the longest side will have length"
4040
" max(thumbsize).",
4141
)
4242
@click.option(
43-
"--median-filter-size",
43+
"--seg-median-filter-size",
4444
default=7,
4545
type=click.IntRange(min=3),
4646
help="The kernel size for median filtering. Must be greater than 1 and odd.",
4747
)
4848
@click.option(
49-
"--binary-threshold",
49+
"--seg-binary-threshold",
5050
default=7,
5151
type=click.IntRange(min=1),
5252
help="The threshold for image binarization.",
5353
)
5454
@click.option(
55-
"--closing-kernel-size",
55+
"--seg-closing-kernel-size",
5656
default=6,
5757
type=click.IntRange(min=1),
5858
help="The kernel size for binary closing (morphological operation).",
5959
)
6060
@click.option(
61-
"--min-object-size-um2",
61+
"--seg-min-object-size-um2",
6262
default=200**2,
6363
type=click.FloatRange(min=0),
6464
help="The minimum size of an object to keep during tissue detection. If a"
6565
" contiguous object is smaller than this area, it replaced with background."
6666
" The default is 200um x 200um. The units of this argument are microns squared.",
6767
)
6868
@click.option(
69-
"--min-hole-size-um2",
69+
"--seg-min-hole-size-um2",
7070
default=190**2,
7171
type=click.FloatRange(min=0),
7272
help="The minimum size of a hole to keep as a hole. If a hole is smaller than this"
@@ -78,23 +78,23 @@ def patch(
7878
results_dir: str,
7979
patch_size_px: int,
8080
patch_spacing_um_px: float,
81-
thumbsize: tuple[int, int],
82-
median_filter_size: int,
83-
binary_threshold: int,
84-
closing_kernel_size: int,
85-
min_object_size_um2: float,
86-
min_hole_size_um2: float,
81+
seg_thumbsize: tuple[int, int],
82+
seg_median_filter_size: int,
83+
seg_binary_threshold: int,
84+
seg_closing_kernel_size: int,
85+
seg_min_object_size_um2: float,
86+
seg_min_hole_size_um2: float,
8787
) -> None:
8888
"""Patch a directory of whole slide iamges."""
8989
segment_and_patch_directory_of_slides(
9090
wsi_dir=wsi_dir,
9191
save_dir=results_dir,
9292
patch_size_px=patch_size_px,
9393
patch_spacing_um_px=patch_spacing_um_px,
94-
thumbsize=thumbsize,
95-
median_filter_size=median_filter_size,
96-
binary_threshold=binary_threshold,
97-
closing_kernel_size=closing_kernel_size,
98-
min_object_size_um2=min_object_size_um2,
99-
min_hole_size_um2=min_hole_size_um2,
94+
thumbsize=seg_thumbsize,
95+
median_filter_size=seg_median_filter_size,
96+
binary_threshold=seg_binary_threshold,
97+
closing_kernel_size=seg_closing_kernel_size,
98+
min_object_size_um2=seg_min_object_size_um2,
99+
min_hole_size_um2=seg_min_hole_size_um2,
100100
)

wsinfer/patchlib/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ def segment_and_patch_one_slide(
8585
None
8686
"""
8787

88-
save_dir = Path(save_dir).resolve()
89-
slide_path = Path(slide_path).resolve()
88+
save_dir = Path(save_dir)
89+
slide_path = Path(slide_path)
9090
slide_prefix = slide_path.stem
9191

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

0 commit comments

Comments
 (0)