lavlab-shell 0.2.0__tar.gz → 0.3.1__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.
Files changed (41) hide show
  1. lavlab_shell-0.3.1/.github/workflows/build.yml +54 -0
  2. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/.github/workflows/publish.yml +11 -8
  3. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/.github/workflows/pytest.yml +11 -3
  4. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/Dockerfile +4 -2
  5. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/PKG-INFO +2 -1
  6. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/pyproject.toml +11 -12
  7. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/src/shell/__about__.py +1 -1
  8. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/src/shell/cli.py +73 -33
  9. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/src/shell/infer_omero_wsi.py +296 -75
  10. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/src/shell/infer_wsi.py +137 -37
  11. lavlab_shell-0.3.1/src/shell/inference.py +294 -0
  12. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/src/shell/model.py +3 -3
  13. lavlab_shell-0.3.1/src/shell/transforms.py +650 -0
  14. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/src/shell/weights/model_v1.pth +0 -0
  15. lavlab_shell-0.2.0/.github/workflows/build.yml +0 -22
  16. lavlab_shell-0.2.0/src/shell/inference.py +0 -126
  17. lavlab_shell-0.2.0/src/shell/preprocessing.py +0 -337
  18. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/.devcontainer/Dockerfile +0 -0
  19. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/.editorconfig +0 -0
  20. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/.github/dependabot.yml +0 -0
  21. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/.github/workflows/lint.yml +0 -0
  22. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/.gitignore +0 -0
  23. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/.pre-commit-config.yaml +0 -0
  24. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/CONTRIBUTING.md +0 -0
  25. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/LICENSE.txt +0 -0
  26. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/Makefile +0 -0
  27. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/README.md +0 -0
  28. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/docs/api.md +0 -0
  29. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/docs/index.md +0 -0
  30. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/mkdocs.yml +0 -0
  31. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/requirements/requirements-docs.txt +0 -0
  32. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/requirements/requirements-lint.txt +0 -0
  33. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/requirements/requirements-test.txt +0 -0
  34. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/requirements/requirements-types.txt +0 -0
  35. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/requirements.txt +0 -0
  36. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/src/shell/__init__.py +0 -0
  37. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/src/shell/benchmark.py +0 -0
  38. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/src/shell/py.typed +0 -0
  39. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/tests/__init__.py +0 -0
  40. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/tests/conftest.py +0 -0
  41. {lavlab_shell-0.2.0 → lavlab_shell-0.3.1}/tests/test_shell.py +0 -0
