lavlab-shell 0.3.1__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.
Potentially problematic release.
This version of lavlab-shell might be problematic. Click here for more details.
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/PKG-INFO +1 -1
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/src/shell/__about__.py +1 -1
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/src/shell/cli.py +63 -2
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/src/shell/infer_omero_wsi.py +52 -8
- lavlab_shell-0.4.0/src/shell/infer_wsi.py +796 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/src/shell/inference.py +28 -16
- {lavlab_shell-0.3.1 → 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.1 → lavlab_shell-0.4.0}/src/shell/weights/model_v1.pth +0 -0
- lavlab_shell-0.3.1/src/shell/infer_wsi.py +0 -373
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/.devcontainer/Dockerfile +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/.editorconfig +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/.github/dependabot.yml +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/.github/workflows/build.yml +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/.github/workflows/lint.yml +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/.github/workflows/publish.yml +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/.github/workflows/pytest.yml +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/.gitignore +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/.pre-commit-config.yaml +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/CONTRIBUTING.md +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/Dockerfile +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/LICENSE.txt +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/Makefile +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/README.md +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/docs/api.md +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/docs/index.md +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/mkdocs.yml +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/pyproject.toml +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/requirements/requirements-docs.txt +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/requirements/requirements-lint.txt +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/requirements/requirements-test.txt +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/requirements/requirements-types.txt +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/requirements.txt +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/src/shell/__init__.py +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/src/shell/benchmark.py +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/src/shell/py.typed +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/src/shell/transforms.py +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/tests/__init__.py +0 -0
- {lavlab_shell-0.3.1 → lavlab_shell-0.4.0}/tests/conftest.py +0 -0
- {lavlab_shell-0.3.1 → 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
|
|
|
@@ -1689,7 +1699,10 @@ def infer_omero_wsi(
|
|
|
1689
1699
|
save_eho=save_eho,
|
|
1690
1700
|
output_path=output_path,
|
|
1691
1701
|
tile_overlap=tile_overlap,
|
|
1702
|
+
roi_size=inference_tile_size,
|
|
1692
1703
|
sw_overlap=sw_overlap,
|
|
1704
|
+
tissue_mask_full=tissue_mask_full,
|
|
1705
|
+
profile=profile,
|
|
1693
1706
|
num_fetch_workers=num_fetch_workers,
|
|
1694
1707
|
)
|
|
1695
1708
|
|
|
@@ -1736,7 +1749,10 @@ def _run_pipeline(
|
|
|
1736
1749
|
save_eho: str | None,
|
|
1737
1750
|
output_path: str,
|
|
1738
1751
|
tile_overlap: int,
|
|
1752
|
+
roi_size: int,
|
|
1739
1753
|
sw_overlap: float,
|
|
1754
|
+
tissue_mask_full: np.ndarray | None = None,
|
|
1755
|
+
profile: str = "best_effort",
|
|
1740
1756
|
num_fetch_workers: int = 4,
|
|
1741
1757
|
) -> np.ndarray:
|
|
1742
1758
|
"""Producer/consumer pipeline: parallel fetch+EHO → inference in main.
|
|
@@ -1818,7 +1834,10 @@ def _run_pipeline(
|
|
|
1818
1834
|
except Exception:
|
|
1819
1835
|
logger.exception("Failed to start OMERO fetch thread; continuing.")
|
|
1820
1836
|
|
|
1821
|
-
|
|
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)
|
|
1822
1841
|
|
|
1823
1842
|
eho_canvas: np.ndarray | None = None
|
|
1824
1843
|
if save_eho:
|
|
@@ -1838,24 +1857,34 @@ def _run_pipeline(
|
|
|
1838
1857
|
if eho_canvas is not None:
|
|
1839
1858
|
eho_canvas[oy0 : oy0 + oh, ox0 : ox0 + ow] = tile_eho
|
|
1840
1859
|
|
|
1841
|
-
|
|
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(
|
|
1842
1864
|
tile_eho,
|
|
1843
1865
|
model,
|
|
1844
1866
|
device,
|
|
1867
|
+
roi_size=(roi_size, roi_size),
|
|
1845
1868
|
overlap=sw_overlap,
|
|
1869
|
+
return_raw=True,
|
|
1846
1870
|
)
|
|
1847
1871
|
del tile_eho
|
|
1848
1872
|
|
|
1849
1873
|
# Write only the centre-cropped keep region to avoid seams.
|
|
1850
|
-
ph, pw =
|
|
1874
|
+
ph, pw = inner_tile.shape[:2]
|
|
1851
1875
|
ky1 = min(keep_y0 + keep_h, ph)
|
|
1852
1876
|
kx1 = min(keep_x0 + keep_w, pw)
|
|
1853
|
-
kept = tile_pred[keep_y0:ky1, keep_x0:kx1]
|
|
1854
1877
|
out_y0 = oy0 + keep_y0
|
|
1855
1878
|
out_x0 = ox0 + keep_x0
|
|
1856
|
-
ch
|
|
1857
|
-
|
|
1858
|
-
|
|
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
|
|
1859
1888
|
|
|
1860
1889
|
processed += 1
|
|
1861
1890
|
if processed % 25 == 0:
|
|
@@ -1873,7 +1902,22 @@ def _run_pipeline(
|
|
|
1873
1902
|
if torch.cuda.is_available():
|
|
1874
1903
|
torch.cuda.empty_cache()
|
|
1875
1904
|
|
|
1876
|
-
|
|
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)
|
|
1877
1921
|
|
|
1878
1922
|
finally:
|
|
1879
1923
|
fetch_thread.join(timeout=30)
|