senoquant 1.0.0b1__py3-none-any.whl

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 (148) hide show
  1. senoquant/__init__.py +6 -0
  2. senoquant/_reader.py +7 -0
  3. senoquant/_widget.py +33 -0
  4. senoquant/napari.yaml +83 -0
  5. senoquant/reader/__init__.py +5 -0
  6. senoquant/reader/core.py +369 -0
  7. senoquant/tabs/__init__.py +15 -0
  8. senoquant/tabs/batch/__init__.py +10 -0
  9. senoquant/tabs/batch/backend.py +641 -0
  10. senoquant/tabs/batch/config.py +270 -0
  11. senoquant/tabs/batch/frontend.py +1283 -0
  12. senoquant/tabs/batch/io.py +326 -0
  13. senoquant/tabs/batch/layers.py +86 -0
  14. senoquant/tabs/quantification/__init__.py +1 -0
  15. senoquant/tabs/quantification/backend.py +228 -0
  16. senoquant/tabs/quantification/features/__init__.py +80 -0
  17. senoquant/tabs/quantification/features/base.py +142 -0
  18. senoquant/tabs/quantification/features/marker/__init__.py +5 -0
  19. senoquant/tabs/quantification/features/marker/config.py +69 -0
  20. senoquant/tabs/quantification/features/marker/dialog.py +437 -0
  21. senoquant/tabs/quantification/features/marker/export.py +879 -0
  22. senoquant/tabs/quantification/features/marker/feature.py +119 -0
  23. senoquant/tabs/quantification/features/marker/morphology.py +285 -0
  24. senoquant/tabs/quantification/features/marker/rows.py +654 -0
  25. senoquant/tabs/quantification/features/marker/thresholding.py +46 -0
  26. senoquant/tabs/quantification/features/roi.py +346 -0
  27. senoquant/tabs/quantification/features/spots/__init__.py +5 -0
  28. senoquant/tabs/quantification/features/spots/config.py +62 -0
  29. senoquant/tabs/quantification/features/spots/dialog.py +477 -0
  30. senoquant/tabs/quantification/features/spots/export.py +1292 -0
  31. senoquant/tabs/quantification/features/spots/feature.py +112 -0
  32. senoquant/tabs/quantification/features/spots/morphology.py +279 -0
  33. senoquant/tabs/quantification/features/spots/rows.py +241 -0
  34. senoquant/tabs/quantification/frontend.py +815 -0
  35. senoquant/tabs/segmentation/__init__.py +1 -0
  36. senoquant/tabs/segmentation/backend.py +131 -0
  37. senoquant/tabs/segmentation/frontend.py +1009 -0
  38. senoquant/tabs/segmentation/models/__init__.py +5 -0
  39. senoquant/tabs/segmentation/models/base.py +146 -0
  40. senoquant/tabs/segmentation/models/cpsam/details.json +65 -0
  41. senoquant/tabs/segmentation/models/cpsam/model.py +150 -0
  42. senoquant/tabs/segmentation/models/default_2d/details.json +69 -0
  43. senoquant/tabs/segmentation/models/default_2d/model.py +664 -0
  44. senoquant/tabs/segmentation/models/default_3d/details.json +69 -0
  45. senoquant/tabs/segmentation/models/default_3d/model.py +682 -0
  46. senoquant/tabs/segmentation/models/hf.py +71 -0
  47. senoquant/tabs/segmentation/models/nuclear_dilation/__init__.py +1 -0
  48. senoquant/tabs/segmentation/models/nuclear_dilation/details.json +26 -0
  49. senoquant/tabs/segmentation/models/nuclear_dilation/model.py +96 -0
  50. senoquant/tabs/segmentation/models/perinuclear_rings/__init__.py +1 -0
  51. senoquant/tabs/segmentation/models/perinuclear_rings/details.json +34 -0
  52. senoquant/tabs/segmentation/models/perinuclear_rings/model.py +132 -0
  53. senoquant/tabs/segmentation/stardist_onnx_utils/__init__.py +2 -0
  54. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/__init__.py +3 -0
  55. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/__init__.py +6 -0
  56. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/generate.py +470 -0
  57. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/prepare.py +273 -0
  58. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/rawdata.py +112 -0
  59. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/transform.py +384 -0
  60. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/__init__.py +0 -0
  61. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/blocks.py +184 -0
  62. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/losses.py +79 -0
  63. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/nets.py +165 -0
  64. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/predict.py +467 -0
  65. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/probability.py +67 -0
  66. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/train.py +148 -0
  67. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/io/__init__.py +163 -0
  68. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/__init__.py +52 -0
  69. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/base_model.py +329 -0
  70. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_isotropic.py +160 -0
  71. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_projection.py +178 -0
  72. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_standard.py +446 -0
  73. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_upsampling.py +54 -0
  74. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/config.py +254 -0
  75. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/pretrained.py +119 -0
  76. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/scripts/__init__.py +0 -0
  77. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/scripts/care_predict.py +180 -0
  78. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/__init__.py +5 -0
  79. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/plot_utils.py +159 -0
  80. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/six.py +18 -0
  81. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/tf.py +644 -0
  82. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/utils.py +272 -0
  83. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/version.py +1 -0
  84. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/docs/source/conf.py +368 -0
  85. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/setup.py +68 -0
  86. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_datagen.py +169 -0
  87. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_models.py +462 -0
  88. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_utils.py +166 -0
  89. senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tools/create_zip_contents.py +34 -0
  90. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/__init__.py +30 -0
  91. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/big.py +624 -0
  92. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/bioimageio_utils.py +494 -0
  93. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/data/__init__.py +39 -0
  94. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/__init__.py +10 -0
  95. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/geom2d.py +215 -0
  96. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/geom3d.py +349 -0
  97. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/matching.py +483 -0
  98. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/__init__.py +28 -0
  99. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/base.py +1217 -0
  100. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/model2d.py +594 -0
  101. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/model3d.py +696 -0
  102. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/nms.py +384 -0
  103. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/__init__.py +2 -0
  104. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/plot.py +74 -0
  105. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/render.py +298 -0
  106. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/rays3d.py +373 -0
  107. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/sample_patches.py +65 -0
  108. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/__init__.py +0 -0
  109. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/predict2d.py +90 -0
  110. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/predict3d.py +93 -0
  111. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/utils.py +408 -0
  112. senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/version.py +1 -0
  113. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/__init__.py +45 -0
  114. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/__init__.py +17 -0
  115. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/cli.py +55 -0
  116. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/core.py +285 -0
  117. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/__init__.py +15 -0
  118. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/cli.py +36 -0
  119. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/divisibility.py +193 -0
  120. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/probe.py +100 -0
  121. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/receptive_field.py +182 -0
  122. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/rf_cli.py +48 -0
  123. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/valid_sizes.py +278 -0
  124. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/post/__init__.py +8 -0
  125. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/post/core.py +157 -0
  126. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/pre/__init__.py +17 -0
  127. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/pre/core.py +226 -0
  128. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/predict/__init__.py +5 -0
  129. senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/predict/core.py +401 -0
  130. senoquant/tabs/settings/__init__.py +1 -0
  131. senoquant/tabs/settings/backend.py +29 -0
  132. senoquant/tabs/settings/frontend.py +19 -0
  133. senoquant/tabs/spots/__init__.py +1 -0
  134. senoquant/tabs/spots/backend.py +139 -0
  135. senoquant/tabs/spots/frontend.py +800 -0
  136. senoquant/tabs/spots/models/__init__.py +5 -0
  137. senoquant/tabs/spots/models/base.py +94 -0
  138. senoquant/tabs/spots/models/rmp/details.json +61 -0
  139. senoquant/tabs/spots/models/rmp/model.py +499 -0
  140. senoquant/tabs/spots/models/udwt/details.json +103 -0
  141. senoquant/tabs/spots/models/udwt/model.py +482 -0
  142. senoquant/utils.py +25 -0
  143. senoquant-1.0.0b1.dist-info/METADATA +193 -0
  144. senoquant-1.0.0b1.dist-info/RECORD +148 -0
  145. senoquant-1.0.0b1.dist-info/WHEEL +5 -0
  146. senoquant-1.0.0b1.dist-info/entry_points.txt +2 -0
  147. senoquant-1.0.0b1.dist-info/licenses/LICENSE +28 -0
  148. senoquant-1.0.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,71 @@
