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.
- senoquant/__init__.py +6 -0
- senoquant/_reader.py +7 -0
- senoquant/_widget.py +33 -0
- senoquant/napari.yaml +83 -0
- senoquant/reader/__init__.py +5 -0
- senoquant/reader/core.py +369 -0
- senoquant/tabs/__init__.py +15 -0
- senoquant/tabs/batch/__init__.py +10 -0
- senoquant/tabs/batch/backend.py +641 -0
- senoquant/tabs/batch/config.py +270 -0
- senoquant/tabs/batch/frontend.py +1283 -0
- senoquant/tabs/batch/io.py +326 -0
- senoquant/tabs/batch/layers.py +86 -0
- senoquant/tabs/quantification/__init__.py +1 -0
- senoquant/tabs/quantification/backend.py +228 -0
- senoquant/tabs/quantification/features/__init__.py +80 -0
- senoquant/tabs/quantification/features/base.py +142 -0
- senoquant/tabs/quantification/features/marker/__init__.py +5 -0
- senoquant/tabs/quantification/features/marker/config.py +69 -0
- senoquant/tabs/quantification/features/marker/dialog.py +437 -0
- senoquant/tabs/quantification/features/marker/export.py +879 -0
- senoquant/tabs/quantification/features/marker/feature.py +119 -0
- senoquant/tabs/quantification/features/marker/morphology.py +285 -0
- senoquant/tabs/quantification/features/marker/rows.py +654 -0
- senoquant/tabs/quantification/features/marker/thresholding.py +46 -0
- senoquant/tabs/quantification/features/roi.py +346 -0
- senoquant/tabs/quantification/features/spots/__init__.py +5 -0
- senoquant/tabs/quantification/features/spots/config.py +62 -0
- senoquant/tabs/quantification/features/spots/dialog.py +477 -0
- senoquant/tabs/quantification/features/spots/export.py +1292 -0
- senoquant/tabs/quantification/features/spots/feature.py +112 -0
- senoquant/tabs/quantification/features/spots/morphology.py +279 -0
- senoquant/tabs/quantification/features/spots/rows.py +241 -0
- senoquant/tabs/quantification/frontend.py +815 -0
- senoquant/tabs/segmentation/__init__.py +1 -0
- senoquant/tabs/segmentation/backend.py +131 -0
- senoquant/tabs/segmentation/frontend.py +1009 -0
- senoquant/tabs/segmentation/models/__init__.py +5 -0
- senoquant/tabs/segmentation/models/base.py +146 -0
- senoquant/tabs/segmentation/models/cpsam/details.json +65 -0
- senoquant/tabs/segmentation/models/cpsam/model.py +150 -0
- senoquant/tabs/segmentation/models/default_2d/details.json +69 -0
- senoquant/tabs/segmentation/models/default_2d/model.py +664 -0
- senoquant/tabs/segmentation/models/default_3d/details.json +69 -0
- senoquant/tabs/segmentation/models/default_3d/model.py +682 -0
- senoquant/tabs/segmentation/models/hf.py +71 -0
- senoquant/tabs/segmentation/models/nuclear_dilation/__init__.py +1 -0
- senoquant/tabs/segmentation/models/nuclear_dilation/details.json +26 -0
- senoquant/tabs/segmentation/models/nuclear_dilation/model.py +96 -0
- senoquant/tabs/segmentation/models/perinuclear_rings/__init__.py +1 -0
- senoquant/tabs/segmentation/models/perinuclear_rings/details.json +34 -0
- senoquant/tabs/segmentation/models/perinuclear_rings/model.py +132 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/__init__.py +2 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/__init__.py +3 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/__init__.py +6 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/generate.py +470 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/prepare.py +273 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/rawdata.py +112 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/data/transform.py +384 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/__init__.py +0 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/blocks.py +184 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/losses.py +79 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/nets.py +165 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/predict.py +467 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/probability.py +67 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/internals/train.py +148 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/io/__init__.py +163 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/__init__.py +52 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/base_model.py +329 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_isotropic.py +160 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_projection.py +178 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_standard.py +446 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/care_upsampling.py +54 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/config.py +254 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/models/pretrained.py +119 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/scripts/__init__.py +0 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/scripts/care_predict.py +180 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/__init__.py +5 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/plot_utils.py +159 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/six.py +18 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/tf.py +644 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/utils/utils.py +272 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/csbdeep/version.py +1 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/docs/source/conf.py +368 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/setup.py +68 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_datagen.py +169 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_models.py +462 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tests/test_utils.py +166 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_csbdeep/tools/create_zip_contents.py +34 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/__init__.py +30 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/big.py +624 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/bioimageio_utils.py +494 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/data/__init__.py +39 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/__init__.py +10 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/geom2d.py +215 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/geometry/geom3d.py +349 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/matching.py +483 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/__init__.py +28 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/base.py +1217 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/model2d.py +594 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/models/model3d.py +696 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/nms.py +384 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/__init__.py +2 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/plot.py +74 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/plot/render.py +298 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/rays3d.py +373 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/sample_patches.py +65 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/__init__.py +0 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/predict2d.py +90 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/scripts/predict3d.py +93 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/utils.py +408 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/_stardist/version.py +1 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/__init__.py +45 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/__init__.py +17 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/cli.py +55 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/convert/core.py +285 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/__init__.py +15 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/cli.py +36 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/divisibility.py +193 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/probe.py +100 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/receptive_field.py +182 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/rf_cli.py +48 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/inspect/valid_sizes.py +278 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/post/__init__.py +8 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/post/core.py +157 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/pre/__init__.py +17 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/pre/core.py +226 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/predict/__init__.py +5 -0
- senoquant/tabs/segmentation/stardist_onnx_utils/onnx_framework/predict/core.py +401 -0
- senoquant/tabs/settings/__init__.py +1 -0
- senoquant/tabs/settings/backend.py +29 -0
- senoquant/tabs/settings/frontend.py +19 -0
- senoquant/tabs/spots/__init__.py +1 -0
- senoquant/tabs/spots/backend.py +139 -0
- senoquant/tabs/spots/frontend.py +800 -0
- senoquant/tabs/spots/models/__init__.py +5 -0
- senoquant/tabs/spots/models/base.py +94 -0
- senoquant/tabs/spots/models/rmp/details.json +61 -0
- senoquant/tabs/spots/models/rmp/model.py +499 -0
- senoquant/tabs/spots/models/udwt/details.json +103 -0
- senoquant/tabs/spots/models/udwt/model.py +482 -0
- senoquant/utils.py +25 -0
- senoquant-1.0.0b1.dist-info/METADATA +193 -0
- senoquant-1.0.0b1.dist-info/RECORD +148 -0
- senoquant-1.0.0b1.dist-info/WHEEL +5 -0
- senoquant-1.0.0b1.dist-info/entry_points.txt +2 -0
- senoquant-1.0.0b1.dist-info/licenses/LICENSE +28 -0
- 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
|