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,298 @@
1
+ from __future__ import print_function, unicode_literals, absolute_import, division
2
+ import numpy as np
3
+ from csbdeep.utils import normalize
4
+ from ..matching import matching
5
+ from .plot import random_label_cmap
6
+
7
+
8
+ def _single_color_integer_cmap(color = (.3,.4,.5)):
9
+ from matplotlib.colors import Colormap
10
+
11
+ assert len(color) in (3,4)
12
+
13
+ class BinaryMap(Colormap):
14
+ def __init__(self, color):
15
+ self.color = np.array(color)
16
+ if len(self.color)==3:
17
+ self.color = np.concatenate([self.color,[1]])
18
+ def __call__(self, X, alpha=None, bytes=False):
19
+ res = np.zeros(X.shape+(4,), np.float32)
20
+ res[...,-1] = self.color[-1]
21
+ res[X>0] = np.expand_dims(self.color,0)
22
+ if bytes:
23
+ return np.clip(256*res,0,255).astype(np.uint8)
24
+ else:
25
+ return res
26
+ return BinaryMap(color)
27
+
28
+
29
+
30
+
31
+ def render_label(lbl, img = None, cmap = None, cmap_img = "gray", alpha = 0.5, alpha_boundary = None, normalize_img = True):
32
+ """Renders a label image and optionally overlays it with another image. Used for generating simple output images to asses the label quality
33
+
34
+ Parameters
35
+ ----------
36
+ lbl: np.ndarray of dtype np.uint16
37
+ The 2D label image
38
+ img: np.ndarray
39
+ The array to overlay the label image with (optional)
40
+ cmap: string, tuple, or callable
41
+ The label colormap. If given as rgb(a) only a single color is used, if None uses a random colormap
42
+ cmap_img: string or callable
43
+ The colormap of img (optional)
44
+ alpha: float
45
+ The alpha value of the overlay. Set alpha=1 to get fully opaque labels
46
+ alpha_boundary: float
47
+ The alpha value of the boundary (if None, use the same as for labels, i.e. no boundaries are visible)
48
+ normalize_img: bool
49
+ If True, normalizes the img (if given)
50
+
51
+ Returns
52
+ -------
53
+ img: np.ndarray
54
+ the (m,n,4) RGBA image of the rendered label
55
+
56
+ Example
57
+ -------
58
+
59
+ from scipy.ndimage import label, zoom
60
+ img = zoom(np.random.uniform(0,1,(16,16)),(8,8),order=3)
61
+ lbl,_ = label(img>.8)
62
+ u1 = render_label(lbl, img = img, alpha = .7)
63
+ u2 = render_label(lbl, img = img, alpha = 0, alpha_boundary =.8)
64
+ plt.subplot(1,2,1);plt.imshow(u1)
65
+ plt.subplot(1,2,2);plt.imshow(u2)
66
+
67
+ """
68
+ from skimage.segmentation import find_boundaries
69
+ try:
70
+ from matplotlib.colormaps import get_cmap
71
+ except:
72
+ from matplotlib.cm import get_cmap
73
+
74
+
75
+ alpha = np.clip(alpha, 0, 1)
76
+
77
+ if alpha_boundary is None:
78
+ alpha_boundary = alpha
79
+
80
+ if cmap is None:
81
+ cmap = random_label_cmap()
82
+ elif isinstance(cmap, tuple):
83
+ cmap = _single_color_integer_cmap(cmap)
84
+ else:
85
+ pass
86
+
87
+ cmap = get_cmap(cmap) if isinstance(cmap, str) else cmap
88
+ cmap_img = get_cmap(cmap_img) if isinstance(cmap_img, str) else cmap_img
89
+
90
+ # render image if given
91
+ if img is None:
92
+ im_img = np.zeros(lbl.shape+(4,),np.float32)
93
+ im_img[...,-1] = 1
94
+
95
+ else:
96
+ assert lbl.shape[:2] == img.shape[:2]
97
+ img = normalize(img) if normalize_img else img
98
+ if img.ndim==2:
99
+ im_img = cmap_img(img)
100
+ elif img.ndim==3:
101
+ im_img = img[...,:4]
102
+ if img.shape[-1]<4:
103
+ im_img = np.concatenate([img, np.ones(img.shape[:2]+(4-img.shape[-1],))], axis = -1)
104
+ else:
105
+ raise ValueError("img should be 2 or 3 dimensional")
106
+
107
+
108
+
109
+ # render label
110
+ im_lbl = cmap(lbl)
111
+
112
+ mask_lbl = lbl>0
113
+ mask_bound = np.bitwise_and(mask_lbl,find_boundaries(lbl, mode = "thick"))
114
+
115
+ # blend
116
+ im = im_img.copy()
117
+
118
+ im[mask_lbl] = alpha*im_lbl[mask_lbl]+(1-alpha)*im_img[mask_lbl]
119
+ im[mask_bound] = alpha_boundary*im_lbl[mask_bound]+(1-alpha_boundary)*im_img[mask_bound]
120
+
121
+ return im
122
+
123
+
124
+ def random_hls(n=2**16, h0 = .33, l0 = (.8,1), s0 = (.5,.8) ):
125
+ """
126
+ h0 = 0 -> red
127
+ h0 = 0.33 -> green
128
+ h0 = 0.66 -> blue
129
+ h0 = 0.833 -> magenta
130
+ """
131
+ _f = lambda x: (x,)*2 if np.isscalar(x) else tuple(x)
132
+ h0,s0,l0 = map(_f,(h0,s0,l0))
133
+
134
+ h = np.random.uniform(*h0,n)
135
+ s = np.random.uniform(*s0,n)
136
+ l = np.random.uniform(*l0,n)
137
+
138
+ return h,l,s
139
+
140
+ def cmap_from_hls(h,l,s):
141
+ import matplotlib
142
+ import colorsys
143
+ h = h%1
144
+ l = np.clip(l,0,1)
145
+ s = np.clip(s,0,1)
146
+
147
+ cols = np.stack([colorsys.hls_to_rgb(_h,_l,_s) for _h,_l,_s in zip(h,l,s)],axis=0)
148
+ cols[0] = 0
149
+ return matplotlib.colors.ListedColormap(cols)
150
+
151
+ def match_labels(y0,y):
152
+ """match labels from y to y0"""
153
+ res = matching(y0,y,report_matches=True, thresh =.1)
154
+ if len(res.matched_pairs)==0:
155
+ print("no matching found")
156
+ return y
157
+
158
+ ind_matched0, ind_matched = tuple(zip(*res.matched_pairs))
159
+
160
+ ind_unmatched = (set(np.unique(y))-{0}) - set(ind_matched)
161
+
162
+ leftover_labels = set(np.arange(1,np.max(ind_matched0))) - set(ind_matched0)
163
+
164
+ leftover_labels = leftover_labels.union(set(np.max(ind_matched0)+1+np.arange(len(ind_unmatched)-len(leftover_labels))))
165
+
166
+ assert len(leftover_labels)>= len(ind_unmatched)
167
+
168
+
169
+ print(f"matched: {len(ind_matched)} unmatched: {len(ind_unmatched)}")
170
+ u = np.zeros_like(y)
171
+ for ind0,ind in zip(ind_matched0, ind_matched):
172
+ u[y==ind] = ind0
173
+
174
+ for ind,ind2 in zip(ind_unmatched, leftover_labels):
175
+ u[y==ind] = ind2
176
+
177
+ return u
178
+
179
+
180
+
181
+ def render_label_pred(y_true, y_pred,
182
+ img = None, cmap_img = "gray", normalize_img = True,
183
+ tp_alpha = .6, fp_alpha = .6, fn_alpha = .6,
184
+ matching_kwargs = dict(thresh=0.5)):
185
+ """Renders an image that shows segmentation errors between y_true and y_pred
186
+
187
+ Correctly matched objects (TP) are colored green (with transparency tp_alpha)
188
+ Erronously detected objects (FP) are colored red (with transparency fp_alpha)
189
+ MIssing GT objects (FN) are colored blue (with transparency fn_alpha)
190
+
191
+ Parameters
192
+ ----------
193
+ y_true: np.ndarray of dtype np.uint16
194
+ The 2D GT label image
195
+ y_pred: np.ndarray of dtype np.uint16
196
+ The 2D prediction label image
197
+ img: np.ndarray
198
+ The array to overlay the label image with (optional)
199
+ cmap_img: string or callable
200
+ The colormap of img (optional)
201
+ tp_alpha: float
202
+ The alpha value of the TP
203
+ fp_alpha: float
204
+ The alpha value of the FP
205
+ fn_alpha: float
206
+ The alpha value of the FN
207
+ matching_kwargs: dict
208
+ The parameters of stardist.matching.matching that are used to compute the TP/FP/FN
209
+ normalize_img: bool
210
+ If True, normalizes the img (if given)
211
+
212
+ Returns
213
+ -------
214
+ img: np.ndarray
215
+ the (m,n,4) RGBA image of the rendered image
216
+
217
+ Example
218
+ -------
219
+
220
+ from scipy.ndimage import label, zoom
221
+ img = zoom(np.random.uniform(0,1,(16,16)),(8,8),order=3)
222
+ y_true = label(img>.9)[0]
223
+ y_pred = label(img>.02)[0]
224
+ plt.imshow(render_label_pred(y_true, y_pred, img=img))
225
+
226
+ """
227
+
228
+ try:
229
+ from matplotlib.colormaps import get_cmap
230
+ except:
231
+ from matplotlib.cm import get_cmap
232
+
233
+ assert y_true.shape == y_pred.shape
234
+
235
+ matching_kwargs["report_matches"] = True
236
+ res = matching(y_true, y_pred, **matching_kwargs)
237
+
238
+ all_true = set(np.unique(y_true))-{0}
239
+ all_pred = set(np.unique(y_pred))-{0}
240
+
241
+ pairs = np.array(res.matched_pairs)
242
+ scores = np.array(res.matched_scores)
243
+ ind_tp_pairs = np.where(scores>=matching_kwargs["thresh"])[0]
244
+ tp_true, tp_pred = tuple(zip(*pairs[ind_tp_pairs]))
245
+ tp = tp_pred
246
+ fn = all_true.difference(tp_true)
247
+ fp = all_pred.difference(tp_pred)
248
+
249
+ assert res.tp == len(tp)
250
+ assert res.fp == len(fp)
251
+ assert res.fn == len(fn)
252
+
253
+ mask_tp = np.isin(y_pred, tuple(tp))
254
+ mask_fn = np.isin(y_true, tuple(fn))
255
+ mask_fp = np.isin(y_pred, tuple(fp))
256
+
257
+ def gen_maps(n, h0,l0,s0):
258
+ h,l,s = random_hls(n,h0,l0,s0)
259
+ return cmap_from_hls(h,l,s), cmap_from_hls(h,1.4*l,s)
260
+
261
+ n0 = np.max(y_pred)+1
262
+
263
+ # green
264
+ cmap_tp, cmap_border_tp = gen_maps(n0, h0 = (.25,.35) , l0 = (.4,.6), s0 = (.5,.7))
265
+ # red
266
+ cmap_fp, cmap_border_fp = gen_maps(n0, h0 = (0,.1) , l0 = (.4,.6), s0 = (.5,.7))
267
+ # blue
268
+ cmap_fn, cmap_border_fn = gen_maps(n0, h0 = (.6,.7) , l0 = (.4,.6), s0 = (.5,.7))
269
+
270
+ im_tp = cmap_tp(y_pred)
271
+ im_fp = cmap_fp(y_pred)
272
+ im_fn = cmap_fn(y_true)
273
+
274
+ # render image if given
275
+ if img is None:
276
+ im_img = np.zeros(y_true.shape+(4,),np.float32)
277
+ im_img[...,-1] = 1
278
+ else:
279
+ assert y_true.shape[:2] == img.shape[:2]
280
+ img = normalize(img) if normalize_img else img
281
+ cmap_img = get_cmap(cmap_img) if isinstance(cmap_img, str) else cmap_img
282
+ if img.ndim==2:
283
+ im_img = cmap_img(img)
284
+ elif img.ndim==3:
285
+ im_img = img[...,:4]
286
+ if img.shape[-1]<4:
287
+ im_img = np.concatenate([img, np.ones(img.shape[:2]+(4-img.shape[-1],))], axis = -1)
288
+ else:
289
+ raise ValueError("img should be 2 or 3 dimensional")
290
+
291
+
292
+ # blend
293
+ im = im_img.copy()
294
+
295
+ im[mask_tp] = tp_alpha*im_tp[mask_tp]+(1-tp_alpha)*im_img[mask_tp]
296
+ im[mask_fp] = fp_alpha*im_fp[mask_fp]+(1-fp_alpha)*im_img[mask_fp]
297
+ im[mask_fn] = fn_alpha*im_fn[mask_fn]+(1-fn_alpha)*im_img[mask_fn]
298
+ return im
@@ -0,0 +1,373 @@
1
+ """
2
+ Ray factory
3
+
4
+ classes that provide vertex and triangle information for rays on spheres
5
+
6
+ Example:
7
+
8
+ rays = Rays_Tetra(n_level = 4)
9
+
10
+ print(rays.vertices)
11
+ print(rays.faces)
12
+
13
+ """
14
+ from __future__ import print_function, unicode_literals, absolute_import, division
15
+ import numpy as np
16
+ from scipy.spatial import ConvexHull
17
+ import copy
18
+ import warnings
19
+
20
+ class Rays_Base(object):
21
+ def __init__(self, **kwargs):
22
+ self.kwargs = kwargs
23
+ self._vertices, self._faces = self.setup_vertices_faces()
24
+ self._vertices = np.asarray(self._vertices, np.float32)
25
+ self._faces = np.asarray(self._faces, int)
26
+ self._faces = np.asanyarray(self._faces)
27
+
28
+ def setup_vertices_faces(self):
29
+ """has to return
30
+
31
+ verts , faces
32
+
33
+ verts = ( (z_1,y_1,x_1), ... )
34
+ faces ( (0,1,2), (2,3,4), ... )
35
+
36
+ """
37
+ raise NotImplementedError()
38
+
39
+ @property
40
+ def vertices(self):
41
+ """read-only property"""
42
+ return self._vertices.copy()
43
+
44
+ @property
45
+ def faces(self):
46
+ """read-only property"""
47
+ return self._faces.copy()
48
+
49
+ def __getitem__(self, i):
50
+ return self.vertices[i]
51
+
52
+ def __len__(self):
53
+ return len(self._vertices)
54
+
55
+ def __repr__(self):
56
+ def _conv(x):
57
+ if isinstance(x,(tuple, list, np.ndarray)):
58
+ return "_".join(_conv(_x) for _x in x)
59
+ if isinstance(x,float):
60
+ return "%.2f"%x
61
+ return str(x)
62
+ return "%s_%s" % (self.__class__.__name__, "_".join("%s_%s" % (k, _conv(v)) for k, v in sorted(self.kwargs.items())))
63
+
64
+ def to_json(self):
65
+ return {
66
+ "name": self.__class__.__name__,
67
+ "kwargs": self.kwargs
68
+ }
69
+
70
+ def dist_loss_weights(self, anisotropy = (1,1,1)):
71
+ """returns the anisotropy corrected weights for each ray"""
72
+ anisotropy = np.array(anisotropy)
73
+ assert anisotropy.shape == (3,)
74
+ return np.linalg.norm(self.vertices*anisotropy, axis = -1)
75
+
76
+ def volume(self, dist=None):
77
+ """volume of the starconvex polyhedron spanned by dist (if None, uses dist=1)
78
+ dist can be a nD array, but the last dimension has to be of length n_rays
79
+ """
80
+ if dist is None: dist = np.ones_like(self.vertices)
81
+
82
+ dist = np.asarray(dist)
83
+
84
+ if not dist.shape[-1]==len(self.vertices):
85
+ raise ValueError("last dimension of dist should have length len(rays.vertices)")
86
+ # all the shuffling below is to allow dist to be an arbitrary sized array (with last dim n_rays)
87
+ # self.vertices -> (n_rays,3)
88
+ # dist -> (m,n,..., n_rays)
89
+
90
+ # dist -> (m,n,..., n_rays, 3)
91
+ dist = np.repeat(np.expand_dims(dist,-1), 3, axis = -1)
92
+ # verts -> (m,n,..., n_rays, 3)
93
+ verts = np.broadcast_to(self.vertices, dist.shape)
94
+
95
+ # dist, verts -> (n_rays, m,n, ..., 3)
96
+ dist = np.moveaxis(dist,-2,0)
97
+ verts = np.moveaxis(verts,-2,0)
98
+
99
+ # vs -> (n_faces, 3, m, n, ..., 3)
100
+ vs = (dist*verts)[self.faces]
101
+ # vs -> (n_faces, m, n, ..., 3, 3)
102
+ vs = np.moveaxis(vs, 1,-2)
103
+ # vs -> (n_faces * m * n, 3, 3)
104
+ vs = vs.reshape((len(self.faces)*int(np.prod(dist.shape[1:-1])),3,3))
105
+ d = np.linalg.det(list(vs)).reshape((len(self.faces),)+dist.shape[1:-1])
106
+
107
+ return -1./6*np.sum(d, axis = 0)
108
+
109
+ def surface(self, dist=None):
110
+ """surface area of the starconvex polyhedron spanned by dist (if None, uses dist=1)"""
111
+ dist = np.asarray(dist)
112
+
113
+ if not dist.shape[-1]==len(self.vertices):
114
+ raise ValueError("last dimension of dist should have length len(rays.vertices)")
115
+
116
+ # self.vertices -> (n_rays,3)
117
+ # dist -> (m,n,..., n_rays)
118
+
119
+ # all the shuffling below is to allow dist to be an arbitrary sized array (with last dim n_rays)
120
+
121
+ # dist -> (m,n,..., n_rays, 3)
122
+ dist = np.repeat(np.expand_dims(dist,-1), 3, axis = -1)
123
+ # verts -> (m,n,..., n_rays, 3)
124
+ verts = np.broadcast_to(self.vertices, dist.shape)
125
+
126
+ # dist, verts -> (n_rays, m,n, ..., 3)
127
+ dist = np.moveaxis(dist,-2,0)
128
+ verts = np.moveaxis(verts,-2,0)
129
+
130
+ # vs -> (n_faces, 3, m, n, ..., 3)
131
+ vs = (dist*verts)[self.faces]
132
+ # vs -> (n_faces, m, n, ..., 3, 3)
133
+ vs = np.moveaxis(vs, 1,-2)
134
+ # vs -> (n_faces * m * n, 3, 3)
135
+ vs = vs.reshape((len(self.faces)*int(np.prod(dist.shape[1:-1])),3,3))
136
+
137
+ pa = vs[...,1,:]-vs[...,0,:]
138
+ pb = vs[...,2,:]-vs[...,0,:]
139
+
140
+ d = .5*np.linalg.norm(np.cross(list(pa), list(pb)), axis = -1)
141
+ d = d.reshape((len(self.faces),)+dist.shape[1:-1])
142
+ return np.sum(d, axis = 0)
143
+
144
+
145
+ def copy(self, scale=(1,1,1)):
146
+ """ returns a copy whose vertices are scaled by given factor"""
147
+ scale = np.asarray(scale)
148
+ assert scale.shape == (3,)
149
+ res = copy.deepcopy(self)
150
+ res._vertices *= scale[np.newaxis]
151
+ return res
152
+
153
+
154
+
155
+
156
+ def rays_from_json(d):
157
+ return eval(d["name"])(**d["kwargs"])
158
+
159
+
160
+ ################################################################
161
+
162
+ class Rays_Explicit(Rays_Base):
163
+ def __init__(self, vertices0, faces0):
164
+ self.vertices0, self.faces0 = vertices0, faces0
165
+ super().__init__(vertices0=list(vertices0), faces0=list(faces0))
166
+
167
+ def setup_vertices_faces(self):
168
+ return self.vertices0, self.faces0
169
+
170
+
171
+ class Rays_Cartesian(Rays_Base):
172
+ def __init__(self, n_rays_x=11, n_rays_z=5):
173
+ super().__init__(n_rays_x=n_rays_x, n_rays_z=n_rays_z)
174
+
175
+ def setup_vertices_faces(self):
176
+ """has to return list of ( (z_1,y_1,x_1), ... ) _"""
177
+ n_rays_x, n_rays_z = self.kwargs["n_rays_x"], self.kwargs["n_rays_z"]
178
+ dphi = np.float32(2. * np.pi / n_rays_x)
179
+ dtheta = np.float32(np.pi / n_rays_z)
180
+
181
+ verts = []
182
+ for mz in range(n_rays_z):
183
+ for mx in range(n_rays_x):
184
+ phi = mx * dphi
185
+ theta = mz * dtheta
186
+ if mz == 0:
187
+ theta = 1e-12
188
+ if mz == n_rays_z - 1:
189
+ theta = np.pi - 1e-12
190
+ dx = np.cos(phi) * np.sin(theta)
191
+ dy = np.sin(phi) * np.sin(theta)
192
+ dz = np.cos(theta)
193
+ if mz == 0 or mz == n_rays_z - 1:
194
+ dx += 1e-12
195
+ dy += 1e-12
196
+ verts.append([dz, dy, dx])
197
+
198
+ verts = np.array(verts)
199
+
200
+ def _ind(mz, mx):
201
+ return mz * n_rays_x + mx
202
+
203
+ faces = []
204
+
205
+ for mz in range(n_rays_z - 1):
206
+ for mx in range(n_rays_x):
207
+ faces.append([_ind(mz, mx), _ind(mz + 1, (mx + 1) % n_rays_x), _ind(mz, (mx + 1) % n_rays_x)])
208
+ faces.append([_ind(mz, mx), _ind(mz + 1, mx), _ind(mz + 1, (mx + 1) % n_rays_x)])
209
+
210
+ faces = np.array(faces)
211
+
212
+ return verts, faces
213
+
214
+
215
+ class Rays_SubDivide(Rays_Base):
216
+ """
217
+ Subdivision polyehdra
218
+
219
+ n_level = 1 -> base polyhedra
220
+ n_level = 2 -> 1x subdivision
221
+ n_level = 3 -> 2x subdivision
222
+ ...
223
+ """
224
+
225
+ def __init__(self, n_level=4):
226
+ super().__init__(n_level=n_level)
227
+
228
+ def base_polyhedron(self):
229
+ raise NotImplementedError()
230
+
231
+ def setup_vertices_faces(self):
232
+ n_level = self.kwargs["n_level"]
233
+ verts0, faces0 = self.base_polyhedron()
234
+ return self._recursive_split(verts0, faces0, n_level)
235
+
236
+ def _recursive_split(self, verts, faces, n_level):
237
+ if n_level <= 1:
238
+ return verts, faces
239
+ else:
240
+ verts, faces = Rays_SubDivide.split(verts, faces)
241
+ return self._recursive_split(verts, faces, n_level - 1)
242
+
243
+ @classmethod
244
+ def split(self, verts0, faces0):
245
+ """split a level"""
246
+
247
+ split_edges = dict()
248
+ verts = list(verts0[:])
249
+ faces = []
250
+
251
+ def _add(a, b):
252
+ """ returns index of middle point and adds vertex if not already added"""
253
+ edge = tuple(sorted((a, b)))
254
+ if not edge in split_edges:
255
+ v = .5 * (verts[a] + verts[b])
256
+ v *= 1. / np.linalg.norm(v)
257
+ verts.append(v)
258
+ split_edges[edge] = len(verts) - 1
259
+ return split_edges[edge]
260
+
261
+ for v1, v2, v3 in faces0:
262
+ ind1 = _add(v1, v2)
263
+ ind2 = _add(v2, v3)
264
+ ind3 = _add(v3, v1)
265
+ faces.append([v1, ind1, ind3])
266
+ faces.append([v2, ind2, ind1])
267
+ faces.append([v3, ind3, ind2])
268
+ faces.append([ind1, ind2, ind3])
269
+
270
+ return verts, faces
271
+
272
+
273
+ class Rays_Tetra(Rays_SubDivide):
274
+ """
275
+ Subdivision of a tetrahedron
276
+
277
+ n_level = 1 -> normal tetrahedron (4 vertices)
278
+ n_level = 2 -> 1x subdivision (10 vertices)
279
+ n_level = 3 -> 2x subdivision (34 vertices)
280
+ ...
281
+ """
282
+
283
+ def base_polyhedron(self):
284
+ verts = np.array([
285
+ [np.sqrt(8. / 9), 0., -1. / 3],
286
+ [-np.sqrt(2. / 9), np.sqrt(2. / 3), -1. / 3],
287
+ [-np.sqrt(2. / 9), -np.sqrt(2. / 3), -1. / 3],
288
+ [0., 0., 1.]
289
+ ])
290
+ faces = [[0, 1, 2],
291
+ [0, 3, 1],
292
+ [0, 2, 3],
293
+ [1, 3, 2]]
294
+
295
+ return verts, faces
296
+
297
+
298
+ class Rays_Octo(Rays_SubDivide):
299
+ """
300
+ Subdivision of a tetrahedron
301
+
302
+ n_level = 1 -> normal Octahedron (6 vertices)
303
+ n_level = 2 -> 1x subdivision (18 vertices)
304
+ n_level = 3 -> 2x subdivision (66 vertices)
305
+
306
+ """
307
+
308
+ def base_polyhedron(self):
309
+ verts = np.array([
310
+ [0, 0, 1],
311
+ [0, 1, 0],
312
+ [0, 0, -1],
313
+ [0, -1, 0],
314
+ [1, 0, 0],
315
+ [-1, 0, 0]])
316
+
317
+ faces = [[0, 1, 4],
318
+ [0, 5, 1],
319
+ [1, 2, 4],
320
+ [1, 5, 2],
321
+ [2, 3, 4],
322
+ [2, 5, 3],
323
+ [3, 0, 4],
324
+ [3, 5, 0],
325
+ ]
326
+
327
+ return verts, faces
328
+
329
+
330
+ def reorder_faces(verts, faces):
331
+ """reorder faces such that their orientation points outward"""
332
+ def _single(face):
333
+ return face[::-1] if np.linalg.det(verts[face])>0 else face
334
+ return tuple(map(_single, faces))
335
+
336
+
337
+ class Rays_GoldenSpiral(Rays_Base):
338
+ def __init__(self, n=70, anisotropy = None):
339
+ if n<4:
340
+ raise ValueError("At least 4 points have to be given!")
341
+ super().__init__(n=n, anisotropy = anisotropy if anisotropy is None else tuple(anisotropy))
342
+
343
+ def setup_vertices_faces(self):
344
+ n = self.kwargs["n"]
345
+ anisotropy = self.kwargs["anisotropy"]
346
+ if anisotropy is None:
347
+ anisotropy = np.ones(3)
348
+ else:
349
+ anisotropy = np.array(anisotropy)
350
+
351
+ # the smaller golden angle = 2pi * 0.3819...
352
+ g = (3. - np.sqrt(5.)) * np.pi
353
+ phi = g * np.arange(n)
354
+ # z = np.linspace(-1, 1, n + 2)[1:-1]
355
+ # rho = np.sqrt(1. - z ** 2)
356
+ # verts = np.stack([rho*np.cos(phi), rho*np.sin(phi),z]).T
357
+ #
358
+ z = np.linspace(-1, 1, n)
359
+ rho = np.sqrt(1. - z ** 2)
360
+ verts = np.stack([z, rho * np.sin(phi), rho * np.cos(phi)]).T
361
+
362
+ # warnings.warn("ray definition has changed! Old results are invalid!")
363
+
364
+ # correct for anisotropy
365
+ verts = verts/anisotropy
366
+ #verts /= np.linalg.norm(verts, axis=-1, keepdims=True)
367
+
368
+ hull = ConvexHull(verts)
369
+ faces = reorder_faces(verts,hull.simplices)
370
+
371
+ verts /= np.linalg.norm(verts, axis=-1, keepdims=True)
372
+
373
+ return verts, faces