PYME-extra 1.0.4.post0__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.
- PYMEcs/Acquire/Actions/__init__.py +0 -0
- PYMEcs/Acquire/Actions/custom.py +167 -0
- PYMEcs/Acquire/Hardware/LPthreadedSimple.py +248 -0
- PYMEcs/Acquire/Hardware/LPthreadedSimpleSim.py +246 -0
- PYMEcs/Acquire/Hardware/NikonTiFlaskServer.py +45 -0
- PYMEcs/Acquire/Hardware/NikonTiFlaskServerT.py +59 -0
- PYMEcs/Acquire/Hardware/NikonTiRESTClient.py +73 -0
- PYMEcs/Acquire/Hardware/NikonTiSim.py +35 -0
- PYMEcs/Acquire/Hardware/__init__.py +0 -0
- PYMEcs/Acquire/Hardware/driftTrackGUI.py +329 -0
- PYMEcs/Acquire/Hardware/driftTrackGUI_n.py +472 -0
- PYMEcs/Acquire/Hardware/driftTracking.py +424 -0
- PYMEcs/Acquire/Hardware/driftTracking_n.py +433 -0
- PYMEcs/Acquire/Hardware/fakeCamX.py +15 -0
- PYMEcs/Acquire/Hardware/offsetPiezoRESTCorrelLog.py +38 -0
- PYMEcs/Acquire/__init__.py +0 -0
- PYMEcs/Analysis/MBMcollection.py +552 -0
- PYMEcs/Analysis/MINFLUX.py +280 -0
- PYMEcs/Analysis/MapUtils.py +77 -0
- PYMEcs/Analysis/NPC.py +1176 -0
- PYMEcs/Analysis/Paraflux.py +218 -0
- PYMEcs/Analysis/Simpler.py +81 -0
- PYMEcs/Analysis/Sofi.py +140 -0
- PYMEcs/Analysis/__init__.py +0 -0
- PYMEcs/Analysis/decSofi.py +211 -0
- PYMEcs/Analysis/eventProperties.py +50 -0
- PYMEcs/Analysis/fitDarkTimes.py +569 -0
- PYMEcs/Analysis/objectVolumes.py +20 -0
- PYMEcs/Analysis/offlineTracker.py +130 -0
- PYMEcs/Analysis/stackTracker.py +180 -0
- PYMEcs/Analysis/timeSeries.py +63 -0
- PYMEcs/Analysis/trackFiducials.py +186 -0
- PYMEcs/Analysis/zerocross.py +91 -0
- PYMEcs/IO/MINFLUX.py +851 -0
- PYMEcs/IO/NPC.py +117 -0
- PYMEcs/IO/__init__.py +0 -0
- PYMEcs/IO/darkTimes.py +19 -0
- PYMEcs/IO/picasso.py +219 -0
- PYMEcs/IO/tabular.py +11 -0
- PYMEcs/__init__.py +0 -0
- PYMEcs/experimental/CalcZfactor.py +51 -0
- PYMEcs/experimental/FRC.py +338 -0
- PYMEcs/experimental/ImageJROItools.py +49 -0
- PYMEcs/experimental/MINFLUX.py +1537 -0
- PYMEcs/experimental/NPCcalcLM.py +560 -0
- PYMEcs/experimental/Simpler.py +369 -0
- PYMEcs/experimental/Sofi.py +78 -0
- PYMEcs/experimental/__init__.py +0 -0
- PYMEcs/experimental/binEventProperty.py +187 -0
- PYMEcs/experimental/chaining.py +23 -0
- PYMEcs/experimental/clusterTrack.py +179 -0
- PYMEcs/experimental/combine_maps.py +104 -0
- PYMEcs/experimental/eventProcessing.py +93 -0
- PYMEcs/experimental/fiducials.py +323 -0
- PYMEcs/experimental/fiducialsNew.py +402 -0
- PYMEcs/experimental/mapTools.py +271 -0
- PYMEcs/experimental/meas2DplotDh5view.py +107 -0
- PYMEcs/experimental/mortensen.py +131 -0
- PYMEcs/experimental/ncsDenoise.py +158 -0
- PYMEcs/experimental/onTimes.py +295 -0
- PYMEcs/experimental/procPoints.py +77 -0
- PYMEcs/experimental/pyme2caml.py +73 -0
- PYMEcs/experimental/qPAINT.py +965 -0
- PYMEcs/experimental/randMap.py +188 -0
- PYMEcs/experimental/regExtraCmaps.py +11 -0
- PYMEcs/experimental/selectROIfilterTable.py +72 -0
- PYMEcs/experimental/showErrs.py +51 -0
- PYMEcs/experimental/showErrsDh5view.py +58 -0
- PYMEcs/experimental/showShiftMap.py +56 -0
- PYMEcs/experimental/snrEvents.py +188 -0
- PYMEcs/experimental/specLabeling.py +51 -0
- PYMEcs/experimental/splitRender.py +246 -0
- PYMEcs/experimental/testChannelByName.py +36 -0
- PYMEcs/experimental/timedSpecies.py +28 -0
- PYMEcs/experimental/utils.py +31 -0
- PYMEcs/misc/ExtraCmaps.py +177 -0
- PYMEcs/misc/__init__.py +0 -0
- PYMEcs/misc/configUtils.py +169 -0
- PYMEcs/misc/guiMsgBoxes.py +27 -0
- PYMEcs/misc/mapUtils.py +230 -0
- PYMEcs/misc/matplotlib.py +136 -0
- PYMEcs/misc/rectsFromSVG.py +182 -0
- PYMEcs/misc/shellutils.py +1110 -0
- PYMEcs/misc/utils.py +205 -0
- PYMEcs/misc/versionCheck.py +20 -0
- PYMEcs/misc/zcInfo.py +90 -0
- PYMEcs/pyme_warnings.py +4 -0
- PYMEcs/recipes/__init__.py +0 -0
- PYMEcs/recipes/base.py +75 -0
- PYMEcs/recipes/localisations.py +2380 -0
- PYMEcs/recipes/manipulate_yaml.py +83 -0
- PYMEcs/recipes/output.py +177 -0
- PYMEcs/recipes/processing.py +247 -0
- PYMEcs/recipes/simpler.py +290 -0
- PYMEcs/version.py +2 -0
- pyme_extra-1.0.4.post0.dist-info/METADATA +114 -0
- pyme_extra-1.0.4.post0.dist-info/RECORD +101 -0
- pyme_extra-1.0.4.post0.dist-info/WHEEL +5 -0
- pyme_extra-1.0.4.post0.dist-info/entry_points.txt +3 -0
- pyme_extra-1.0.4.post0.dist-info/licenses/LICENSE +674 -0
- pyme_extra-1.0.4.post0.dist-info/top_level.txt +1 -0
PYMEcs/Analysis/NPC.py
ADDED
|
@@ -0,0 +1,1176 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import numpy as np
|
|
3
|
+
from PYMEcs.pyme_warnings import warn
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
piover4 = np.pi/4.0
|
|
9
|
+
|
|
10
|
+
# from circle_fit import taubinSVD
|
|
11
|
+
# def fitcirc(x,y,sigma=None):
|
|
12
|
+
# pcs = np.vstack((x,y)).T
|
|
13
|
+
# xc, yc, r, sigma = taubinSVD(pcs)
|
|
14
|
+
# return (xc, yc, r, sigma)
|
|
15
|
+
|
|
16
|
+
# def centreshift(x,y,xc,yc):
|
|
17
|
+
# return (x-xc,y-yc)
|
|
18
|
+
|
|
19
|
+
# def plot_segments(rad,rotang=0):
|
|
20
|
+
# for i in range(8):
|
|
21
|
+
# ang = i * np.pi / 4.0
|
|
22
|
+
# plt.plot([0,rad*np.cos(ang+rotang)],[0,rad*np.sin(ang+rotang)],'b')
|
|
23
|
+
|
|
24
|
+
# def phi_from_coords(xn,yn):
|
|
25
|
+
# phis = np.arctan2(yn,xn)
|
|
26
|
+
# return phis
|
|
27
|
+
|
|
28
|
+
# def rot_coords(xn,yn,ang):
|
|
29
|
+
# c, s = np.cos(ang), np.sin(ang)
|
|
30
|
+
# R = np.array(((c, -s), (s, c)))
|
|
31
|
+
# pcs = np.vstack((xn,yn))
|
|
32
|
+
# crot = R @ pcs
|
|
33
|
+
|
|
34
|
+
# return (crot.T[:,0],crot.T[:,1])
|
|
35
|
+
|
|
36
|
+
from circle_fit import taubinSVD
|
|
37
|
+
def fitcirc(x,y,sigma=None):
|
|
38
|
+
pcs = np.vstack((x,y)).T
|
|
39
|
+
xc, yc, r, sigma = taubinSVD(pcs)
|
|
40
|
+
return (xc, yc, r, sigma)
|
|
41
|
+
|
|
42
|
+
def centreshift(x,y,xc,yc):
|
|
43
|
+
return (x-xc,y-yc)
|
|
44
|
+
|
|
45
|
+
def plot_segments(rad,rotang=0,ax=None):
|
|
46
|
+
if ax is None:
|
|
47
|
+
ax = plt.gca()
|
|
48
|
+
for i in range(8):
|
|
49
|
+
ang = i * np.pi / 4.0
|
|
50
|
+
ax.plot([0,rad*np.cos(ang+rotang)],[0,rad*np.sin(ang+rotang)],'b')
|
|
51
|
+
|
|
52
|
+
def phi_from_coords(xn,yn):
|
|
53
|
+
phis = np.arctan2(yn,xn)
|
|
54
|
+
return phis
|
|
55
|
+
|
|
56
|
+
def r_from_coords(xn,yn):
|
|
57
|
+
r = np.sqrt(yn*yn+xn*xn)
|
|
58
|
+
return r
|
|
59
|
+
|
|
60
|
+
# this implementation seems broken; retire for now
|
|
61
|
+
def estimate_rotation(xn,yn,spacing=1.0,mode='abs',do_plot=False,secondpass=False):
|
|
62
|
+
n_ang = int(45.0/spacing)
|
|
63
|
+
rotrad = np.arange(n_ang)*piover4/n_ang
|
|
64
|
+
phis = phi_from_coords(xn,yn)
|
|
65
|
+
r = r_from_coords(xn,yn)
|
|
66
|
+
frac, integ = np.modf((np.pi+phis)/piover4)
|
|
67
|
+
if mode == 'abs':
|
|
68
|
+
sqdiff = [ np.sum(np.abs(rd - frac*piover4)) for rd in rotrad]
|
|
69
|
+
elif mode == 'square':
|
|
70
|
+
sqdiff = [ np.sum((rd - frac*piover4)**2) for rd in rotrad]
|
|
71
|
+
else:
|
|
72
|
+
raise RuntimeError("unknown estimation mode %s" % mode)
|
|
73
|
+
|
|
74
|
+
if do_plot:
|
|
75
|
+
fig,ax = plt.subplots(2,1)
|
|
76
|
+
ax[0].scatter(np.degrees(rotrad),sqdiff)
|
|
77
|
+
ax[1].scatter(np.degrees(phis),r,alpha=0.4)
|
|
78
|
+
|
|
79
|
+
indmin = np.argmin(sqdiff)
|
|
80
|
+
radmin = rotrad[indmin]
|
|
81
|
+
radrot = piover4/2.0-radmin
|
|
82
|
+
|
|
83
|
+
if secondpass:
|
|
84
|
+
xn2, yn2 = rot_coords(xn,yn,radrot)
|
|
85
|
+
radrot2 = estimate_rotation(xn2,yn2,spacing=spacing,mode=mode,do_plot=do_plot,secondpass=False)
|
|
86
|
+
radrot = radrot+radrot2
|
|
87
|
+
|
|
88
|
+
return radrot
|
|
89
|
+
|
|
90
|
+
# FIXED up optimal rotation estimator
|
|
91
|
+
# we calculate a metric that looks at angles of events and is designed to have the smallest penalty (zero)
|
|
92
|
+
# at the center of a pi/4 (45 deg) segment and increases linearly or squarely towards the edges of the
|
|
93
|
+
# segments
|
|
94
|
+
# we do this by calculating the fractional part of the angle modulo pi/4 and subtract 0.5 so that the center
|
|
95
|
+
# gets a value of 0 and edges go to +- 0.5; we then take abs or squares of this "angle penalty" and sum these up
|
|
96
|
+
# we do this by rotating angles by a range of 0..pi/4 and then find the rotation angle that minimises the total penalty
|
|
97
|
+
# NOTE: important property of a well working routine: if we rotate the data the determined "optimal rotation"
|
|
98
|
+
# should shift linearly with this external rotation
|
|
99
|
+
def estimate_rotation2(xn,yn,spacing=1.0,mode='abs',do_plot=False):
|
|
100
|
+
n_ang = int(45.0/spacing)
|
|
101
|
+
rotrad = np.arange(n_ang)*piover4/n_ang
|
|
102
|
+
phis = phi_from_coords(xn,yn)
|
|
103
|
+
r = r_from_coords(xn,yn)
|
|
104
|
+
|
|
105
|
+
sqdiff = []
|
|
106
|
+
if mode == 'abs':
|
|
107
|
+
for rd in rotrad:
|
|
108
|
+
frac, integ = np.modf((np.pi+phis+rd)/piover4)
|
|
109
|
+
sqdiff.append(np.sum(np.abs(frac-0.5)))
|
|
110
|
+
elif mode == 'square':
|
|
111
|
+
for rd in rotrad:
|
|
112
|
+
frac, integ = np.modf((np.pi+phis+rd)/piover4)
|
|
113
|
+
sqdiff.append(np.sum((frac-0.5)**2))
|
|
114
|
+
else:
|
|
115
|
+
raise RuntimeError("unknown estimation mode %s" % mode)
|
|
116
|
+
|
|
117
|
+
if do_plot:
|
|
118
|
+
fig,ax = plt.subplots(2,1)
|
|
119
|
+
ax[0].scatter(np.degrees(rotrad),sqdiff)
|
|
120
|
+
ax[1].scatter(np.degrees(phis),r,alpha=0.4)
|
|
121
|
+
ax[1].set_ylim(0,80)
|
|
122
|
+
|
|
123
|
+
indmin = np.argmin(sqdiff)
|
|
124
|
+
radmin = rotrad[indmin]
|
|
125
|
+
|
|
126
|
+
return radmin
|
|
127
|
+
|
|
128
|
+
def rot_coords(xn,yn,ang):
|
|
129
|
+
c, s = np.cos(ang), np.sin(ang)
|
|
130
|
+
R = np.array(((c, -s), (s, c)))
|
|
131
|
+
pcs = np.vstack((xn,yn))
|
|
132
|
+
crot = R @ pcs
|
|
133
|
+
|
|
134
|
+
return (crot.T[:,0],crot.T[:,1])
|
|
135
|
+
|
|
136
|
+
def rfilt(xn,yn,r0,dr=25.0):
|
|
137
|
+
|
|
138
|
+
ri = np.sqrt(xn*xn+yn*yn)
|
|
139
|
+
rimask = (ri >r0-dr)*(ri < r0+dr)
|
|
140
|
+
return (xn[rimask],yn[rimask])
|
|
141
|
+
|
|
142
|
+
def estimate_nlabeled(x,y,r0=None,nthresh=10,dr=30.0,rotation=None,
|
|
143
|
+
do_plot=False,secondpass=False,fitmode='abs',return_radius=False,return_bysegments=False):
|
|
144
|
+
if r0 is None:
|
|
145
|
+
xc, yc, r0, sigma = fitcirc(x,y)
|
|
146
|
+
xn, yn = centreshift(x, y, xc, yc)
|
|
147
|
+
if rotation is None:
|
|
148
|
+
radrot = estimate_rotation2(xn,yn,mode=fitmode)
|
|
149
|
+
else:
|
|
150
|
+
radrot = rotation
|
|
151
|
+
xr1,yr1 = rot_coords(xn,yn,radrot)
|
|
152
|
+
xr, yr = rfilt(xr1,yr1,r0,dr=dr)
|
|
153
|
+
if secondpass:
|
|
154
|
+
xc2, yc2, r0, sigma = fitcirc(xr,yr)
|
|
155
|
+
xn, yn = centreshift(xr, yr, xc2, yc2)
|
|
156
|
+
if rotation is None:
|
|
157
|
+
radrot = estimate_rotation2(xn,yn,mode=fitmode)
|
|
158
|
+
else:
|
|
159
|
+
radrot = rotation
|
|
160
|
+
xr,yr = rot_coords(xn,yn,radrot)
|
|
161
|
+
else:
|
|
162
|
+
if rotation is None:
|
|
163
|
+
radrot = estimate_rotation2(x,y,mode=fitmode)
|
|
164
|
+
else:
|
|
165
|
+
radrot = rotation
|
|
166
|
+
xr1,yr1 = rot_coords(x,y,radrot)
|
|
167
|
+
xr, yr = rfilt(xr1,yr1,r0,dr=dr)
|
|
168
|
+
|
|
169
|
+
phis = phi_from_coords(xr,yr)
|
|
170
|
+
phibinedges = -np.pi + piover4*np.arange(9)
|
|
171
|
+
nhist,be = np.histogram(phis,bins=phibinedges)
|
|
172
|
+
Nlabeled = np.sum(nhist>=nthresh)
|
|
173
|
+
|
|
174
|
+
if do_plot:
|
|
175
|
+
segment_radius = 80.0
|
|
176
|
+
fig, axs = plt.subplots(2)
|
|
177
|
+
# first subplot
|
|
178
|
+
axs[0].set_aspect('equal')
|
|
179
|
+
axs[0].scatter(xr,yr,s=10,alpha=0.4,edgecolors='none')
|
|
180
|
+
axs[0].scatter([0],[0],marker='+')
|
|
181
|
+
plot_segments(segment_radius,ax=axs[0])
|
|
182
|
+
cir2 = plt.Circle((0, 0), r0, color='r',fill=False)
|
|
183
|
+
axs[0].add_patch(cir2)
|
|
184
|
+
from matplotlib.patches import Wedge
|
|
185
|
+
phibinedges_deg = np.degrees(phibinedges)
|
|
186
|
+
|
|
187
|
+
phibincenters = 0.5*(phibinedges[0:-1]+phibinedges[1:])
|
|
188
|
+
textradius = segment_radius + 13
|
|
189
|
+
for i,phic in enumerate(phibincenters):
|
|
190
|
+
xt,yt = (textradius*np.cos(phic),textradius*np.sin(phic))
|
|
191
|
+
axs[0].text(xt,yt,str(i+1),horizontalalignment='center', # we number segments from 1 to 8
|
|
192
|
+
verticalalignment='center',alpha=0.4)
|
|
193
|
+
axs[0].set_xlim(-100,100)
|
|
194
|
+
axs[0].set_ylim(-100,100)
|
|
195
|
+
for i in range(nhist.size):
|
|
196
|
+
if nhist[i] >= nthresh:
|
|
197
|
+
axs[0].add_patch(Wedge(
|
|
198
|
+
(0, 0), # (x,y)
|
|
199
|
+
segment_radius, # radius
|
|
200
|
+
phibinedges_deg[i], # theta1 (in degrees)
|
|
201
|
+
phibinedges_deg[i+1], # theta2
|
|
202
|
+
color="r", alpha=0.1))
|
|
203
|
+
axs[0].set_title('NPC Segments = %d, r0 = %.1f nm\nEvent threshold = %d, mode = %s, rot= %.2f rad' % (Nlabeled,r0,nthresh,fitmode,radrot))
|
|
204
|
+
axs[0].invert_yaxis() # the y axis direction seems inverted WRT PYMEVisualise, so try to make equal
|
|
205
|
+
|
|
206
|
+
# second suplot
|
|
207
|
+
def radtosegno(rad):
|
|
208
|
+
return (rad + np.pi + 0.5*piover4) / piover4
|
|
209
|
+
|
|
210
|
+
def segnotorad(sec):
|
|
211
|
+
return -np.pi + 0.5*piover4 + sec * piover4
|
|
212
|
+
|
|
213
|
+
axs[1].hist(phis,bins=phibinedges)
|
|
214
|
+
axs[1].plot([phibinedges[0],phibinedges[-1]],[nthresh,nthresh],'r--')
|
|
215
|
+
axs[1].set_xlabel('Angle range $\phi$, $\pi/4$ per segment (radians -$\pi,\cdots,\pi$)')
|
|
216
|
+
axs[1].set_ylabel('Events in segment')
|
|
217
|
+
secax = axs[1].secondary_xaxis('top', functions=(radtosegno, segnotorad))
|
|
218
|
+
secax.set_xlabel('segment number')
|
|
219
|
+
plt.tight_layout()
|
|
220
|
+
|
|
221
|
+
if return_radius:
|
|
222
|
+
return (Nlabeled,r0)
|
|
223
|
+
elif return_bysegments:
|
|
224
|
+
return (Nlabeled,nhist)
|
|
225
|
+
else:
|
|
226
|
+
return Nlabeled
|
|
227
|
+
|
|
228
|
+
from scipy.special import binom
|
|
229
|
+
from scipy.optimize import curve_fit
|
|
230
|
+
|
|
231
|
+
def pn(k,n,p):
|
|
232
|
+
return (binom(n,k)*(np.power(p,k)*np.power((1-p),(n-k))))
|
|
233
|
+
|
|
234
|
+
def pnpc(k,plabel):
|
|
235
|
+
pbright = 1-pn(0,4,plabel)
|
|
236
|
+
p_k_npc = pn(k,8,pbright)
|
|
237
|
+
|
|
238
|
+
return p_k_npc
|
|
239
|
+
|
|
240
|
+
# this is the formula for the 16-spot 3D arrangement, with 2 chances to label per spot
|
|
241
|
+
def pnpc3d(k,plabel):
|
|
242
|
+
pbright = 1-pn(0,2,plabel)
|
|
243
|
+
p_k_npc = pn(k,16,pbright)
|
|
244
|
+
|
|
245
|
+
return p_k_npc
|
|
246
|
+
|
|
247
|
+
def pnpc3dc(kfit,plabel):
|
|
248
|
+
krange = np.arange(17,dtype='i')
|
|
249
|
+
# important point: we must always first evealuate the probability expressions at the
|
|
250
|
+
# canonical points (0-16), form the cumluative sum and only then
|
|
251
|
+
# interpolate onto the coordinates where the fit is tested in a second step
|
|
252
|
+
pc = np.cumsum(pnpc3d(krange,plabel))
|
|
253
|
+
return np.interp(kfit,krange,pc)
|
|
254
|
+
|
|
255
|
+
def prangeNPC3D():
|
|
256
|
+
krange = np.arange(17,dtype='i')
|
|
257
|
+
prange=0.1*np.arange(1,10)
|
|
258
|
+
|
|
259
|
+
probs = {}
|
|
260
|
+
probs['krange'] = krange
|
|
261
|
+
for p in prange:
|
|
262
|
+
probs[p] = pnpc3d(krange,p)
|
|
263
|
+
|
|
264
|
+
return probs
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def npclabel_fit(nphist,sigma=None):
|
|
268
|
+
npnormed = nphist/nphist.sum()
|
|
269
|
+
ks = np.arange(9)
|
|
270
|
+
popt, pcov = curve_fit(pnpc, ks, npnormed, sigma=sigma, method='lm', p0=[0.3])
|
|
271
|
+
perr = np.sqrt(np.diag(pcov))
|
|
272
|
+
nlabels_fit = pnpc(ks,popt[0])
|
|
273
|
+
n_labels_scaled = nphist.sum()*nlabels_fit
|
|
274
|
+
|
|
275
|
+
return (popt[0],n_labels_scaled,perr[0])
|
|
276
|
+
|
|
277
|
+
from PYMEcs.misc.utils import get_timestamp_from_filename
|
|
278
|
+
def plotcdf_npc3d(nlab,plot_as_points=True,timestamp=None,thresh=None,return_data=False):
|
|
279
|
+
pr = prangeNPC3D()
|
|
280
|
+
for p in pr.keys():
|
|
281
|
+
if p != 'krange':
|
|
282
|
+
plt.plot(pr['krange'],np.cumsum(pr[p]),label="p=%.1f" % p,alpha=0.5)
|
|
283
|
+
if timestamp is None:
|
|
284
|
+
labelexp = 'experiment'
|
|
285
|
+
else:
|
|
286
|
+
labelexp = 'exp %s' % timestamp
|
|
287
|
+
|
|
288
|
+
if thresh is not None:
|
|
289
|
+
labelexp = "thresh %d, %s" % (thresh,labelexp)
|
|
290
|
+
|
|
291
|
+
# make sure our bin centers are integer spaced from 0 to 16
|
|
292
|
+
histret = plt.hist(nlab,bins=np.arange(17)+0.5,density=True,
|
|
293
|
+
histtype="step", cumulative=1, label=labelexp, alpha=0.3)
|
|
294
|
+
if plot_as_points:
|
|
295
|
+
histn = histret[0]
|
|
296
|
+
histctr = 0.5*(histret[1][1:]+histret[1][0:-1])
|
|
297
|
+
plt.scatter(histctr,histn)
|
|
298
|
+
|
|
299
|
+
# ensure we only have integer major ticks in the plot
|
|
300
|
+
ax = plt.gca()
|
|
301
|
+
ax.xaxis.get_major_locator().set_params(integer=True)
|
|
302
|
+
|
|
303
|
+
popt,perr, pcbfx, pcbestfit = npclabel_fit3D(histctr,histn)
|
|
304
|
+
plt.plot(pcbfx,pcbestfit,'--')
|
|
305
|
+
plt.legend()
|
|
306
|
+
|
|
307
|
+
plt.title("NPC 3D analysis using %d NPCs, LE = %.1f %% +- %.1f %%" %
|
|
308
|
+
(nlab.size,100.0*popt,100.0*perr))
|
|
309
|
+
plt.xlabel("N labeled")
|
|
310
|
+
plt.ylabel("CDF")
|
|
311
|
+
|
|
312
|
+
if return_data:
|
|
313
|
+
return (histctr,histn)
|
|
314
|
+
|
|
315
|
+
def npclabel_fit3D(histx,histv,sigma=0.1):
|
|
316
|
+
popt, pcov = curve_fit(pnpc3dc, histx, histv, sigma=sigma, method='lm', p0=[0.4])
|
|
317
|
+
perr = np.sqrt(np.diag(pcov))
|
|
318
|
+
krange = np.arange(17,dtype='i')
|
|
319
|
+
pcumulative = pnpc3dc(krange,popt[0])
|
|
320
|
+
|
|
321
|
+
return (popt[0],perr[0], krange, pcumulative)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
#################
|
|
325
|
+
# NPC 3D Analysis
|
|
326
|
+
#################
|
|
327
|
+
|
|
328
|
+
def to3vecs(x,y,z):
|
|
329
|
+
return np.stack((x,y,z),axis=1)
|
|
330
|
+
|
|
331
|
+
def xyzfrom3vec(v):
|
|
332
|
+
return (v[:,0],v[:,1],v[:,2])
|
|
333
|
+
|
|
334
|
+
from scipy.spatial.transform import Rotation as R
|
|
335
|
+
from scipy.interpolate import RegularGridInterpolator
|
|
336
|
+
from scipy.signal import fftconvolve
|
|
337
|
+
|
|
338
|
+
def fpinterpolate(fp3d,x,y,z,method='linear', bounds_error=True, fill_value=np.nan):
|
|
339
|
+
# V[i,j,k] = 100*x[i] + 10*y[j] + z[k]
|
|
340
|
+
fpinterp = RegularGridInterpolator((x,y,z), fp3d, method=method, bounds_error=bounds_error, fill_value=fill_value)
|
|
341
|
+
return fpinterp
|
|
342
|
+
|
|
343
|
+
# variation on makeNPC function in SimuFLUX by Marin & Ries (https://github.com/ries-lab/SimuFLUX)
|
|
344
|
+
def makeNPC(center=[0,0,0], R=50, copynumber=32, dz=50, rotation=0, twistangle=np.pi/32, shiftangle=np.pi/16, dR=3, dzpair=3.2):
|
|
345
|
+
if not isinstance(center, np.ndarray):
|
|
346
|
+
center = np.array(center, dtype=float)
|
|
347
|
+
|
|
348
|
+
dphi = np.pi/4
|
|
349
|
+
maxphi = 2 * np.pi - dphi - rotation
|
|
350
|
+
nphi = int(maxphi / dphi)
|
|
351
|
+
phi = np.arange(nphi+1)*dphi
|
|
352
|
+
v0 = np.zeros_like(phi)
|
|
353
|
+
|
|
354
|
+
if copynumber == 8:
|
|
355
|
+
phiall = phi
|
|
356
|
+
zall = v0
|
|
357
|
+
Rall = v0 + R
|
|
358
|
+
elif copynumber == 16:
|
|
359
|
+
phiall = np.hstack([phi, -phi+twistangle])
|
|
360
|
+
zall = np.hstack([v0+dz/2, v0-dz/2])
|
|
361
|
+
Rall = np.hstack([v0,v0]) + R
|
|
362
|
+
elif copynumber == 32:
|
|
363
|
+
phiall = np.hstack([phi, phi+shiftangle, -phi+twistangle, -phi + twistangle - shiftangle])
|
|
364
|
+
zall = np.hstack([v0+dz/2+dzpair/2, v0+dz/2-dzpair/2, v0-dz/2-dzpair/2, v0-dz/2+dzpair/2])
|
|
365
|
+
Rall = np.hstack([v0+R+dR, v0+R, v0+R+dR, v0+R])
|
|
366
|
+
else:
|
|
367
|
+
raise ValueError("NPC copy number: must be 8, 16 or 32, is: %d" % copynumber)
|
|
368
|
+
|
|
369
|
+
posnpc = np.vstack([Rall*np.cos(phiall), Rall*np.sin(phiall), zall]).T
|
|
370
|
+
posnpc += center
|
|
371
|
+
|
|
372
|
+
return posnpc
|
|
373
|
+
|
|
374
|
+
def npctemplate_detailed(x3d,y3d,z3d,npcgeometry,sigma=5.0):
|
|
375
|
+
vol = np.zeros_like(x3d)
|
|
376
|
+
d0, h0 = npcgeometry
|
|
377
|
+
npcpts = makeNPC(R=d0/2, dz=h0)
|
|
378
|
+
x=npcpts[:,0]
|
|
379
|
+
y=npcpts[:,1]
|
|
380
|
+
z=npcpts[:,2]
|
|
381
|
+
for i in range(x.size):
|
|
382
|
+
vol += np.exp(-((x3d-x[i])**2+(y3d-y[i])**2+(z3d-z[i])**2)/(2*sigma*sigma))
|
|
383
|
+
|
|
384
|
+
return vol
|
|
385
|
+
|
|
386
|
+
# we may rewrite this for our purpose if bounds violations become a problem
|
|
387
|
+
# code from https://stackoverflow.com/questions/21670080/how-to-find-global-minimum-in-python-optimization-with-bounds
|
|
388
|
+
class RandomDisplacementBounds(object):
|
|
389
|
+
"""random displacement with bounds"""
|
|
390
|
+
def __init__(self, xmin, xmax, stepsize=0.5):
|
|
391
|
+
self.xmin = xmin
|
|
392
|
+
self.xmax = xmax
|
|
393
|
+
self.stepsize = stepsize
|
|
394
|
+
|
|
395
|
+
def __call__(self, x):
|
|
396
|
+
"""take a random step but ensure the new position is within the bounds"""
|
|
397
|
+
while True:
|
|
398
|
+
# this could be done in a much more clever way, but it will work for example purposes
|
|
399
|
+
xnew = x + np.random.uniform(-self.stepsize, self.stepsize, np.shape(x))
|
|
400
|
+
if np.all(xnew < self.xmax) and np.all(xnew > self.xmin):
|
|
401
|
+
break
|
|
402
|
+
return xnew
|
|
403
|
+
|
|
404
|
+
# # define the new step taking routine and pass it to basinhopping
|
|
405
|
+
# take_step = RandomDisplacementBounds(xmin, xmax)
|
|
406
|
+
# result = basinhopping(f, x0, niter=100, minimizer_kwargs=minimizer_kwargs,
|
|
407
|
+
# take_step=take_step)
|
|
408
|
+
|
|
409
|
+
maxshift = 50.0
|
|
410
|
+
class LLmaximizerNPC3D(object):
|
|
411
|
+
# the bgprop value needs a little more thought, it could be specific for this set of parameters
|
|
412
|
+
def __init__(self, npcgeometry, extent_nm=150.0, voxelsize_nm=2.0, sigma=5.0, bgprob=1e-9,volcallback=None):
|
|
413
|
+
self.x = np.arange(-extent_nm/2.0, extent_nm/2.0+1.0, voxelsize_nm, dtype='f')
|
|
414
|
+
self.y = self.x.copy()
|
|
415
|
+
self.z = self.x.copy()
|
|
416
|
+
self.npcgeometry = npcgeometry
|
|
417
|
+
self.sigma = sigma
|
|
418
|
+
self.bgprob = bgprob
|
|
419
|
+
self.volcallback = volcallback
|
|
420
|
+
|
|
421
|
+
x2d,y2d = np.meshgrid(self.x,self.y)
|
|
422
|
+
x3d,y3d, z3d = np.meshgrid(self.x,self.y,self.z)
|
|
423
|
+
|
|
424
|
+
if volcallback is not None:
|
|
425
|
+
if not callable(volcallback):
|
|
426
|
+
raise RuntimeError("volcallback option is not callable")
|
|
427
|
+
self.fpg3d = volcallback(x3d,y3d,z3d,npcgeometry,sigma=sigma)
|
|
428
|
+
else:
|
|
429
|
+
eps=15.0 # we have removed the eps variable as input, set it here
|
|
430
|
+
d0, h0 = npcgeometry # diameter of ring and ring spacing
|
|
431
|
+
self.circ2d = (x2d**2 + y2d**2 -0.25*d0**2 <= eps**2) & (x2d**2 + y2d**2 -0.25*d0**2 >= -eps**2)
|
|
432
|
+
self.g3d = np.exp(-(x3d**2+y3d**2+z3d**2)/2.0/(sigma**2))
|
|
433
|
+
self.fp3d = np.zeros_like(x3d)
|
|
434
|
+
idz = np.argmin(np.abs(self.z-h0/2))
|
|
435
|
+
self.fp3d[:,:,idz] = self.circ2d
|
|
436
|
+
idz = np.argmin(np.abs(self.z-(-h0/2)))
|
|
437
|
+
self.fp3d[:,:,idz] = self.circ2d
|
|
438
|
+
self.fpg3d = np.clip(fftconvolve(self.fp3d,self.g3d,mode='same'),0,None)
|
|
439
|
+
|
|
440
|
+
self.fpg3d += bgprob # add background probability
|
|
441
|
+
self.fpg3d /= self.fpg3d.sum()
|
|
442
|
+
self.nllfp = -np.log10(self.fpg3d)
|
|
443
|
+
self.nllfpi = None
|
|
444
|
+
self.interpolator()
|
|
445
|
+
|
|
446
|
+
self.points = None
|
|
447
|
+
self.pars0 = (0.,0.,0., 0., 0., 100.0, 100.0) # shift_x, shift_y, shift_z, angle_around_z, angle_around_y, scale_xy, scale_z
|
|
448
|
+
self.bounds = (
|
|
449
|
+
(-maxshift,maxshift), # p[0]
|
|
450
|
+
(-maxshift,maxshift), # p[1]
|
|
451
|
+
(-maxshift,maxshift), # p[2]
|
|
452
|
+
(-90.0,90.0), # p[3]
|
|
453
|
+
(-35.0,35.0), # p[4]
|
|
454
|
+
(80.0,120.0), # p[5] - limit to 20% variation to avoid misfits
|
|
455
|
+
(50.0,150.0) # p[6] - limit to 50% variation to avoid misfits
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
def registerPoints(self,pts): # register candidate points for fitting
|
|
459
|
+
self.points = pts
|
|
460
|
+
self.transform_coords(self.pars0)
|
|
461
|
+
|
|
462
|
+
def fit(self,method='XXX'): # run the maximumLL fit
|
|
463
|
+
pass
|
|
464
|
+
|
|
465
|
+
def fitpars(self): # get the best fit parameters
|
|
466
|
+
pass
|
|
467
|
+
|
|
468
|
+
def LLcalc(self,params=None): # return log-likelihood for given parameter set; if None use best fit params
|
|
469
|
+
pass
|
|
470
|
+
|
|
471
|
+
def interpolator(self):
|
|
472
|
+
if self.nllfpi is None:
|
|
473
|
+
self.nllfpi = fpinterpolate(self.nllfp, self.x, self.y, self.z,bounds_error=False,fill_value=15.0) # need to think about the fill_value, it could need tweaking
|
|
474
|
+
|
|
475
|
+
return self.nllfpi
|
|
476
|
+
|
|
477
|
+
def lleval(self,pars):
|
|
478
|
+
c3d = self.transform_coords(pars)
|
|
479
|
+
llvals = self.nllfpi((c3d[:,0],c3d[:,1],c3d[:,2]))
|
|
480
|
+
return llvals.sum()
|
|
481
|
+
|
|
482
|
+
def transform_coords(self,pars):
|
|
483
|
+
if self.points is None:
|
|
484
|
+
raise RuntimeError("no valid points, please register points first")
|
|
485
|
+
c3d = self.points + [pars[0],pars[1],pars[2]] # pars[0:3] should be vector offset
|
|
486
|
+
self.c3dr = R.from_euler('zy', [pars[3],pars[4]], degrees=True).apply(c3d)
|
|
487
|
+
self.c3dr[:,0:2] *= 0.01*pars[5]
|
|
488
|
+
self.c3dr[:,2] *= 0.01*pars[6]
|
|
489
|
+
self._lastpars = pars
|
|
490
|
+
return self.c3dr
|
|
491
|
+
|
|
492
|
+
def transform_coords_inv(self,pars): # this is apparently currently not used and needs to be debugged before use
|
|
493
|
+
if 'c3dr' not in dir(self) or self.c3dr is None:
|
|
494
|
+
raise RuntimeError("need transformed points to start with")
|
|
495
|
+
c3dr = self.c3dr.copy()
|
|
496
|
+
c3dr[:,0:2] /= 0.01*pars[5]
|
|
497
|
+
c3dr[:,2] /= 0.01*pars[6]
|
|
498
|
+
c3di = R.from_euler('zy', [pars[3],pars[4]], degrees=True).inv().apply(c3dr)
|
|
499
|
+
c3di -= [pars[0],pars[1],pars[2]]
|
|
500
|
+
self.c3di = c3di
|
|
501
|
+
return self.c3di
|
|
502
|
+
|
|
503
|
+
def plot_points(self,mode='transformed',external_pts=None,axes=None,p0=None): # supported modes should be 'original', 'transformed', 'both', external
|
|
504
|
+
if mode == 'transformed':
|
|
505
|
+
x,y,z = xyzfrom3vec(self.c3dr)
|
|
506
|
+
elif mode == 'original':
|
|
507
|
+
x,y,z = xyzfrom3vec(self.points)
|
|
508
|
+
elif mode == 'both':
|
|
509
|
+
if p0 is not None: # in this mode we consider a p0 as initial transform if provided
|
|
510
|
+
plast = self._lastpars # perhaps better to use opt_result.x?
|
|
511
|
+
self.transform_coords(p0)
|
|
512
|
+
x,y,z = xyzfrom3vec(self.c3dr)
|
|
513
|
+
self.transform_coords(plast) # restore transformed coords to what they were at the start
|
|
514
|
+
else:
|
|
515
|
+
x,y,z = xyzfrom3vec(self.points)
|
|
516
|
+
x1,y1,z1 = xyzfrom3vec(self.c3dr)
|
|
517
|
+
elif mode == 'external':
|
|
518
|
+
if external_pts is None:
|
|
519
|
+
raise RuntimeError("with mode='external' need external points but none supplied")
|
|
520
|
+
x,y,z = xyzfrom3vec(external_pts)
|
|
521
|
+
else:
|
|
522
|
+
raise RuntimeError("unknown mode %s" % mode)
|
|
523
|
+
|
|
524
|
+
if mode == 'both':
|
|
525
|
+
if axes is None:
|
|
526
|
+
fig, (axt,axb) = plt.subplots(2,3)
|
|
527
|
+
else:
|
|
528
|
+
(axt,axb) = axes
|
|
529
|
+
else:
|
|
530
|
+
if axes is None:
|
|
531
|
+
fig, axt = plt.subplots(1,3,figsize=(6.4,2.4))
|
|
532
|
+
else:
|
|
533
|
+
axt = axes
|
|
534
|
+
|
|
535
|
+
axt[0].cla()
|
|
536
|
+
axt[0].imshow(self.fpg3d.sum(axis=2).T,extent=[self.x.min(), self.x.max(), self.y.min(), self.y.max()])
|
|
537
|
+
axt[0].scatter(x,y,c='orange',s=10)
|
|
538
|
+
axt[0].set_aspect('equal')
|
|
539
|
+
axt[0].set_title('x-y')
|
|
540
|
+
axt[1].cla()
|
|
541
|
+
axt[1].imshow(self.fpg3d.sum(axis=1).T,extent=[self.x.min(), self.x.max(), self.z.min(), self.z.max()])
|
|
542
|
+
axt[1].scatter(x,z,c='orange',s=10)
|
|
543
|
+
axt[1].set_aspect('equal')
|
|
544
|
+
axt[1].set_title('x-z')
|
|
545
|
+
axt[2].cla()
|
|
546
|
+
axt[2].imshow(self.fpg3d.sum(axis=0).T,extent=[self.y.min(), self.y.max(), self.z.min(), self.z.max()])
|
|
547
|
+
axt[2].scatter(y,z,c='orange',s=10)
|
|
548
|
+
axt[2].set_aspect('equal')
|
|
549
|
+
axt[2].set_title('y-z')
|
|
550
|
+
|
|
551
|
+
if mode == 'both':
|
|
552
|
+
axb[0].cla()
|
|
553
|
+
axb[0].imshow(self.fpg3d.sum(axis=2).T,extent=[self.x.min(), self.x.max(), self.y.min(), self.y.max()])
|
|
554
|
+
axb[0].scatter(x1,y1,c='orange',s=10)
|
|
555
|
+
axb[0].set_aspect('equal')
|
|
556
|
+
axb[0].set_title('x-y')
|
|
557
|
+
axb[1].cla()
|
|
558
|
+
axb[1].imshow(self.fpg3d.sum(axis=1).T,extent=[self.x.min(), self.x.max(), self.z.min(), self.z.max()])
|
|
559
|
+
axb[1].scatter(x1,z1,c='orange',s=10)
|
|
560
|
+
axb[1].set_aspect('equal')
|
|
561
|
+
axb[1].set_title('x-z')
|
|
562
|
+
axb[2].cla()
|
|
563
|
+
axb[2].imshow(self.fpg3d.sum(axis=0).T,extent=[self.y.min(), self.y.max(), self.z.min(), self.z.max()])
|
|
564
|
+
axb[2].scatter(y1,z1,c='orange',s=10)
|
|
565
|
+
axb[2].set_aspect('equal')
|
|
566
|
+
axb[2].set_title('y-z')
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def function_to_minimize(self):
|
|
570
|
+
def minfunc(p):
|
|
571
|
+
return self.lleval(p)
|
|
572
|
+
|
|
573
|
+
return minfunc
|
|
574
|
+
|
|
575
|
+
# minimize the negative log likelihood
|
|
576
|
+
def nllminimize(self,p0=(0,0,0,0,0,100.0,100.0),method='L-BFGS-B'):
|
|
577
|
+
from scipy.optimize import minimize
|
|
578
|
+
self.p0 = p0
|
|
579
|
+
self.minmethod = method
|
|
580
|
+
self.opt_result = minimize(self.function_to_minimize(),p0,method=method,bounds=self.bounds)
|
|
581
|
+
|
|
582
|
+
def nll_basin_hopping(self,p0,method='L-BFGS-B',bounds=None):
|
|
583
|
+
from scipy.optimize import basinhopping
|
|
584
|
+
self.p0 = p0 # we record as p0 since pars0 is used at the time of "registerPoints"; will cause issues as nllm object is reused
|
|
585
|
+
self.minmethod = "basinhopping with %s" % method
|
|
586
|
+
if bounds is None:
|
|
587
|
+
bounds=self.bounds # default bounds
|
|
588
|
+
minimizer_kwargs = dict(method=method, bounds=bounds)
|
|
589
|
+
self.opt_result = basinhopping(self.function_to_minimize(), p0, minimizer_kwargs=minimizer_kwargs)
|
|
590
|
+
|
|
591
|
+
def pprint_lastpars(self):
|
|
592
|
+
print("Origin: %s" % self._lastpars[0:3])
|
|
593
|
+
print("Angles: %d rot-z, %d rot-y" % tuple(np.round(self._lastpars[3:5])))
|
|
594
|
+
print("Ring diam: %d, ring spacing: %d" % tuple(np.round(np.array(self.npcgeometry)*100.0/np.array(self._lastpars[5:]))))
|
|
595
|
+
|
|
596
|
+
class NPC3D(object):
|
|
597
|
+
def __init__(self, points=None, pipeline=None, objectID=None, zclip=None, offset_mode='mean'):
|
|
598
|
+
self.points = points
|
|
599
|
+
if pipeline is not None:
|
|
600
|
+
if objectID is None:
|
|
601
|
+
raise RuntimeError("need an objectID to set points from pipeline, None was given")
|
|
602
|
+
npcidx = pipeline['objectID'] == objectID
|
|
603
|
+
self.points = to3vecs(pipeline['x'][npcidx],pipeline['y'][npcidx],pipeline['z'][npcidx])
|
|
604
|
+
self.t = pipeline['t'][npcidx]
|
|
605
|
+
self.objectID = objectID
|
|
606
|
+
self.npts = None
|
|
607
|
+
if self.points is not None:
|
|
608
|
+
self.normalize_points(zclip=zclip, mode=offset_mode)
|
|
609
|
+
self.transformed_pts = None
|
|
610
|
+
self.opt_result = None
|
|
611
|
+
self.filtered_pts = None
|
|
612
|
+
self.fitted = False
|
|
613
|
+
|
|
614
|
+
def normalize_points(self,zclip=None,mode='mean'):
|
|
615
|
+
if mode == 'mean':
|
|
616
|
+
self.offset = self.points.mean(axis=0)[None,:]
|
|
617
|
+
elif mode == 'median':
|
|
618
|
+
self.offset = np.median(self.points,axis=0)[None,:]
|
|
619
|
+
else:
|
|
620
|
+
raise RuntimeError("unknown mode '%s', should be mean or median" % mode)
|
|
621
|
+
npts = self.points - self.offset
|
|
622
|
+
nt = self.t
|
|
623
|
+
if not zclip is None:
|
|
624
|
+
zgood = (npts[:,2] > -zclip)*(npts[:,2] < zclip)
|
|
625
|
+
npts = npts[zgood,:]
|
|
626
|
+
nt = nt[zgood]
|
|
627
|
+
self.npts = npts
|
|
628
|
+
self.nt = nt
|
|
629
|
+
|
|
630
|
+
def fitbymll(self,nllminimizer,plot=True,printpars=True,axes=None,preminimizer=None,axespre=None):
|
|
631
|
+
nllm = nllminimizer
|
|
632
|
+
self.nllminimizer = nllm
|
|
633
|
+
self.preminimizer = preminimizer
|
|
634
|
+
|
|
635
|
+
if preminimizer is not None:
|
|
636
|
+
preminimizer.registerPoints(self.npts)
|
|
637
|
+
preminimizer.nll_basin_hopping(p0=(0,0,0,0,0,100.0,100.0))
|
|
638
|
+
self.opt_result_pre = preminimizer.opt_result
|
|
639
|
+
self.transformed_pts_pre = preminimizer.c3dr
|
|
640
|
+
self.bounds_pre = preminimizer.bounds
|
|
641
|
+
# in the second stage llm minimizing stage start with best fit from previous fit as p0
|
|
642
|
+
# and allow mainly variation in rotation angles
|
|
643
|
+
# for other parameters only allow deviation from robust fitting in quite narrow range
|
|
644
|
+
p0 = self.opt_result_pre.x
|
|
645
|
+
dc = 3.0 # max deviation in coordinates (in nm)
|
|
646
|
+
dperc = 5.0 # max deviation in scaling percentage
|
|
647
|
+
bounds = (
|
|
648
|
+
(p0[0]-dc,p0[0]+dc), # p[0]
|
|
649
|
+
(p0[1]-dc,p0[1]+dc), # p[1]
|
|
650
|
+
(p0[2]-dc,p0[2]+dc), # p[2]
|
|
651
|
+
(-90.0,90.0), # p[3]
|
|
652
|
+
(-35.0,35.0), # p[4]
|
|
653
|
+
(p0[5]-dperc,p0[5]+dperc), # p[5] - limit to 20% variation to avoid misfits
|
|
654
|
+
(p0[6]-dperc,p0[6]+dperc) # p[6] - limit to 20% variation to avoid misfits
|
|
655
|
+
)
|
|
656
|
+
logger.info("p0 %s" % p0)
|
|
657
|
+
logger.info("bounds %s" % repr(bounds))
|
|
658
|
+
|
|
659
|
+
nllm.registerPoints(self.npts)
|
|
660
|
+
nllm.nll_basin_hopping(p0=p0,bounds=bounds)
|
|
661
|
+
self.opt_result = nllm.opt_result
|
|
662
|
+
self.transformed_pts = nllm.c3dr
|
|
663
|
+
self.bounds = bounds
|
|
664
|
+
else:
|
|
665
|
+
nllm.registerPoints(self.npts)
|
|
666
|
+
nllm.nll_basin_hopping(p0=(0,0,0,0,0,100.0,100.0)) # no bound keyword implies default bounds
|
|
667
|
+
self.opt_result = nllm.opt_result
|
|
668
|
+
self.transformed_pts = nllm.c3dr
|
|
669
|
+
self.bounds = nllm.bounds
|
|
670
|
+
self.fitted = True
|
|
671
|
+
if printpars:
|
|
672
|
+
nllm.pprint_lastpars()
|
|
673
|
+
if plot:
|
|
674
|
+
if preminimizer is not None:
|
|
675
|
+
preminimizer.plot_points(mode='both',axes=axespre)
|
|
676
|
+
p0 = nllm.p0
|
|
677
|
+
else:
|
|
678
|
+
p0 = None
|
|
679
|
+
nllm.plot_points(mode='both',axes=axes,p0=p0) # if a prefit was done we use its p0
|
|
680
|
+
|
|
681
|
+
def filter(self,axis='z',minval=0, maxval=100):
|
|
682
|
+
if axis == 'x':
|
|
683
|
+
coords = self.transformed_pts[:,0]
|
|
684
|
+
elif axis == 'y':
|
|
685
|
+
coords = self.transformed_pts[:,1]
|
|
686
|
+
elif axis == 'z':
|
|
687
|
+
coords = self.transformed_pts[:,2]
|
|
688
|
+
else:
|
|
689
|
+
raise RuntimeError("unknow axis %s requested (must be x, y or z)" % axis)
|
|
690
|
+
|
|
691
|
+
goodidx = (coords >= minval)*(coords <= maxval)
|
|
692
|
+
self.filtered_pts = self.transformed_pts[goodidx,:]
|
|
693
|
+
try:
|
|
694
|
+
self.filtered_t = self.nt[goodidx]
|
|
695
|
+
except AttributeError: # ignore if we do not have the 'nt' attribute
|
|
696
|
+
pass
|
|
697
|
+
|
|
698
|
+
def plot_points(self,mode='transformed'):
|
|
699
|
+
if mode == 'normalized':
|
|
700
|
+
pts = self.npts
|
|
701
|
+
elif mode == 'transformed':
|
|
702
|
+
pts = self.transformed_pts
|
|
703
|
+
elif mode == 'filtered':
|
|
704
|
+
pts = self.filtered_pts
|
|
705
|
+
else:
|
|
706
|
+
raise RuntimeError("unknown mode %s" % mode)
|
|
707
|
+
|
|
708
|
+
self.nllminimizer.plot_points(mode='external',external_pts=pts)
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def plot_points3D(self,mode='transformed',ax=None,with_offset=False,s=10):
|
|
712
|
+
if mode == 'normalized':
|
|
713
|
+
pts = self.npts
|
|
714
|
+
if with_offset:
|
|
715
|
+
pts = pts + self.offset
|
|
716
|
+
elif mode == 'transformed':
|
|
717
|
+
pts = self.transformed_pts
|
|
718
|
+
elif mode == 'filtered':
|
|
719
|
+
pts = self.filtered_pts
|
|
720
|
+
else:
|
|
721
|
+
raise RuntimeError("unknown mode %s" % mode)
|
|
722
|
+
|
|
723
|
+
if ax is None:
|
|
724
|
+
fig = plt.figure()
|
|
725
|
+
ax = fig.add_subplot(projection='3d')
|
|
726
|
+
ax.scatter(pts[:,0], pts[:,1], pts[:,2], 'o', s=s)
|
|
727
|
+
return ax
|
|
728
|
+
|
|
729
|
+
# the nominal glyph diam and height, set by the LLM fitter npcgeometry
|
|
730
|
+
# note access will only work after llm fit has taklen place!
|
|
731
|
+
def get_glyph_diam(self):
|
|
732
|
+
return self.nllminimizer.npcgeometry[0]
|
|
733
|
+
|
|
734
|
+
def get_glyph_height(self):
|
|
735
|
+
return self.nllminimizer.npcgeometry[1]
|
|
736
|
+
|
|
737
|
+
def get_glyph(self, with_offset=True):
|
|
738
|
+
|
|
739
|
+
def get_circ_coords(radius=50.0, npoints=25):
|
|
740
|
+
angle = np.linspace( 0 , 2 * np.pi , npoints)
|
|
741
|
+
xc = radius * np.cos( angle )
|
|
742
|
+
yc = radius * np.sin( angle )
|
|
743
|
+
return (xc,yc)
|
|
744
|
+
|
|
745
|
+
def transform_coords_invs(x,y,z,pars,offset):
|
|
746
|
+
xr = x.copy()
|
|
747
|
+
yr = y.copy()
|
|
748
|
+
zr = z.copy()
|
|
749
|
+
zr /= 0.01*pars[6]
|
|
750
|
+
xr /= 0.01*pars[5]
|
|
751
|
+
yr /= 0.01*pars[5]
|
|
752
|
+
c3d = np.stack([xr,yr,zr],axis=1)
|
|
753
|
+
c3di = R.from_euler('zy', [pars[3],pars[4]], degrees=True).inv().apply(c3d)
|
|
754
|
+
c3di -= [pars[0],pars[1],pars[2]]
|
|
755
|
+
c3di += offset
|
|
756
|
+
|
|
757
|
+
return (c3di[:,0],c3di[:,1],c3di[:,2])
|
|
758
|
+
|
|
759
|
+
xc, yc = get_circ_coords(radius=0.5*self.get_glyph_diam(), npoints=25)
|
|
760
|
+
|
|
761
|
+
if with_offset:
|
|
762
|
+
offset = self.offset
|
|
763
|
+
else:
|
|
764
|
+
offset = 0
|
|
765
|
+
pars = self.opt_result.x
|
|
766
|
+
glyph = {}
|
|
767
|
+
x1,y1,z1 = transform_coords_invs(xc,yc,np.zeros_like(xc)-0.5*self.get_glyph_height(),pars,offset)
|
|
768
|
+
glyph['circ_bot'] = to3vecs(x1,y1,z1)
|
|
769
|
+
x2,y2,z2 = transform_coords_invs(xc,yc,np.zeros_like(xc)+0.5*self.get_glyph_height(),pars,offset)
|
|
770
|
+
glyph['circ_top'] = to3vecs(x2,y2,z2)
|
|
771
|
+
xa,ya,za = transform_coords_invs([0,0],[0,0],[-75.0,+75.0],pars,offset)
|
|
772
|
+
glyph['axis'] = to3vecs(xa,ya,za)
|
|
773
|
+
|
|
774
|
+
return glyph
|
|
775
|
+
|
|
776
|
+
def plot_points3D_with_glyph(self, ax=None, with_offset=False, s=10):
|
|
777
|
+
|
|
778
|
+
if ax is None:
|
|
779
|
+
fig = plt.figure()
|
|
780
|
+
ax = fig.add_subplot(projection='3d')
|
|
781
|
+
|
|
782
|
+
self.plot_points3D(mode='normalized',ax=ax,with_offset=with_offset,s=s)
|
|
783
|
+
glyph = self.get_glyph(with_offset=with_offset)
|
|
784
|
+
x1,y1,z1 = xyzfrom3vec(glyph['circ_bot'])
|
|
785
|
+
ax.plot(x1,y1,z1,'r')
|
|
786
|
+
x2,y2,z2 = xyzfrom3vec(glyph['circ_top'])
|
|
787
|
+
ax.plot(x2,y2,z2,'r')
|
|
788
|
+
xa,ya,za = xyzfrom3vec(glyph['axis'])
|
|
789
|
+
ax.plot(xa,ya,za,'r')
|
|
790
|
+
|
|
791
|
+
return ax
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def nlabeled(self,nthresh=1,r0=50.0,dr=25.0,do_plot=False,rotlocked=True,zrange=150.0,analysis2d=False,rotation=None):
|
|
795
|
+
zrangeabs = abs(zrange)
|
|
796
|
+
if rotlocked and rotation is None:
|
|
797
|
+
self.filter('z',-zrangeabs,zrangeabs)
|
|
798
|
+
if self.filtered_pts.size > 0:
|
|
799
|
+
rotation = estimate_rotation2(self.filtered_pts[:,0],self.filtered_pts[:,1])
|
|
800
|
+
else:
|
|
801
|
+
rotation=None
|
|
802
|
+
self.rotation = rotation # remember rotation
|
|
803
|
+
|
|
804
|
+
if analysis2d:
|
|
805
|
+
self.filter('z',-zrangeabs,zrangeabs)
|
|
806
|
+
x=self.filtered_pts[:,0]
|
|
807
|
+
y=self.filtered_pts[:,1]
|
|
808
|
+
if self.filtered_pts.size > 0:
|
|
809
|
+
self.n = estimate_nlabeled(x,y,r0=r0,dr=dr,nthresh=nthresh,do_plot=do_plot,rotation=rotation)
|
|
810
|
+
else:
|
|
811
|
+
self.n = 0
|
|
812
|
+
return self.n
|
|
813
|
+
else:
|
|
814
|
+
self.filter('z',0,zrangeabs)
|
|
815
|
+
if self.filtered_pts.size > 0:
|
|
816
|
+
# self.plot_points('filtered')
|
|
817
|
+
x=self.filtered_pts[:,0]
|
|
818
|
+
y=self.filtered_pts[:,1]
|
|
819
|
+
(self.n_top,self.n_top_bysegments) = estimate_nlabeled(x,y,r0=r0,dr=dr,nthresh=nthresh,do_plot=do_plot,
|
|
820
|
+
rotation=rotation,return_bysegments=True)
|
|
821
|
+
else:
|
|
822
|
+
(self.n_top,self.n_top_bysegments) = (0,np.zeros((8)))
|
|
823
|
+
self.filter('z',-zrangeabs,0)
|
|
824
|
+
if self.filtered_pts.size > 0:
|
|
825
|
+
# self.plot_points('filtered')
|
|
826
|
+
x=self.filtered_pts[:,0]
|
|
827
|
+
y=self.filtered_pts[:,1]
|
|
828
|
+
(self.n_bot,self.n_bot_bysegments) = estimate_nlabeled(x,y,r0=r0,dr=dr,nthresh=nthresh,do_plot=do_plot,
|
|
829
|
+
rotation=rotation,return_bysegments=True)
|
|
830
|
+
else:
|
|
831
|
+
(self.n_bot,self.n_bot_bysegments) = (0,np.zeros((8)))
|
|
832
|
+
return (self.n_top,self.n_bot)
|
|
833
|
+
|
|
834
|
+
class NPC3DSet(object):
|
|
835
|
+
def __init__(self,filename=None,zclip=75.0,offset_mode='median',NPCdiam=100.0,NPCheight=70.0,
|
|
836
|
+
foreshortening=1.0,known_number=-1,templatemode='standard',sigma=7.0):
|
|
837
|
+
self.filename=filename
|
|
838
|
+
self.zclip = zclip
|
|
839
|
+
self.offset_mode = offset_mode
|
|
840
|
+
self.npcdiam = NPCdiam
|
|
841
|
+
self.npcheight = NPCheight
|
|
842
|
+
self.npcs = []
|
|
843
|
+
self.foreshortening=foreshortening # we just record this for reference
|
|
844
|
+
# TODO: expose llm parameters to this init method as needed in practice!
|
|
845
|
+
self.templatemode = templatemode
|
|
846
|
+
if templatemode == 'standard':
|
|
847
|
+
volcallback=None
|
|
848
|
+
# sigma = 7.0 # we set this via sigma keyword now
|
|
849
|
+
elif templatemode == 'detailed' or templatemode == 'twostage':
|
|
850
|
+
volcallback=npctemplate_detailed
|
|
851
|
+
# sigma = 7.0 # we set this via sigma keyword now
|
|
852
|
+
self.llm = LLmaximizerNPC3D([self.npcdiam,self.npcheight],sigma=sigma,bgprob=1e-9,extent_nm=300.0,volcallback=volcallback)
|
|
853
|
+
if templatemode == 'twostage':
|
|
854
|
+
self.llmpre = LLmaximizerNPC3D([self.npcdiam,self.npcheight],sigma=sigma,bgprob=1e-9,extent_nm=300.0,volcallback=None)
|
|
855
|
+
else:
|
|
856
|
+
self.llmpre = None
|
|
857
|
+
self.measurements = []
|
|
858
|
+
self.known_number = known_number # only considered if > 0
|
|
859
|
+
|
|
860
|
+
# v1.1
|
|
861
|
+
# - has templatemode added
|
|
862
|
+
# - has more complete recording of llm initialization parameters
|
|
863
|
+
# v1.2
|
|
864
|
+
# - twostage mode introduced with prefitting with robust template followed by detailed template
|
|
865
|
+
# v1.3
|
|
866
|
+
# - add time from original points
|
|
867
|
+
# v1.4
|
|
868
|
+
# - add bounds info to npc object when fitting
|
|
869
|
+
self._version='1.4' # REMEMBER to increment version when changing this object or the underlying npc object definitions
|
|
870
|
+
|
|
871
|
+
def registerNPC(self,npc):
|
|
872
|
+
self.npcs.append(npc)
|
|
873
|
+
|
|
874
|
+
def addNPCfromPipeline(self,pipeline,oid):
|
|
875
|
+
self.registerNPC(NPC3D(pipeline=pipeline,objectID=oid,zclip=self.zclip,offset_mode=self.offset_mode))
|
|
876
|
+
|
|
877
|
+
def measure_labeleff(self,nthresh=1,do_plot=False,printpars=False,refit=False):
|
|
878
|
+
self.measurements = []
|
|
879
|
+
if do_plot:
|
|
880
|
+
fig, axes = plt.subplots(2,3)
|
|
881
|
+
else:
|
|
882
|
+
axes = None
|
|
883
|
+
for npc in self.npcs:
|
|
884
|
+
if not npc.fitted or refit:
|
|
885
|
+
npc.fitbymll(self.llm,plot=do_plot,axes=axes,printpars=printpars)
|
|
886
|
+
nt,nb = npc.nlabeled(nthresh=nthresh,dr=20.0)
|
|
887
|
+
self.measurements.append([nt,nb])
|
|
888
|
+
|
|
889
|
+
def plot_labeleff(self,thresh=None):
|
|
890
|
+
from PYMEcs.misc.utils import get_timestamp_from_filename
|
|
891
|
+
if len(self.measurements) < 10:
|
|
892
|
+
raise RuntimeError("not enough measurements, need at least 10, got %d" %
|
|
893
|
+
len(self.measurements))
|
|
894
|
+
meas = np.array(self.measurements)
|
|
895
|
+
nlab = meas.sum(axis=1)
|
|
896
|
+
# fill with trailing zeros if we have a known number of NPCs but have fewer measurements
|
|
897
|
+
# the "missing NPCs" typically represent NPCs with no events
|
|
898
|
+
if int(self.known_number) > 0 and nlab.shape[0] < int(self.known_number):
|
|
899
|
+
nlab = np.pad(nlab,((0,int(self.known_number)-nlab.shape[0])))
|
|
900
|
+
|
|
901
|
+
plt.figure()
|
|
902
|
+
plotcdf_npc3d(nlab,timestamp=get_timestamp_from_filename(self.filename),thresh=thresh)
|
|
903
|
+
|
|
904
|
+
def diam(self):
|
|
905
|
+
diams = []
|
|
906
|
+
for npc in self.npcs:
|
|
907
|
+
diams.append(npc.get_glyph_diam()/(0.01*npc.opt_result.x[5]))
|
|
908
|
+
return diams
|
|
909
|
+
|
|
910
|
+
def height(self):
|
|
911
|
+
heights = []
|
|
912
|
+
for npc in self.npcs:
|
|
913
|
+
heights.append(npc.get_glyph_height()/(0.01*npc.opt_result.x[6]))
|
|
914
|
+
return heights
|
|
915
|
+
|
|
916
|
+
def n_bysegments(self):
|
|
917
|
+
nbs_top = []
|
|
918
|
+
nbs_bot = []
|
|
919
|
+
for npc in self.npcs:
|
|
920
|
+
if npc.fitted:
|
|
921
|
+
try:
|
|
922
|
+
nbs_top.append(npc.n_top_bysegments)
|
|
923
|
+
except AttributeError:
|
|
924
|
+
pass
|
|
925
|
+
try:
|
|
926
|
+
nbs_bot.append(npc.n_bot_bysegments)
|
|
927
|
+
except AttributeError:
|
|
928
|
+
pass
|
|
929
|
+
if len(nbs_top) == 0 and len(nbs_bot) == 0:
|
|
930
|
+
return None
|
|
931
|
+
else:
|
|
932
|
+
return dict(top=np.array(nbs_top),bottom=np.array(nbs_bot))
|
|
933
|
+
|
|
934
|
+
def version(self):
|
|
935
|
+
return self._version
|
|
936
|
+
|
|
937
|
+
class NPCSetContainer(object):
|
|
938
|
+
def __init__(self,npcs):
|
|
939
|
+
self.npcs=npcs
|
|
940
|
+
|
|
941
|
+
def get_npcset(self):
|
|
942
|
+
if '_unpickled' in dir(self):
|
|
943
|
+
warn(self._unpickled)
|
|
944
|
+
return None
|
|
945
|
+
return self.npcs
|
|
946
|
+
|
|
947
|
+
# we add custom pickling/unpickling methods so that an npcset instance in the
|
|
948
|
+
# PYME metadata won't greatly inflate the image saving footprint
|
|
949
|
+
# the pickled version of the npcset should be on disk already, no need to "properly" pickle/unpickle the NPCSetContainer
|
|
950
|
+
# which was created for this very purpose, i.e. the NPCSetContainer object needs to only persist during the lifetime of the pymevis viewer
|
|
951
|
+
def __getstate__(self):
|
|
952
|
+
warn("NPCset is being pickled - just a dummy mostly for PYME metadata - won't be usable after unpickling")
|
|
953
|
+
return 'not a valid npcset object after pickling/unpickling'
|
|
954
|
+
|
|
955
|
+
def __setstate__(self, d):
|
|
956
|
+
warn("NPCset is being unpickled - this is just a dummy unpickle, won't be usable after unpickling")
|
|
957
|
+
self._unpickled = d
|
|
958
|
+
|
|
959
|
+
def mk_NPC_gallery(npcs,mode,zclip3d,NPCRotationAngle,xoffs=0,yoffs=0,enforce_8foldsym=False):
|
|
960
|
+
x = np.empty((0))
|
|
961
|
+
y = np.empty((0))
|
|
962
|
+
z = np.empty((0))
|
|
963
|
+
t = np.empty((0),int)
|
|
964
|
+
objectID = np.empty((0),int)
|
|
965
|
+
is_top = np.empty((0),int)
|
|
966
|
+
segmentID = np.empty((0),int)
|
|
967
|
+
phi = np.empty((0))
|
|
968
|
+
|
|
969
|
+
if mode == 'TopOverBottom':
|
|
970
|
+
gspx = 180
|
|
971
|
+
gspy = 180
|
|
972
|
+
gsdx = 0
|
|
973
|
+
rowlength = 10
|
|
974
|
+
elif mode == 'TopBesideBottom':
|
|
975
|
+
gspx = 400
|
|
976
|
+
gspy = 180
|
|
977
|
+
gsdx = 180
|
|
978
|
+
rowlength = 5
|
|
979
|
+
|
|
980
|
+
elif mode == 'SingleAverageSBS':
|
|
981
|
+
gspx = 0
|
|
982
|
+
gspy = 0
|
|
983
|
+
gsdx = 180
|
|
984
|
+
rowlength = 5
|
|
985
|
+
|
|
986
|
+
else:
|
|
987
|
+
gspx = 0
|
|
988
|
+
gspy = 0
|
|
989
|
+
gsdx = 0
|
|
990
|
+
rowlength = 5
|
|
991
|
+
|
|
992
|
+
xtr = np.empty((0))
|
|
993
|
+
ytr = np.empty((0))
|
|
994
|
+
ztr = np.empty((0))
|
|
995
|
+
objectIDtr = np.empty((0),int)
|
|
996
|
+
polyidtr = np.empty((0),int)
|
|
997
|
+
|
|
998
|
+
xg = []
|
|
999
|
+
yg = []
|
|
1000
|
+
dphi = np.pi/4
|
|
1001
|
+
radius = 65.0
|
|
1002
|
+
pi_base = []
|
|
1003
|
+
|
|
1004
|
+
for i in range(8):
|
|
1005
|
+
xg.extend([0,radius*np.sin(i*dphi)])
|
|
1006
|
+
yg.extend([0,radius*np.cos(i*dphi)])
|
|
1007
|
+
pi_base.extend([i+1,i+1])
|
|
1008
|
+
|
|
1009
|
+
zt = np.full((16),25.0)
|
|
1010
|
+
zb = -np.full((16),25.0)
|
|
1011
|
+
|
|
1012
|
+
polyidx = np.array(pi_base,dtype='i')
|
|
1013
|
+
xga = np.array(xg)
|
|
1014
|
+
yga = np.array(yg)
|
|
1015
|
+
|
|
1016
|
+
def filtered_t(npc):
|
|
1017
|
+
if 'filtered_t' in dir(npc):
|
|
1018
|
+
return npc.filtered_t
|
|
1019
|
+
else:
|
|
1020
|
+
return range(npc.filtered_pts.shape[0])
|
|
1021
|
+
|
|
1022
|
+
if enforce_8foldsym:
|
|
1023
|
+
angled_repeats = 8
|
|
1024
|
+
else:
|
|
1025
|
+
angled_repeats = 1
|
|
1026
|
+
|
|
1027
|
+
for i,npc in enumerate(npcs.npcs):
|
|
1028
|
+
if not npc.fitted:
|
|
1029
|
+
warn("NPC not yet fitted, please call only after fitting")
|
|
1030
|
+
return
|
|
1031
|
+
|
|
1032
|
+
gxt = (i % rowlength) * gspx
|
|
1033
|
+
gxb = gxt + gsdx
|
|
1034
|
+
gy = (i // rowlength) * gspy
|
|
1035
|
+
|
|
1036
|
+
npc.filter('z',0,zclip3d)
|
|
1037
|
+
ptst = npc.filtered_pts
|
|
1038
|
+
tt = filtered_t(npc)
|
|
1039
|
+
|
|
1040
|
+
npc.filter('z',-zclip3d,0)
|
|
1041
|
+
ptsb = npc.filtered_pts
|
|
1042
|
+
tb = filtered_t(npc)
|
|
1043
|
+
|
|
1044
|
+
from scipy.spatial.transform import Rotation as R
|
|
1045
|
+
for i in range(angled_repeats):
|
|
1046
|
+
if npc.rotation is not None:
|
|
1047
|
+
if NPCRotationAngle == 'negative':
|
|
1048
|
+
factor = -1.0
|
|
1049
|
+
elif NPCRotationAngle == 'positive':
|
|
1050
|
+
factor = 1.0
|
|
1051
|
+
else:
|
|
1052
|
+
factor = 0.0
|
|
1053
|
+
ptst_t = R.from_euler('z', factor*npc.rotation + i*piover4, degrees=False).apply(ptst)
|
|
1054
|
+
ptsb_t = R.from_euler('z', factor*npc.rotation + i*piover4, degrees=False).apply(ptsb)
|
|
1055
|
+
|
|
1056
|
+
phit = phi_from_coords(ptst_t[:,0],ptst_t[:,1])
|
|
1057
|
+
phib = phi_from_coords(ptsb_t[:,0],ptsb_t[:,1])
|
|
1058
|
+
|
|
1059
|
+
x = np.append(x,ptst_t[:,0] + gxt)
|
|
1060
|
+
y = np.append(y,ptst_t[:,1] + gy)
|
|
1061
|
+
z = np.append(z,ptst_t[:,2])
|
|
1062
|
+
phi = np.append(phi, phit)
|
|
1063
|
+
segmentID = np.append(segmentID, ((phit+np.pi)/piover4).astype(int))
|
|
1064
|
+
t = np.append(t,tt)
|
|
1065
|
+
|
|
1066
|
+
x = np.append(x,ptsb_t[:,0] + gxb)
|
|
1067
|
+
y = np.append(y,ptsb_t[:,1] + gy)
|
|
1068
|
+
z = np.append(z,ptsb_t[:,2])
|
|
1069
|
+
phi = np.append(phi, phib)
|
|
1070
|
+
segmentID = np.append(segmentID, ((phib+np.pi)/piover4).astype(int))
|
|
1071
|
+
t = np.append(t,tb)
|
|
1072
|
+
|
|
1073
|
+
objectID = np.append(objectID,np.full_like(ptst[:,0],npc.objectID,dtype=int))
|
|
1074
|
+
objectID = np.append(objectID,np.full_like(ptsb[:,0],npc.objectID,dtype=int))
|
|
1075
|
+
|
|
1076
|
+
is_top = np.append(is_top,np.ones_like(phit,dtype=int))
|
|
1077
|
+
is_top = np.append(is_top,np.zeros_like(phib,dtype=int))
|
|
1078
|
+
|
|
1079
|
+
# remaining stuff for trace dict which shows segment boundaries
|
|
1080
|
+
xtr = np.append(xtr,xga + gxt)
|
|
1081
|
+
ytr = np.append(ytr,yga + gy)
|
|
1082
|
+
ztr = np.append(ztr,zt)
|
|
1083
|
+
|
|
1084
|
+
objectIDtr = np.append(objectIDtr, np.full_like(xga,npc.objectID,dtype=int))
|
|
1085
|
+
polyidtr = np.append(polyidtr, polyidx)
|
|
1086
|
+
polyidx += 8
|
|
1087
|
+
|
|
1088
|
+
xtr = np.append(xtr,xga + gxb)
|
|
1089
|
+
ytr = np.append(ytr,yga + gy)
|
|
1090
|
+
ztr = np.append(ztr,zb)
|
|
1091
|
+
|
|
1092
|
+
objectIDtr = np.append(objectIDtr, np.full_like(xga,npc.objectID,dtype=int))
|
|
1093
|
+
polyidtr = np.append(polyidtr, polyidx)
|
|
1094
|
+
polyidx += 8
|
|
1095
|
+
|
|
1096
|
+
# t = np.arange(x.size)
|
|
1097
|
+
A = np.full_like(x,10.0,dtype='f')
|
|
1098
|
+
error_x = np.full_like(x,1.0,dtype='f')
|
|
1099
|
+
error_y = np.full_like(x,1.0,dtype='f')
|
|
1100
|
+
error_z = np.full_like(x,1.0,dtype='f')
|
|
1101
|
+
|
|
1102
|
+
dsdict = dict(x=x+xoffs,y=y+yoffs,z=z,
|
|
1103
|
+
objectID=objectID,t=t,A=A,
|
|
1104
|
+
error_x=error_x,error_y=error_y,error_z=error_z,
|
|
1105
|
+
is_top=is_top,segmentID=segmentID,phi=phi)
|
|
1106
|
+
|
|
1107
|
+
trdict = dict(x=xtr+xoffs,y=ytr+yoffs,z=ztr,
|
|
1108
|
+
objectID=objectIDtr,polyIndex=polyidtr)
|
|
1109
|
+
|
|
1110
|
+
from PYME.IO.tabular import DictSource
|
|
1111
|
+
gallery = DictSource(dsdict)
|
|
1112
|
+
segments = DictSource(trdict)
|
|
1113
|
+
|
|
1114
|
+
return gallery,segments
|
|
1115
|
+
|
|
1116
|
+
def mk_npctemplates(npcs):
|
|
1117
|
+
x = np.empty((0))
|
|
1118
|
+
y = np.empty((0))
|
|
1119
|
+
z = np.empty((0))
|
|
1120
|
+
polyIndex = np.empty((0),int)
|
|
1121
|
+
polySize = np.empty((0),int)
|
|
1122
|
+
objectID = np.empty((0),int)
|
|
1123
|
+
NtopLabelled = np.empty((0),int)
|
|
1124
|
+
NbotLabelled = np.empty((0),int)
|
|
1125
|
+
NLabelled = np.empty((0),int)
|
|
1126
|
+
diams = np.empty((0),float)
|
|
1127
|
+
heights = np.empty((0),float)
|
|
1128
|
+
fitquals = np.empty((0),float)
|
|
1129
|
+
cx = np.empty((0),float)
|
|
1130
|
+
cy = np.empty((0),float)
|
|
1131
|
+
cz = np.empty((0),float)
|
|
1132
|
+
ci = 1
|
|
1133
|
+
for npc in npcs.npcs:
|
|
1134
|
+
nt, nb = (npc.n_top,npc.n_bot)
|
|
1135
|
+
glyph = npc.get_glyph()
|
|
1136
|
+
pars = npc.opt_result.x
|
|
1137
|
+
diam = npc.get_glyph_diam() / (0.01*pars[5])
|
|
1138
|
+
height = npc.get_glyph_height() / (0.01*pars[6])
|
|
1139
|
+
fitqual = npc.opt_result.fun/npc.npts.shape[0]
|
|
1140
|
+
offset = npc.offset[0]
|
|
1141
|
+
for poly in ['circ_bot','circ_top','axis']:
|
|
1142
|
+
c3 = glyph[poly]
|
|
1143
|
+
xg = c3[:,0]
|
|
1144
|
+
yg = c3[:,1]
|
|
1145
|
+
zg = c3[:,2]
|
|
1146
|
+
x = np.append(x,xg)
|
|
1147
|
+
y = np.append(y,yg)
|
|
1148
|
+
z = np.append(z,zg)
|
|
1149
|
+
polyIndex = np.append(polyIndex,np.full_like(xg,ci,dtype=int))
|
|
1150
|
+
polySize = np.append(polySize,np.full_like(xg,xg.size,dtype=int))
|
|
1151
|
+
ci += 1
|
|
1152
|
+
objectID = np.append(objectID,np.full_like(xg,npc.objectID,dtype=int))
|
|
1153
|
+
NtopLabelled = np.append(NtopLabelled,np.full_like(xg,nt,dtype=int))
|
|
1154
|
+
NbotLabelled = np.append(NbotLabelled,np.full_like(xg,nb,dtype=int))
|
|
1155
|
+
NLabelled = np.append(NLabelled,np.full_like(xg,nt+nb,dtype=int))
|
|
1156
|
+
diams = np.append(diams,np.full_like(xg,diam,dtype=float))
|
|
1157
|
+
heights = np.append(heights,np.full_like(xg,height,dtype=float))
|
|
1158
|
+
fitquals = np.append(fitquals,np.full_like(xg,fitqual,dtype=float))
|
|
1159
|
+
cx = np.append(cx,np.full_like(xg,offset[0]-pars[0],dtype=float))
|
|
1160
|
+
cy = np.append(cy,np.full_like(xg,offset[1]-pars[1],dtype=float))
|
|
1161
|
+
cz = np.append(cz,np.full_like(xg,offset[2]-pars[2],dtype=float))
|
|
1162
|
+
t = np.arange(x.size)
|
|
1163
|
+
A = np.full_like(x,10.0,dtype='f')
|
|
1164
|
+
error_x = np.full_like(x,1.0,dtype='f')
|
|
1165
|
+
error_y = np.full_like(x,1.0,dtype='f')
|
|
1166
|
+
error_z = np.full_like(x,1.0,dtype='f')
|
|
1167
|
+
|
|
1168
|
+
dsdict = dict(x=x,y=y,z=z,polyIndex=polyIndex,polySize=polySize,
|
|
1169
|
+
NtopLabelled=NtopLabelled,NbotLabelled=NbotLabelled,NLabelled=NLabelled,
|
|
1170
|
+
objectID=objectID,t=t,A=A,
|
|
1171
|
+
error_x=error_x,error_y=error_y,error_z=error_z,
|
|
1172
|
+
npc_height=heights,npc_diam=diams,npc_fitqual=fitquals,
|
|
1173
|
+
npc_ctrx=cx,npc_ctry=cy,npc_ctrz=cz)
|
|
1174
|
+
|
|
1175
|
+
from PYME.IO.tabular import DictSource
|
|
1176
|
+
return DictSource(dsdict)
|