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,157 @@
1
+ """Post-processing helpers using StarDist geometry and NMS."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+
7
+
8
+ def instances_from_prediction_2d(
9
+ prob: np.ndarray,
10
+ dist: np.ndarray,
11
+ *,
12
+ grid: tuple[int, int],
13
+ prob_thresh: float,
14
+ nms_thresh: float,
15
+ scale: dict[str, float] | None = None,
16
+ img_shape: tuple[int, int] | None = None,
17
+ ) -> tuple[np.ndarray, dict]:
18
+ """Create 2D instance labels from StarDist outputs.
19
+
20
+ Parameters
21
+ ----------
22
+ prob : numpy.ndarray
23
+ Probability map with shape (Y, X).
24
+ dist : numpy.ndarray
25
+ Distance/ray map with shape (Y, X, R), where R is the number of rays.
26
+ grid : tuple[int, int]
27
+ Subsampling grid of the model (e.g., (1, 1) or (2, 2)).
28
+ prob_thresh : float
29
+ Probability threshold used to filter candidate points before NMS.
30
+ nms_thresh : float
31
+ NMS IoU/overlap threshold for suppressing nearby detections.
32
+ scale : dict[str, float], optional
33
+ Scale factors applied to the input image before inference. If
34
+ provided, must include ``"X"`` and ``"Y"`` so that points and
35
+ distances can be rescaled back to the original image space.
36
+ img_shape : tuple[int, int], optional
37
+ Original image shape (Y, X). If provided, the output label image
38
+ is generated in this shape instead of the scaled prediction shape.
39
+ Returns
40
+ -------
41
+ tuple[numpy.ndarray, dict]
42
+ Label image of shape (Y, X) and a metadata dict with:
43
+ - ``points``: center points used for each instance.
44
+ - ``prob``: per-instance probabilities.
45
+ - ``dist``: per-instance ray distances.
46
+
47
+ Notes
48
+ -----
49
+ This function performs non-maximum suppression on the probability map
50
+ and then rasterizes polygons using the selected points and distances.
51
+ """
52
+ from ..._stardist.nms import non_maximum_suppression
53
+ points, scores, distances = non_maximum_suppression(
54
+ dist,
55
+ prob,
56
+ grid=grid,
57
+ prob_thresh=prob_thresh,
58
+ nms_thresh=nms_thresh,
59
+ )
60
+ if scale is not None:
61
+ if not (isinstance(scale, dict) and "X" in scale and "Y" in scale):
62
+ raise ValueError("scale must be a dictionary with entries for 'X' and 'Y'")
63
+ rescale = (1 / scale["Y"], 1 / scale["X"])
64
+ points = points * np.array(rescale).reshape(1, 2)
65
+ else:
66
+ rescale = (1, 1)
67
+ from ..._stardist.geometry.geom2d import polygons_to_label
68
+ shape = img_shape if img_shape is not None else tuple(
69
+ s * g for s, g in zip(prob.shape, grid)
70
+ )
71
+ labels = polygons_to_label(
72
+ distances, points, shape=shape, prob=scores, scale_dist=rescale
73
+ )
74
+ return labels, {"points": points, "prob": scores, "dist": distances}
75
+
76
+
77
+ def instances_from_prediction_3d(
78
+ prob: np.ndarray,
79
+ dist: np.ndarray,
80
+ *,
81
+ grid: tuple[int, int, int],
82
+ prob_thresh: float,
83
+ nms_thresh: float,
84
+ rays,
85
+ scale: dict[str, float] | None = None,
86
+ img_shape: tuple[int, int, int] | None = None,
87
+ ) -> tuple[np.ndarray, dict]:
88
+ """Create 3D instance labels from StarDist outputs.
89
+
90
+ Parameters
91
+ ----------
92
+ prob : numpy.ndarray
93
+ Probability map with shape (Z, Y, X).
94
+ dist : numpy.ndarray
95
+ Distance/ray map with shape (Z, Y, X, R), where R is the number of rays.
96
+ grid : tuple[int, int, int]
97
+ Subsampling grid of the model (e.g., (1, 1, 1) or (2, 2, 2)).
98
+ prob_thresh : float
99
+ Probability threshold used to filter candidate points before NMS.
100
+ nms_thresh : float
101
+ NMS IoU/overlap threshold for suppressing nearby detections.
102
+ rays : object
103
+ StarDist 3D rays object describing ray directions and sampling.
104
+ scale : dict[str, float], optional
105
+ Scale factors applied to the input image before inference. If
106
+ provided, must include ``"X"``, ``"Y"``, and ``"Z"`` so that points
107
+ and rays can be rescaled back to the original image space.
108
+ img_shape : tuple[int, int, int], optional
109
+ Original image shape (Z, Y, X). If provided, the output label
110
+ volume is generated in this shape instead of the scaled prediction
111
+ shape.
112
+ Returns
113
+ -------
114
+ tuple[numpy.ndarray, dict]
115
+ Label volume of shape (Z, Y, X) and a metadata dict with:
116
+ - ``points``: center points used for each instance.
117
+ - ``prob``: per-instance probabilities.
118
+ - ``dist``: per-instance ray distances.
119
+
120
+ Notes
121
+ -----
122
+ This function performs non-maximum suppression in 3D and then
123
+ rasterizes polyhedra using the selected points and distances. The
124
+ Python backend uses an axis-aligned bounding-box approximation.
125
+ """
126
+ from ..._stardist.nms import non_maximum_suppression_3d
127
+ points, scores, distances = non_maximum_suppression_3d(
128
+ dist,
129
+ prob,
130
+ rays,
131
+ grid=grid,
132
+ prob_thresh=prob_thresh,
133
+ nms_thresh=nms_thresh,
134
+ )
135
+ if scale is not None:
136
+ if not (
137
+ isinstance(scale, dict)
138
+ and "X" in scale
139
+ and "Y" in scale
140
+ and "Z" in scale
141
+ ):
142
+ raise ValueError(
143
+ "scale must be a dictionary with entries for 'X', 'Y', and 'Z'"
144
+ )
145
+ rescale = (1 / scale["Z"], 1 / scale["Y"], 1 / scale["X"])
146
+ points = points * np.array(rescale).reshape(1, 3)
147
+ rays = rays.copy(scale=rescale)
148
+ else:
149
+ rescale = (1, 1, 1)
150
+ from ..._stardist.geometry.geom3d import polyhedron_to_label
151
+ shape = img_shape if img_shape is not None else tuple(
152
+ s * g for s, g in zip(prob.shape, grid)
153
+ )
154
+ labels = polyhedron_to_label(
155
+ distances, points, rays=rays, shape=shape, prob=scores, verbose=False
156
+ )
157
+ return labels, {"points": points, "prob": scores, "dist": distances}
@@ -0,0 +1,17 @@
1
+ """Preprocessing utilities for ONNX inference."""
2
+
3
+ from .core import (
4
+ normalize,
5
+ pad_for_tiling,
6
+ pad_to_multiple,
7
+ unpad_to_shape,
8
+ validate_image,
9
+ )
10
+
11
+ __all__ = [
12
+ "normalize",
13
+ "pad_for_tiling",
14
+ "pad_to_multiple",
15
+ "unpad_to_shape",
16
+ "validate_image",
17
+ ]
@@ -0,0 +1,226 @@
1
+ """Preprocessing helpers for ONNX StarDist inference.
2
+
3
+ These helpers mirror the high-level behavior of StarDist's preprocessing:
4
+ validate input dimensionality, optionally normalize, and pad to the grid
5
+ required by the network. The helpers assume single-channel images with
6
+ spatial axes ordered as YX (2D) or ZYX (3D).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import math
12
+
13
+ import numpy as np
14
+
15
+
16
+ def validate_image(image: np.ndarray) -> None:
17
+ """Validate input image dimensionality.
18
+
19
+ Parameters
20
+ ----------
21
+ image : numpy.ndarray
22
+ Input image array expected to be 2D (YX) or 3D (ZYX) and single-channel.
23
+
24
+ Raises
25
+ ------
26
+ ValueError
27
+ If the input is not 2D or 3D.
28
+ """
29
+ if image.ndim not in (2, 3):
30
+ raise ValueError("Input image must be 2D (YX) or 3D (ZYX).")
31
+
32
+
33
+ def normalize(
34
+ image: np.ndarray,
35
+ pmin: float = 1.0,
36
+ pmax: float = 99.8,
37
+ eps: float = 1e-6,
38
+ ) -> np.ndarray:
39
+ """Percentile normalize a single-channel image.
40
+
41
+ Parameters
42
+ ----------
43
+ image : numpy.ndarray
44
+ Input image to normalize.
45
+ pmin : float, optional
46
+ Lower percentile for normalization. Default is 1.0.
47
+ pmax : float, optional
48
+ Upper percentile for normalization. Default is 99.8.
49
+ eps : float, optional
50
+ Small constant to avoid division by zero. Default is 1e-6.
51
+
52
+ Returns
53
+ -------
54
+ numpy.ndarray
55
+ Normalized float32 image with values clipped to [0, 1].
56
+
57
+ Raises
58
+ ------
59
+ ValueError
60
+ If ``pmax`` is not greater than ``pmin``.
61
+ """
62
+ if pmax <= pmin:
63
+ raise ValueError("pmax must be greater than pmin.")
64
+ lo = np.percentile(image, pmin, keepdims=True)
65
+ hi = np.percentile(image, pmax, keepdims=True)
66
+ scale = hi - lo
67
+ scale = np.where(scale < eps, 1.0, scale)
68
+ normalized = (image - lo) / scale
69
+ return np.clip(normalized, 0.0, 1.0).astype(np.float32)
70
+
71
+
72
+ def pad_to_multiple(
73
+ image: np.ndarray,
74
+ multiples: tuple[int, ...],
75
+ mode: str = "reflect",
76
+ ) -> tuple[np.ndarray, tuple[tuple[int, int], ...]]:
77
+ """Pad each axis so its length is divisible by the corresponding multiple.
78
+
79
+ Parameters
80
+ ----------
81
+ image : numpy.ndarray
82
+ Input image (2D or 3D).
83
+ multiples : tuple[int, ...]
84
+ Per-axis divisibility constraints (e.g., the StarDist grid).
85
+ mode : str, optional
86
+ Padding mode passed to ``numpy.pad``. Default is "reflect".
87
+
88
+ Returns
89
+ -------
90
+ numpy.ndarray
91
+ Padded image as float32.
92
+ tuple[tuple[int, int], ...]
93
+ Padding applied per axis as (before, after) pairs. This implementation
94
+ only pads at the end of each axis (before = 0).
95
+
96
+ Raises
97
+ ------
98
+ ValueError
99
+ If the multiples do not match the image dimensionality or contain
100
+ non-positive values.
101
+ """
102
+ validate_image(image)
103
+ if len(multiples) != image.ndim:
104
+ raise ValueError("Multiples must match image dimensionality.")
105
+ pads = []
106
+ for dim, mult in zip(image.shape, multiples):
107
+ if mult <= 0:
108
+ raise ValueError("Multiples must be positive.")
109
+ pad_after = (mult - (dim % mult)) % mult
110
+ pads.append((0, pad_after))
111
+ padded = np.pad(image, pads, mode=mode)
112
+ return padded.astype(np.float32, copy=False), tuple(pads)
113
+
114
+
115
+ def unpad_to_shape(
116
+ data: np.ndarray,
117
+ pads: tuple[tuple[int, int], ...],
118
+ scale: tuple[int, ...] | None = None,
119
+ ) -> np.ndarray:
120
+ """Crop padded output back to the unpadded shape (accounting for scale).
121
+
122
+ Parameters
123
+ ----------
124
+ data : numpy.ndarray
125
+ Output array to crop (e.g., probability or distance map).
126
+ pads : tuple[tuple[int, int], ...]
127
+ Padding applied to the input image as returned by ``pad_to_multiple``.
128
+ scale : tuple[int, ...] or None, optional
129
+ Per-axis scale factor between input and output spatial grids.
130
+ For StarDist, this is typically the grid (e.g., (1, 1) or (2, 2)).
131
+ If None, assumes scale 1 for all axes.
132
+
133
+ Returns
134
+ -------
135
+ numpy.ndarray
136
+ Cropped array with padding removed.
137
+
138
+ Raises
139
+ ------
140
+ ValueError
141
+ If scale dimensionality does not match pads, or if non-zero
142
+ pre-padding is provided (this function assumes end-padding only).
143
+ """
144
+ if scale is None:
145
+ scale = (1,) * len(pads)
146
+ if len(scale) != len(pads):
147
+ raise ValueError("Scale must match pad dimensionality.")
148
+ slices = []
149
+ for (before, after), mult in zip(pads, scale):
150
+ if before != 0:
151
+ raise ValueError("Only end-padding is supported.")
152
+ if after >= mult:
153
+ slices.append(slice(0, -(after // mult)))
154
+ else:
155
+ slices.append(slice(None))
156
+ slices.extend([slice(None)] * (data.ndim - len(pads)))
157
+ return data[tuple(slices)]
158
+
159
+
160
+ def pad_for_tiling(
161
+ image: np.ndarray,
162
+ grid: tuple[int, ...],
163
+ tile_shape: tuple[int, ...],
164
+ overlap: tuple[int, ...],
165
+ div_by: tuple[int, ...] | None = None,
166
+ mode: str = "reflect",
167
+ ) -> tuple[np.ndarray, tuple[tuple[int, int], ...]]:
168
+ """Pad input so tiled prediction aligns with overlap and divisibility.
169
+
170
+ Parameters
171
+ ----------
172
+ image : numpy.ndarray
173
+ Input image (2D or 3D).
174
+ grid : tuple[int, ...]
175
+ Model output grid/stride per axis.
176
+ tile_shape : tuple[int, ...]
177
+ Spatial size of each tile in input pixels.
178
+ overlap : tuple[int, ...]
179
+ Overlap per axis in input pixels.
180
+ div_by : tuple[int, ...] or None, optional
181
+ Additional per-axis divisibility constraints. If None, uses ``grid``.
182
+ mode : str, optional
183
+ Padding mode passed to ``numpy.pad``. Default is "reflect".
184
+
185
+ Returns
186
+ -------
187
+ numpy.ndarray
188
+ Padded image as float32.
189
+ tuple[tuple[int, int], ...]
190
+ Padding applied per axis as (before, after) pairs. This implementation
191
+ only pads at the end of each axis (before = 0).
192
+
193
+ Raises
194
+ ------
195
+ ValueError
196
+ If shapes are inconsistent or overlap is invalid.
197
+ """
198
+ if len(grid) != image.ndim:
199
+ raise ValueError("Grid must match image dimensionality.")
200
+ if len(tile_shape) != image.ndim:
201
+ raise ValueError("tile_shape must match image dimensionality.")
202
+ if len(overlap) != image.ndim:
203
+ raise ValueError("overlap must match image dimensionality.")
204
+
205
+ if div_by is None:
206
+ div_by = grid
207
+ if len(div_by) != image.ndim:
208
+ raise ValueError("div_by must match image dimensionality.")
209
+
210
+ pads = []
211
+ for dim, g, ts, ov, d in zip(image.shape, grid, tile_shape, overlap, div_by):
212
+ step = ts - ov
213
+ if step <= 0:
214
+ raise ValueError("overlap must be smaller than tile size.")
215
+ if dim <= ts:
216
+ target = ts
217
+ else:
218
+ target = ts + math.ceil((dim - ts) / step) * step
219
+ div_req = math.lcm(int(g), int(d))
220
+ if div_req > 1:
221
+ while target % div_req != 0:
222
+ target += step
223
+ pads.append((0, int(max(0, target - dim))))
224
+
225
+ padded = np.pad(image, pads, mode=mode)
226
+ return padded.astype(np.float32, copy=False), tuple(pads)
@@ -0,0 +1,5 @@
1
+ """Prediction utilities for ONNX StarDist inference."""
2
+
3
+ from .core import TilingSpec, default_tiling_spec, predict_tiled
4
+
5
+ __all__ = ["TilingSpec", "default_tiling_spec", "predict_tiled"]