@@ -0,0 +1,54 @@
1
+ name: Build Wheel using Hatch
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+
9
+ jobs:
10
+ build-wheel:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Set up Python
15
+ uses: actions/setup-python@v4
16
+ with:
17
+ python-version: '3.12'
18
+
19
+ - name: Install build tools
20
+ run: |
21
+ python -m pip install --upgrade pip setuptools wheel
22
+ python -m pip install "virtualenv==20.23.1"
23
+ python -m pip install --upgrade hatch
24
+
25
+ - name: Build wheel with hatch
26
+ run: hatch build
27
+ - name: Upload wheel
28
+ uses: actions/upload-artifact@v4
29
+ with:
30
+ name: wheel
31
+ path: dist/*.whl
32
+
33
+ - uses: actions/checkout@v4
34
+
35
+ - name: Build Docker image (hatch stage)
36
+ uses: docker/build-push-action@v4
37
+ with:
38
+ context: .
39
+ file: ./Dockerfile
40
+ target: hatch
41
+ tags: shell-ci:latest
42
+
43
+ - name: Run build inside Docker container
44
+ run: |
45
+ docker run --rm \
46
+ -v "${{ github.workspace }}:/app" \
47
+ -w /app \
48
+ shell-ci:latest build
49
+
50
+ - name: Upload wheel
51
+ uses: actions/upload-artifact@v4
52
+ with:
53
+ name: wheel
54
+ path: dist/*.whl
@@ -17,17 +17,20 @@ jobs:
17
17
  - name: Checkout
18
18
  uses: actions/checkout@v4
19
19
 
20
- - name: Set up Python 3.12
21
- uses: actions/setup-python@v5
20
+ - name: Build Docker image (hatch stage)
21
+ uses: docker/build-push-action@v4
22
22
  with:
23
- python-version: "3.12"
23
+ context: .
24
+ file: ./Dockerfile
25
+ target: hatch
26
+ tags: shell-ci:latest
24
27
 
25
- - name: Install build tools
28
+ - name: Build wheel and sdist inside Docker
26
29
  run: |
27
- python -m pip install --upgrade pip setuptools wheel hatch
28
-
29
- - name: Build wheel and sdist
30
- run: hatch build
30
+ docker run --rm \
31
+ -v "${{ github.workspace }}:/app" \
32
+ -w /app \
33
+ shell-ci:latest build
31
34
 
32
35
  - name: Publish to PyPI
33
36
  uses: pypa/gh-action-pypi-publish@release/v1
@@ -10,11 +10,19 @@ jobs:
10
10
  steps:
11
11
  - uses: actions/checkout@v4
12
12
 
13
- - name: Build Docker image
14
- run: docker build --target hatch -t shell:hatch .
13
+ - name: Set up Python
14
+ uses: actions/setup-python@v4
15
+ with:
16
+ python-version: '3.12'
17
+
18
+ - name: Install test dependencies
19
+ run: |
20
+ python -m pip install --upgrade pip
21
+ python -m pip install pytest pytest-cov coverage[toml]>=6.2
15
22
 
16
23
  - name: Run pytest and generate coverage report
17
- run: docker run --rm -e HATCH_ENV=test -v "${{ github.workspace }}:/app" shell:hatch cov
24
+ run: |
25
+ pytest --cov=src --cov-report=xml:coverage.xml
18
26
 
19
27
  - name: Upload coverage artifact
20
28
  uses: actions/upload-artifact@v4
@@ -22,7 +22,9 @@ COPY src/ src/
22
22
  RUN chown -R vscode:vscode /app
23
23
 
24
24
  FROM base AS hatch
25
- RUN pip3 install --no-cache-dir hatch uv
25
+ RUN pip3 install --no-cache-dir --upgrade pip setuptools wheel
26
+ RUN pip3 install --no-cache-dir 'virtualenv<21'
27
+ RUN pip3 install --no-cache-dir --upgrade hatch hatch-uv
26
28
  ENV HATCH_ENV=default
27
29
  ENTRYPOINT ["hatch", "run"]
28
30
 
@@ -32,7 +34,7 @@ COPY requirements.txt ./
32
34
  COPY tests/ tests/
33
35
  COPY docs/ docs/
34
36
  COPY mkdocs.yml ./
35
- RUN pip3 install --no-cache-dir hatch \
37
+ RUN pip3 install --no-cache-dir hatch hatch-uv \
36
38
  && hatch build \
37
39
  && pip3 install --no-cache-dir $(find /app -name 'requirement*.txt' -exec echo -n '-r {} ' \;)
38
40
  USER vscode
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lavlab-shell
3
- Version: 0.2.0
3
+ Version: 0.3.1
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
@@ -21,6 +21,7 @@ Requires-Dist: monai
21
21
  Requires-Dist: numpy
22
22
  Requires-Dist: openslide-python
23
23
  Requires-Dist: pyvips
24
+ Requires-Dist: scikit-image
24
25
  Requires-Dist: scipy
25
26
  Requires-Dist: torch
26
27
  Provides-Extra: omero
@@ -33,6 +33,7 @@ dependencies = [
33
33
  "pyvips",
34
34
  "openslide-python",
35
35
  "macenko-pca",
36
+ "scikit-image",
36
37
  ]
37
38
 
38
39
  [project.optional-dependencies]
@@ -53,27 +54,25 @@ path = "src/shell/__about__.py"
53
54
  [tool.hatch.build.targets.wheel]
54
55
  packages = ["src/shell"]
55
56
 
56
- [tool.hatch.env]
57
- requires = ["hatch-pip-compile"]
58
-
59
57
  [tool.hatch.envs.default]
60
- type = "pip-compile"
61
- pip-compile-resolver = "uv"
62
- pip-compile-hashes = false
63
- pip-compile-args = ["--allow-unsafe", "--universal"]
64
- dependencies = ["setuptools==82.0.0"]
58
+ dependencies = ["setuptools>=82.0.0"]
65
59
 
66
60
  [tool.hatch.envs.default.scripts]
67
61
  build = "hatch build && chmod -R 777 dist/*"
68
62
 
69
63
  [tool.hatch.envs.test]
70
- dependencies = ["pytest", "pytest-cov", "coverage[toml]>=6.2", "setuptools==82.0.0"]
64
+ dependencies = [
65
+ "pytest",
66
+ "pytest-cov",
67
+ "coverage[toml]>=6.2",
68
+ "setuptools>=82.0.0",
69
+ ]
71
70
  [tool.hatch.envs.test.scripts]
72
71
  test = "pytest {args:tests}"
73
72
  cov = "pytest --cov=src --cov-report=term-missing --cov-report=xml {args:tests}"
74
73
 
75
74
  [tool.hatch.envs.lint]
76
- dependencies = ["ruff>=0.4.0", "setuptools==82.0.0"]
75
+ dependencies = ["ruff>=0.4.0", "setuptools>=82.0.0"]
77
76
  [tool.hatch.envs.lint.scripts]
78
77
  check = "ruff check src tests"
79
78
  format = "ruff format src tests"
@@ -82,7 +81,7 @@ fix = "ruff check --fix src tests"
82
81
  all = ["format", "fix", "check"]
83
82
 
84
83
  [tool.hatch.envs.types]
85
- dependencies = ["mypy>=1.0.0", "setuptools==82.0.0"]
84
+ dependencies = ["mypy>=1.0.0", "setuptools>=82.0.0"]
86
85
  [tool.hatch.envs.types.scripts]
87
86
  check = "mypy --install-types --non-interactive {args:src/shell tests}"
88
87
 
@@ -95,7 +94,7 @@ dependencies = [
95
94
  "mkdocs-minify-plugin",
96
95
  "mkdocs-material-extensions",
97
96
  "mkdocs-git-revision-date-localized-plugin",
98
- "setuptools==82.0.0",
97
+ "setuptools>=82.0.0",
99
98
  ]
100
99
  [tool.hatch.envs.docs.scripts]
101
100
  build-docs = "mkdocs build"
@@ -3,4 +3,4 @@
3
3
  # SPDX-License-Identifier: MIT
4
4
  """Version information for shell."""
5
5
 
6
- __version__ = "0.2.0"
6
+ __version__ = "0.3.1"
@@ -12,6 +12,30 @@ Provides two subcommands:
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
+ import os
16
+
17
+ # Ensure PyTorch initialises its runtime and threads before other native
18
+ # libraries (pyvips, Ice/OMERO) to avoid macOS-level races that can lead
19
+ # to segmentation faults when different C extensions compete for thread
20
+ # runtime ownership. Set conservative thread counts before importing torch.
21
+ os.environ.setdefault("OMP_NUM_THREADS", "1")
22
+ os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")
23
+ os.environ.setdefault("MKL_NUM_THREADS", "1")
24
+ os.environ.setdefault("VECLIB_MAXIMUM_THREADS", "1")
25
+
26
+ try:
27
+ # Import torch early so it initialises its native runtime in the main
28
+ # thread. This is intentional — we only probe __version__ to force
29
+ # initialisation without performing heavy work here.
30
+ import torch # type: ignore
31
+
32
+ _ = torch.__version__ # ensure attribute access triggers initialisation
33
+ TORCH_AVAILABLE = True
34
+ except Exception:
35
+ # If torch is unavailable we still continue — the program will emit
36
+ # a clearer error later when trying to build/load the model.
37
+ TORCH_AVAILABLE = False
38
+
15
39
  import argparse
16
40
  import getpass
17
41
  import logging
@@ -19,6 +43,9 @@ import sys
19
43
 
20
44
  from shell.__about__ import __version__
21
45
 
46
+ # Module logger — used for diagnostic messages instead of printing directly.
47
+ log = logging.getLogger(__name__)
48
+
22
49
 
23
50
  def build_parser() -> argparse.ArgumentParser:
24
51
  """Build and return the argument parser."""
@@ -98,12 +125,6 @@ def build_parser() -> argparse.ArgumentParser:
98
125
  default=None,
99
126
  help="Optional: also save the intermediate EHO image.",
100
127
  )
101
- infer_p.add_argument(
102
- "--stain-downsample",
103
- type=int,
104
- default=4,
105
- help="Downsample factor for stain parameter estimation.",
106
- )
107
128
  infer_p.add_argument(
108
129
  "--device",
109
130
  type=str,
@@ -138,7 +159,6 @@ def build_parser() -> argparse.ArgumentParser:
138
159
  default=1.0,
139
160
  help="Desired output resolution (um/pixel)",
140
161
  )
141
- omero_p.add_argument("--max-dim", type=int, default=None)
142
162
  omero_p.add_argument(
143
163
  "--model-path",
144
164
  type=str,
@@ -283,7 +303,6 @@ def main(argv: list[str] | None = None) -> int:
283
303
  target_mpp=args.target_mpp,
284
304
  mpp=args.mpp,
285
305
  save_eho=args.save_eho,
286
- stain_downsample=args.stain_downsample,
287
306
  device=args.device,
288
307
  )
289
308
 
@@ -295,36 +314,57 @@ def main(argv: list[str] | None = None) -> int:
295
314
  return 0
296
315
 
297
316
  if args.command == "infer-omero":
317
+ # Diagnostic prints added to help narrow where a macOS segfault occurs.
318
+ # These prints are intentionally lightweight and flushed so they appear
319
+ # even if the process crashes in a C extension later on.
320
+ log.debug("DIAG: infer-omero selected")
321
+ log.debug("DIAG: about to import infer_omero_wsi")
298
322
  from shell.infer_omero_wsi import infer_omero_wsi
299
323
 
324
+ log.debug("DIAG: imported infer_omero_wsi")
325
+
300
326
  if args.password is None:
301
327
  args.password = getpass.getpass("OMERO password: ")
302
328
 
303
- print(f"Connecting to {args.host}:{args.port} …")
304
- infer_omero_wsi(
305
- host=args.host,
306
- port=args.port,
307
- username=args.username,
308
- password=args.password,
309
- image_id=args.image_id,
310
- model_path=args.model_path,
311
- model_version=args.model_version,
312
- output_path=args.output,
313
- target_mpp=args.target_mpp,
314
- max_dim=args.max_dim,
315
- group_id=args.group_id,
316
- save_eho=args.save_eho,
317
- no_tissue_crop=args.no_tissue_crop,
318
- device=args.device,
319
- inference_tile_size=args.inference_tile_size,
320
- tile_overlap=args.tile_overlap,
321
- sw_overlap=args.sw_overlap,
322
- num_bg_tiles=args.num_bg_tiles,
323
- min_thumb_size=args.min_thumb_size,
324
- min_tissue_frac=args.min_tissue_frac,
325
- prefetch_depth=args.prefetch_depth,
326
- num_fetch_workers=args.fetch_workers,
327
- )
329
+ print(f"Connecting to {args.host}:{args.port} …", flush=True)
330
+ log.debug("DIAG: about to call infer_omero_wsi")
331
+ try:
332
+ infer_omero_wsi(
333
+ host=args.host,
334
+ port=args.port,
335
+ username=args.username,
336
+ password=args.password,
337
+ image_id=args.image_id,
338
+ model_path=args.model_path,
339
+ model_version=args.model_version,
340
+ output_path=args.output,
341
+ target_mpp=args.target_mpp,
342
+ group_id=args.group_id,
343
+ save_eho=args.save_eho,
344
+ no_tissue_crop=args.no_tissue_crop,
345
+ device=args.device,
346
+ inference_tile_size=args.inference_tile_size,
347
+ tile_overlap=args.tile_overlap,
348
+ sw_overlap=args.sw_overlap,
349
+ num_bg_tiles=args.num_bg_tiles,
350
+ min_thumb_size=args.min_thumb_size,
351
+ min_tissue_frac=args.min_tissue_frac,
352
+ prefetch_depth=args.prefetch_depth,
353
+ num_fetch_workers=args.fetch_workers,
354
+ )
355
+ log.debug("DIAG: infer_omero_wsi returned normally")
356
+ except BaseException as e:
357
+ # Catch and print Python-level exceptions; note segfaults (native crashes)
358
+ # will not be caught here, but these diagnostics will show progress up to
359
+ # the crash point.
360
+ import sys
361
+ import traceback
362
+
363
+ log.debug("DIAG: infer_omero_wsi raised an exception:")
364
+ traceback.print_exc()
365
+ # Re-raise so existing behaviour (non-zero exit) remains.
366
+ raise
367
+ log.debug("DIAG: after infer_omero_wsi (should have saved output)")
328
368
  print(f"Saved prediction to {args.output}")
329
369
  return 0
330
370