lavlab-shell 0.3.2__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/PKG-INFO +1 -1
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/src/shell/__about__.py +1 -1
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/src/shell/cli.py +63 -2
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/src/shell/infer_omero_wsi.py +49 -8
- lavlab_shell-0.4.0/src/shell/infer_wsi.py +796 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/src/shell/inference.py +27 -15
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/src/shell/model.py +14 -3
- lavlab_shell-0.4.0/src/shell/post_process.py +1349 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/src/shell/weights/model_v1.pth +0 -0
- lavlab_shell-0.3.2/src/shell/infer_wsi.py +0 -373
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/.devcontainer/Dockerfile +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/.editorconfig +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/.github/dependabot.yml +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/.github/workflows/build.yml +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/.github/workflows/lint.yml +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/.github/workflows/publish.yml +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/.github/workflows/pytest.yml +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/.gitignore +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/.pre-commit-config.yaml +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/CONTRIBUTING.md +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/Dockerfile +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/LICENSE.txt +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/Makefile +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/README.md +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/docs/api.md +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/docs/index.md +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/mkdocs.yml +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/pyproject.toml +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/requirements/requirements-docs.txt +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/requirements/requirements-lint.txt +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/requirements/requirements-test.txt +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/requirements/requirements-types.txt +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/requirements.txt +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/src/shell/__init__.py +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/src/shell/benchmark.py +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/src/shell/py.typed +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/src/shell/transforms.py +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/tests/__init__.py +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/tests/conftest.py +0 -0
- {lavlab_shell-0.3.2 → lavlab_shell-0.4.0}/tests/test_shell.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lavlab-shell
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: SHELL Highlights Epithelium and Lumen Locations — whole-slide H&E segmentation
|
|
5
5
|
Project-URL: Documentation, https://github.com/laviolette-lab/shell#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/laviolette-lab/shell/issues
|
|
@@ -105,7 +105,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
105
105
|
infer_p.add_argument(
|
|
106
106
|
"--target-mpp",
|
|
107
107
|
type=float,
|
|
108
|
-
default=
|
|
108
|
+
default=2.0,
|
|
109
109
|
help="Desired output resolution (um/pixel).",
|
|
110
110
|
)
|
|
111
111
|
infer_p.add_argument(
|
|
@@ -125,6 +125,51 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
125
125
|
default=None,
|
|
126
126
|
help="Optional: also save the intermediate EHO image.",
|
|
127
127
|
)
|
|
128
|
+
infer_p.add_argument(
|
|
129
|
+
"--save-raw",
|
|
130
|
+
type=str,
|
|
131
|
+
default=None,
|
|
132
|
+
help=(
|
|
133
|
+
"Optional: save raw model predictions as a 3-band uint8 image "
|
|
134
|
+
"(band 0 = inner / lumen, band 1 = outer / epithelium, "
|
|
135
|
+
"band 2 = background), scaled to 0/255. "
|
|
136
|
+
"Saved at the original input resolution. "
|
|
137
|
+
"Useful for debugging or re-running post-processing offline."
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
infer_p.add_argument(
|
|
141
|
+
"--profile",
|
|
142
|
+
type=str,
|
|
143
|
+
default="best_effort",
|
|
144
|
+
choices=["best_effort", "precise", "sensitive"],
|
|
145
|
+
help=(
|
|
146
|
+
"Post-processing filter profile. "
|
|
147
|
+
"'best_effort' (default) balances sensitivity and precision; "
|
|
148
|
+
"'precise' is more conservative; 'sensitive' keeps more predictions."
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
infer_p.add_argument(
|
|
152
|
+
"--mode",
|
|
153
|
+
type=str,
|
|
154
|
+
default="wsi",
|
|
155
|
+
choices=["wsi", "biopsy", "tile"],
|
|
156
|
+
help=(
|
|
157
|
+
"Post-processing mode. "
|
|
158
|
+
"'wsi' (default): full pipeline with tissue restriction and urethra detection. "
|
|
159
|
+
"'biopsy': tissue restriction but no urethra detection. "
|
|
160
|
+
"'tile': no tissue mask/urethra; reflect-pads predictions before morphological ops. "
|
|
161
|
+
"Use 'tile' for individual image tiles that lack surrounding context."
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
infer_p.add_argument(
|
|
165
|
+
"--tile-pad",
|
|
166
|
+
type=int,
|
|
167
|
+
default=None,
|
|
168
|
+
help=(
|
|
169
|
+
"Reflect-padding in pixels applied on each side in tile mode. "
|
|
170
|
+
"Defaults to 50%% of the shorter output dimension when not set."
|
|
171
|
+
),
|
|
172
|
+
)
|
|
128
173
|
infer_p.add_argument(
|
|
129
174
|
"--device",
|
|
130
175
|
type=str,
|
|
@@ -156,7 +201,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
156
201
|
omero_p.add_argument(
|
|
157
202
|
"--target-mpp",
|
|
158
203
|
type=float,
|
|
159
|
-
default=
|
|
204
|
+
default=2.0,
|
|
160
205
|
help="Desired output resolution (um/pixel)",
|
|
161
206
|
)
|
|
162
207
|
omero_p.add_argument(
|
|
@@ -252,6 +297,17 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
252
297
|
"sessions. Default 4."
|
|
253
298
|
),
|
|
254
299
|
)
|
|
300
|
+
omero_p.add_argument(
|
|
301
|
+
"--profile",
|
|
302
|
+
type=str,
|
|
303
|
+
default="best_effort",
|
|
304
|
+
choices=["best_effort", "precise", "sensitive"],
|
|
305
|
+
help=(
|
|
306
|
+
"Post-processing filter profile. "
|
|
307
|
+
"'best_effort' (default) balances sensitivity and precision; "
|
|
308
|
+
"'precise' is more conservative; 'sensitive' keeps more predictions."
|
|
309
|
+
),
|
|
310
|
+
)
|
|
255
311
|
|
|
256
312
|
return parser
|
|
257
313
|
|
|
@@ -303,6 +359,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
303
359
|
target_mpp=args.target_mpp,
|
|
304
360
|
mpp=args.mpp,
|
|
305
361
|
save_eho=args.save_eho,
|
|
362
|
+
save_raw=args.save_raw,
|
|
363
|
+
profile=args.profile,
|
|
364
|
+
mode=args.mode,
|
|
365
|
+
tile_pad=args.tile_pad,
|
|
306
366
|
device=args.device,
|
|
307
367
|
)
|
|
308
368
|
|
|
@@ -351,6 +411,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
351
411
|
min_tissue_frac=args.min_tissue_frac,
|
|
352
412
|
prefetch_depth=args.prefetch_depth,
|
|
353
413
|
num_fetch_workers=args.fetch_workers,
|
|
414
|
+
profile=args.profile,
|
|
354
415
|
)
|
|
355
416
|
log.debug("DIAG: infer_omero_wsi returned normally")
|
|
356
417
|
except BaseException as e:
|
|
@@ -1292,6 +1292,7 @@ def infer_omero_wsi(
|
|
|
1292
1292
|
min_tissue_frac: float = 0.01,
|
|
1293
1293
|
prefetch_depth: int = 8,
|
|
1294
1294
|
num_fetch_workers: int = 4,
|
|
1295
|
+
profile: str = "best_effort",
|
|
1295
1296
|
) -> np.ndarray:
|
|
1296
1297
|
"""Tile-based OMERO inference pipeline.
|
|
1297
1298
|
|
|
@@ -1633,6 +1634,15 @@ def infer_omero_wsi(
|
|
|
1633
1634
|
)
|
|
1634
1635
|
tiles_tissue = len(tile_schedule)
|
|
1635
1636
|
|
|
1637
|
+
# Upscale tissue mask to full resolution for post-processing.
|
|
1638
|
+
th_h, th_w = tissue_mask.shape
|
|
1639
|
+
y_idx = np.clip(
|
|
1640
|
+
(np.arange(out_h) * th_h / out_h).astype(np.int64), 0, th_h - 1,
|
|
1641
|
+
)
|
|
1642
|
+
x_idx = np.clip(
|
|
1643
|
+
(np.arange(out_w) * th_w / out_w).astype(np.int64), 0, th_w - 1,
|
|
1644
|
+
)
|
|
1645
|
+
tissue_mask_full = tissue_mask[y_idx[:, None], x_idx[None, :]]
|
|
1636
1646
|
del tissue_mask
|
|
1637
1647
|
gc.collect()
|
|
1638
1648
|
|
|
@@ -1691,6 +1701,8 @@ def infer_omero_wsi(
|
|
|
1691
1701
|
tile_overlap=tile_overlap,
|
|
1692
1702
|
roi_size=inference_tile_size,
|
|
1693
1703
|
sw_overlap=sw_overlap,
|
|
1704
|
+
tissue_mask_full=tissue_mask_full,
|
|
1705
|
+
profile=profile,
|
|
1694
1706
|
num_fetch_workers=num_fetch_workers,
|
|
1695
1707
|
)
|
|
1696
1708
|
|
|
@@ -1739,6 +1751,8 @@ def _run_pipeline(
|
|
|
1739
1751
|
tile_overlap: int,
|
|
1740
1752
|
roi_size: int,
|
|
1741
1753
|
sw_overlap: float,
|
|
1754
|
+
tissue_mask_full: np.ndarray | None = None,
|
|
1755
|
+
profile: str = "best_effort",
|
|
1742
1756
|
num_fetch_workers: int = 4,
|
|
1743
1757
|
) -> np.ndarray:
|
|
1744
1758
|
"""Producer/consumer pipeline: parallel fetch+EHO → inference in main.
|
|
@@ -1820,7 +1834,10 @@ def _run_pipeline(
|
|
|
1820
1834
|
except Exception:
|
|
1821
1835
|
logger.exception("Failed to start OMERO fetch thread; continuing.")
|
|
1822
1836
|
|
|
1823
|
-
|
|
1837
|
+
# Allocate canvases for inner/outer predictions and hematoxylin.
|
|
1838
|
+
inner_pred = np.zeros((out_h, out_w), dtype=bool)
|
|
1839
|
+
outer_pred = np.zeros((out_h, out_w), dtype=bool)
|
|
1840
|
+
hematoxylin_full = np.zeros((out_h, out_w), dtype=np.uint8)
|
|
1824
1841
|
|
|
1825
1842
|
eho_canvas: np.ndarray | None = None
|
|
1826
1843
|
if save_eho:
|
|
@@ -1840,25 +1857,34 @@ def _run_pipeline(
|
|
|
1840
1857
|
if eho_canvas is not None:
|
|
1841
1858
|
eho_canvas[oy0 : oy0 + oh, ox0 : ox0 + ow] = tile_eho
|
|
1842
1859
|
|
|
1843
|
-
|
|
1860
|
+
# Store hematoxylin channel (ch1) for nuclei segmentation.
|
|
1861
|
+
hematoxylin_full[oy0 : oy0 + oh, ox0 : ox0 + ow] = tile_eho[:, :, 1]
|
|
1862
|
+
|
|
1863
|
+
inner_tile, outer_tile = run_inference(
|
|
1844
1864
|
tile_eho,
|
|
1845
1865
|
model,
|
|
1846
1866
|
device,
|
|
1847
1867
|
roi_size=(roi_size, roi_size),
|
|
1848
1868
|
overlap=sw_overlap,
|
|
1869
|
+
return_raw=True,
|
|
1849
1870
|
)
|
|
1850
1871
|
del tile_eho
|
|
1851
1872
|
|
|
1852
1873
|
# Write only the centre-cropped keep region to avoid seams.
|
|
1853
|
-
ph, pw =
|
|
1874
|
+
ph, pw = inner_tile.shape[:2]
|
|
1854
1875
|
ky1 = min(keep_y0 + keep_h, ph)
|
|
1855
1876
|
kx1 = min(keep_x0 + keep_w, pw)
|
|
1856
|
-
kept = tile_pred[keep_y0:ky1, keep_x0:kx1]
|
|
1857
1877
|
out_y0 = oy0 + keep_y0
|
|
1858
1878
|
out_x0 = ox0 + keep_x0
|
|
1859
|
-
ch
|
|
1860
|
-
|
|
1861
|
-
|
|
1879
|
+
ch = ky1 - keep_y0
|
|
1880
|
+
cw = kx1 - keep_x0
|
|
1881
|
+
inner_pred[out_y0 : out_y0 + ch, out_x0 : out_x0 + cw] = (
|
|
1882
|
+
inner_tile[keep_y0:ky1, keep_x0:kx1]
|
|
1883
|
+
)
|
|
1884
|
+
outer_pred[out_y0 : out_y0 + ch, out_x0 : out_x0 + cw] = (
|
|
1885
|
+
outer_tile[keep_y0:ky1, keep_x0:kx1]
|
|
1886
|
+
)
|
|
1887
|
+
del inner_tile, outer_tile
|
|
1862
1888
|
|
|
1863
1889
|
processed += 1
|
|
1864
1890
|
if processed % 25 == 0:
|
|
@@ -1876,7 +1902,22 @@ def _run_pipeline(
|
|
|
1876
1902
|
if torch.cuda.is_available():
|
|
1877
1903
|
torch.cuda.empty_cache()
|
|
1878
1904
|
|
|
1879
|
-
|
|
1905
|
+
# ── Post-processing ──────────────────────────────────────────
|
|
1906
|
+
from shell.post_process import post_process
|
|
1907
|
+
|
|
1908
|
+
logger.info("Running post-processing (profile=%s) …", profile)
|
|
1909
|
+
label_map = post_process(
|
|
1910
|
+
inner_pred,
|
|
1911
|
+
outer_pred,
|
|
1912
|
+
tissue_mask_full if tissue_mask_full is not None else np.ones((out_h, out_w), dtype=bool),
|
|
1913
|
+
hematoxylin_full,
|
|
1914
|
+
profile_name=profile,
|
|
1915
|
+
verbose=True,
|
|
1916
|
+
)
|
|
1917
|
+
del inner_pred, outer_pred, hematoxylin_full, tissue_mask_full
|
|
1918
|
+
gc.collect()
|
|
1919
|
+
|
|
1920
|
+
return _save_results(label_map, eho_canvas, save_eho, output_path)
|
|
1880
1921
|
|
|
1881
1922
|
finally:
|
|
1882
1923
|
fetch_thread.join(timeout=30)
|