1
+ """Hugging Face model download utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+ try:
9
+ from huggingface_hub import hf_hub_download # type: ignore[import-not-found]
10
+ except ImportError: # pragma: no cover - optional dependency
11
+ hf_hub_download = None
12
+
13
+
14
+ def _resolve_repo_id(default_repo: str) -> str:
15
+ """Resolve the model repository ID from environment or default."""
16
+ env_repo = os.environ.get("SENOQUANT_MODEL_REPO")
17
+ if env_repo:
18
+ return env_repo.strip()
19
+ return default_repo
20
+
21
+
22
+ DEFAULT_REPO_ID = "HaamsRee/senoquant-models"
23
+
24
+
25
+ def ensure_hf_model(
26
+ filename: str,
27
+ target_dir: Path,
28
+ *,
29
+ repo_id: str,
30
+ revision: str | None = None,
31
+ ) -> Path:
32
+ """Ensure a model file exists, downloading from HF if needed.
33
+
34
+ Parameters
35
+ ----------
36
+ filename : str
37
+ File name to download from the HF repo.
38
+ target_dir : pathlib.Path
39
+ Local directory for the model file.
40
+ repo_id : str
41
+ Hugging Face repo id, e.g. "HaamsRee/senoquant-models".
42
+ revision : str or None, optional
43
+ Optional revision/tag/commit to pin.
44
+
45
+ Returns
46
+ -------
47
+ pathlib.Path
48
+ Local path to the downloaded model file.
49
+
50
+ """
51
+ target_dir = Path(target_dir)
52
+ target_dir.mkdir(parents=True, exist_ok=True)
53
+ candidate = target_dir / filename
54
+ if candidate.exists():
55
+ return candidate
56
+
57
+ if hf_hub_download is None:
58
+ message = (
59
+ "huggingface_hub is required to download models. "
60
+ "Install it with `pip install huggingface_hub`."
61
+ )
62
+ raise RuntimeError(message)
63
+
64
+ resolved_repo = _resolve_repo_id(repo_id)
65
+ path = hf_hub_download(
66
+ repo_id=resolved_repo,
67
+ filename=filename,
68
+ revision=revision,
69
+ local_dir=str(target_dir),
70
+ )
71
+ return Path(path)
@@ -0,0 +1 @@
1
+ """Nuclear dilation segmentation model."""
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "nuclear_dilation",
3
+ "description": "Dilates nuclear masks to approximate cytoplasm",
4
+ "version": "0.1.0",
5
+ "order": 4,
6
+ "tasks": {
7
+ "nuclear": {
8
+ "supported": false
9
+ },
10
+ "cytoplasmic": {
11
+ "supported": true,
12
+ "input_modes": ["nuclear"],
13
+ "nuclear_channel_optional": false
14
+ }
15
+ },
16
+ "settings": [
17
+ {
18
+ "key": "dilation_iterations",
19
+ "label": "Dilation iterations",
20
+ "type": "int",
21
+ "min": 1,
22
+ "max": 100,
23
+ "default": 5
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,96 @@
1
+ """Nuclear dilation cytoplasmic segmentation model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ import numpy as np
8
+ from scipy import ndimage as ndi
9
+
10
+ from senoquant.tabs.segmentation.models.base import SenoQuantSegmentationModel
11
+ from senoquant.utils import layer_data_asarray
12
+
13
+ if TYPE_CHECKING:
14
+ from pathlib import Path
15
+
16
+
17
+ class NuclearDilationModel(SenoQuantSegmentationModel):
18
+ """Dilates nuclear masks to approximate cytoplasm.
19
+
20
+ This model only requires a nuclear segmentation mask and dilates it
21
+ to approximate cytoplasmic boundaries. It is useful when cytoplasmic
22
+ staining is weak or unavailable.
23
+
24
+ Notes
25
+ -----
26
+ - Only supports cytoplasmic segmentation task.
27
+ - Requires nuclear_layer, ignores cytoplasmic_layer.
28
+
29
+ """
30
+
31
+ def __init__(self, models_root: Path | None = None) -> None:
32
+ """Initialize the nuclear dilation model wrapper.
33
+
34
+ Parameters
35
+ ----------
36
+ models_root : pathlib.Path or None
37
+ Optional root directory for model storage.
38
+
39
+ """
40
+ super().__init__("nuclear_dilation", models_root=models_root)
41
+
42
+ def run(self, **kwargs: object) -> dict:
43
+ """Run nuclear dilation for cytoplasmic segmentation.
44
+
45
+ Parameters
46
+ ----------
47
+ **kwargs
48
+ task : str
49
+ Must be "cytoplasmic" for this model.
50
+ nuclear_layer : napari.layers.Labels
51
+ Nuclear segmentation mask layer.
52
+ cytoplasmic_layer : napari.layers.Image or None
53
+ Ignored by this model.
54
+ settings : dict
55
+ Model settings keyed by ``details.json``.
56
+
57
+ Returns
58
+ -------
59
+ dict
60
+ Dictionary with:
61
+ - ``masks``: dilated nuclear label image
62
+
63
+ """
64
+ task = kwargs.get("task")
65
+ if task != "cytoplasmic":
66
+ msg = "Nuclear dilation only supports cytoplasmic segmentation."
67
+ raise ValueError(msg)
68
+
69
+ nuclear_layer = kwargs.get("nuclear_layer")
70
+ settings = kwargs.get("settings", {})
71
+
72
+ if nuclear_layer is None:
73
+ msg = "Nuclear layer is required for nuclear dilation."
74
+ raise ValueError(msg)
75
+
76
+ nuclear_data = layer_data_asarray(nuclear_layer)
77
+ if nuclear_data is None:
78
+ msg = "Failed to read nuclear layer data."
79
+ raise ValueError(msg)
80
+
81
+ nuclear_data = nuclear_data.astype(np.uint32, copy=False)
82
+ settings_dict = {} if not isinstance(settings, dict) else settings
83
+ dilation_iterations = max(int(settings_dict.get("dilation_iterations", 5)), 1)
84
+
85
+ dilated_labels = np.zeros_like(nuclear_data)
86
+ for label_id in np.unique(nuclear_data):
87
+ if label_id == 0:
88
+ continue
89
+ mask = nuclear_data == label_id
90
+ dilated_mask = ndi.binary_dilation(
91
+ mask,
92
+ iterations=dilation_iterations,
93
+ )
94
+ dilated_labels[dilated_mask] = label_id
95
+
96
+ return {"masks": dilated_labels}
@@ -0,0 +1 @@
1
+ """Perinuclear rings cytoplasmic segmentation model."""
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "perinuclear_rings",
3
+ "description": "Creates perinuclear rings from nuclear masks",
4
+ "version": "0.1.0",
5
+ "order": 5,
6
+ "tasks": {
7
+ "nuclear": {
8
+ "supported": false
9
+ },
10
+ "cytoplasmic": {
11
+ "supported": true,
12
+ "input_modes": ["nuclear"],
13
+ "nuclear_channel_optional": false
14
+ }
15
+ },
16
+ "settings": [
17
+ {
18
+ "key": "erosion_px",
19
+ "label": "Inner erosion (px)",
20
+ "type": "int",
21
+ "min": 1,
22
+ "max": 50,
23
+ "default": 2
24
+ },
25
+ {
26
+ "key": "dilation_px",
27
+ "label": "Outer dilation (px)",
28
+ "type": "int",
29
+ "min": 0,
30
+ "max": 50,
31
+ "default": 5
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,132 @@
1
+ """Perinuclear rings cytoplasmic segmentation model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ import numpy as np
8
+ from scipy import ndimage as ndi
9
+
10
+ from senoquant.tabs.segmentation.models.base import SenoQuantSegmentationModel
11
+ from senoquant.utils import layer_data_asarray
12
+
13
+ if TYPE_CHECKING:
14
+ from pathlib import Path
15
+
16
+
17
+ class PerinuclearRingsModel(SenoQuantSegmentationModel):
18
+ """Creates perinuclear rings from nuclear masks.
19
+
20
+ This model generates ring-shaped labels around nuclei by eroding the
21
+ nuclear mask inward and dilating it outward, then subtracting the
22
+ eroded mask from the dilated mask. This is useful for detecting
23
+ perinuclear markers that localize to the region immediately
24
+ surrounding the nucleus.
25
+
26
+ The erosion parameter has a minimum of 1 pixel to ensure that the
27
+ resulting rings maintain at least 1 pixel overlap with the original
28
+ nuclear labels, which is required for label relationship logic in
29
+ quantification and batch processing.
30
+
31
+ Notes
32
+ -----
33
+ - Only supports cytoplasmic segmentation task.
34
+ - Requires nuclear_layer, ignores cytoplasmic_layer.
35
+ - Maintains label IDs from the original nuclear segmentation.
36
+
37
+ """
38
+
39
+ def __init__(self, models_root: Path | None = None) -> None:
40
+ """Initialize the perinuclear rings model wrapper.
41
+
42
+ Parameters
43
+ ----------
44
+ models_root : pathlib.Path or None
45
+ Optional root directory for model storage.
46
+
47
+ """
48
+ super().__init__("perinuclear_rings", models_root=models_root)
49
+
50
+ def run(self, **kwargs: object) -> dict:
51
+ """Generate perinuclear rings from nuclear segmentation.
52
+
53
+ Parameters
54
+ ----------
55
+ **kwargs
56
+ task : str
57
+ Must be "cytoplasmic" for this model.
58
+ nuclear_layer : napari.layers.Labels
59
+ Nuclear segmentation mask layer.
60
+ cytoplasmic_layer : napari.layers.Image or None
61
+ Ignored by this model.
62
+ settings : dict
63
+ Model settings keyed by ``details.json``:
64
+ - ``erosion_px``: pixels to erode inward (min 1)
65
+ - ``dilation_px``: pixels to dilate outward
66
+
67
+ Returns
68
+ -------
69
+ dict
70
+ Dictionary with:
71
+ - ``masks``: perinuclear ring label image
72
+
73
+ Raises
74
+ ------
75
+ ValueError
76
+ If task is not "cytoplasmic" or nuclear_layer is missing.
77
+
78
+ """
79
+ task = kwargs.get("task")
80
+ if task != "cytoplasmic":
81
+ msg = "Perinuclear rings only supports cytoplasmic segmentation."
82
+ raise ValueError(msg)
83
+
84
+ nuclear_layer = kwargs.get("nuclear_layer")
85
+ settings = kwargs.get("settings", {})
86
+
87
+ if nuclear_layer is None:
88
+ msg = "Nuclear layer is required for perinuclear rings."
89
+ raise ValueError(msg)
90
+
91
+ nuclear_data = layer_data_asarray(nuclear_layer)
92
+ if nuclear_data is None:
93
+ msg = "Failed to read nuclear layer data."
94
+ raise ValueError(msg)
95
+
96
+ nuclear_data = nuclear_data.astype(np.uint32, copy=False)
97
+ settings_dict = {} if not isinstance(settings, dict) else settings
98
+
99
+ # Ensure erosion is at least 1 pixel for label relationship logic
100
+ erosion_px = max(int(settings_dict.get("erosion_px", 2)), 1)
101
+ dilation_px = max(int(settings_dict.get("dilation_px", 5)), 0)
102
+
103
+ ring_labels = np.zeros_like(nuclear_data)
104
+
105
+ # Process each nucleus individually to maintain label relationships
106
+ for label_id in np.unique(nuclear_data):
107
+ if label_id == 0:
108
+ continue
109
+
110
+ # Create binary mask for this nucleus
111
+ nucleus_mask = nuclear_data == label_id
112
+
113
+ # Erode inward (minimum 1 px to maintain overlap)
114
+ eroded_mask = ndi.binary_erosion(
115
+ nucleus_mask,
116
+ iterations=erosion_px,
117
+ )
118
+
119
+ # Dilate outward
120
+ dilated_mask = ndi.binary_dilation(
121
+ nucleus_mask,
122
+ iterations=dilation_px,
123
+ )
124
+
125
+ # Ring is the difference: dilated - eroded
126
+ # This creates a ring that includes the original boundary
127
+ ring_mask = dilated_mask & ~eroded_mask
128
+
129
+ # Assign the original label ID to the ring
130
+ ring_labels[ring_mask] = label_id
131
+
132
+ return {"masks": ring_labels}
@@ -0,0 +1,2 @@
1
+ """StarDist ONNX support utilities (vendored StarDist/CSBDeep + helpers)."""
2
+
@@ -0,0 +1,3 @@
1
+ from __future__ import absolute_import, print_function
2
+ from .version import __version__
3
+
@@ -0,0 +1,6 @@
1
+ from __future__ import absolute_import
2
+
3
+ from .transform import *
4
+ from .generate import *
5
+ from .rawdata import *
6
+ from .prepare import *