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
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Fri Mar 28 15:02:50 2014
|
|
4
|
+
|
|
5
|
+
@author: David Baddeley
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from numpy.fft import fftn, ifftn, fftshift, ifftshift
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
from scipy import ndimage
|
|
13
|
+
from PYME.Acquire import eventLog
|
|
14
|
+
|
|
15
|
+
import threading
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
from PYME.contrib import dispatch
|
|
21
|
+
class StandardFrameSource(object):
|
|
22
|
+
'''This is a simple source which emits frames once per polling interval of the frameWrangler
|
|
23
|
+
(i.e. corresponding to the onFrameGroup signal of the frameWrangler).
|
|
24
|
+
|
|
25
|
+
The intention is to reproduce the historical behaviour of the drift tracking code, whilst
|
|
26
|
+
abstracting some of the detailed knowledge of frame handling out of the actual tracking code.
|
|
27
|
+
|
|
28
|
+
'''
|
|
29
|
+
def __init__(self, frameWrangler):
|
|
30
|
+
self._fw = frameWrangler
|
|
31
|
+
self._on_frame = dispatch.Signal(['frameData'])
|
|
32
|
+
self._fw.onFrameGroup.connect(self.tick)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def tick(self, *args, **kwargs):
|
|
36
|
+
self._on_frame.send(sender=self, frameData=self._fw.currentFrame)
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def shape(self):
|
|
40
|
+
return self._fw.currentFrame.shape
|
|
41
|
+
|
|
42
|
+
def connect(self, callback):
|
|
43
|
+
self._on_frame.connect(callback)
|
|
44
|
+
|
|
45
|
+
def disconnect(self, callback):
|
|
46
|
+
self._on_frame.disconnect(callback)
|
|
47
|
+
|
|
48
|
+
class OIDICFrameSource(StandardFrameSource):
|
|
49
|
+
""" Emit frames from the camera to the tracking code only for a single OIDIC orientation.
|
|
50
|
+
|
|
51
|
+
Currently a straw man / skeleton pending details of OIDIC code.
|
|
52
|
+
|
|
53
|
+
TODO - should this reside here, or with the other OIDIC code (which I believe to be in a separate repo)?
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, frameWrangler, oidic_controller, oidic_orientation=0):
|
|
58
|
+
super().__init__(frameWrangler)
|
|
59
|
+
|
|
60
|
+
self._oidic = oidic_controller
|
|
61
|
+
self._target_orientation = oidic_orientation
|
|
62
|
+
|
|
63
|
+
def tick(self, *args, **kwargs):
|
|
64
|
+
# FIXME - change to match actual naming etc ... in OIDIC code.
|
|
65
|
+
# FIXME - check when onFrameGroup is emitted relative to when the OIDIC orientation is set.
|
|
66
|
+
# Is this predictable, or does it depend on the order in which OIDIC and drift tracking are
|
|
67
|
+
# registered with the frameWrangler?
|
|
68
|
+
if self._oidic.orientation == self._target_orientation:
|
|
69
|
+
super().tick(*args, **kwargs)
|
|
70
|
+
else:
|
|
71
|
+
# clobber all frames coming from camera when not in the correct DIC orientation
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
from enum import Enum
|
|
75
|
+
State = Enum('State', ['UNCALIBRATED', 'CALIBRATING', 'FINISHING_CALIBRATION', 'CALIBRATED'])
|
|
76
|
+
|
|
77
|
+
class Correlator(object):
|
|
78
|
+
def __init__(self, scope, piezo=None, frame_source=None, sub_roi=None, focusTolerance=.05, deltaZ=0.2, stackHalfSize=35):
|
|
79
|
+
self.piezo = piezo
|
|
80
|
+
|
|
81
|
+
if frame_source is None:
|
|
82
|
+
self.frame_source = StandardFrameSource(scope.frameWrangler)
|
|
83
|
+
|
|
84
|
+
# configuration parameters, accessible via keyword args
|
|
85
|
+
self.focusTolerance = focusTolerance # (in um) how far focus can drift before we correct
|
|
86
|
+
self.deltaZ = deltaZ #z increment (in um) used for calibration
|
|
87
|
+
self.stackHalfSize = stackHalfSize
|
|
88
|
+
# other configuration parameters - not currently accessible via keyword args
|
|
89
|
+
self.skipframes = 1 # number of frames to skip after changing position to let piezo settle
|
|
90
|
+
self.minDelay = 10
|
|
91
|
+
# NOTE: maxTotalCorrection parameter below - we may want to have a reset offset method
|
|
92
|
+
# to allow resetting the offset during the day as it tends to accumulate;
|
|
93
|
+
# this has already led to the lock giving up on occasion
|
|
94
|
+
self._maxTotalCorrection = 20.0 # maximum total correction in um
|
|
95
|
+
self.Zfactor = 1.0
|
|
96
|
+
self.logShifts = True
|
|
97
|
+
|
|
98
|
+
# we report our tracking info in nm by default
|
|
99
|
+
pixelsize_um = scope.GetPixelSize()
|
|
100
|
+
self.conversion = {'x': 1e3*pixelsize_um[0], 'y':1e3*pixelsize_um[1], 'z':1e3}
|
|
101
|
+
# we may or may not want to use the one below
|
|
102
|
+
# self.trackunits = {'x':'nm', 'y':'nm', 'z':'nm'}
|
|
103
|
+
|
|
104
|
+
# 'state-tracking' variables
|
|
105
|
+
self.NCalibFrames = 2*self.stackHalfSize + 1 # this gets recalculated in _prepare_calibration anyway
|
|
106
|
+
self.calibCurFrame = 0
|
|
107
|
+
self.state = State.UNCALIBRATED
|
|
108
|
+
self.tracking = False
|
|
109
|
+
self.lockActive = False
|
|
110
|
+
self.lockFocus = False
|
|
111
|
+
self._last_target_z = -1
|
|
112
|
+
|
|
113
|
+
def set_subroi(self, bounds):
|
|
114
|
+
""" Set the position of the roi to crop
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
|
|
119
|
+
position : tuple
|
|
120
|
+
The pixel position (x0, x1, y0, y1) in int
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
self.sub_roi = bounds
|
|
124
|
+
self.reCalibrate()
|
|
125
|
+
|
|
126
|
+
def _crop_frame(self, frame_data):
|
|
127
|
+
if self.sub_roi is None:
|
|
128
|
+
return frame_data.squeeze() # we may as well do the squeeze here to avoid lots of squeezes elsewhere
|
|
129
|
+
else:
|
|
130
|
+
x0, x1, y0, y1 = self.sub_roi
|
|
131
|
+
return frame_data.squeeze()[x0:x1, y0:y1]
|
|
132
|
+
|
|
133
|
+
def set_focus_tolerance(self, tolerance):
|
|
134
|
+
""" Set the tolerance for locking position
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
|
|
139
|
+
tolerance : float
|
|
140
|
+
The tolerance in um
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
self.focusTolerance = tolerance
|
|
144
|
+
|
|
145
|
+
def get_focus_tolerance(self):
|
|
146
|
+
return self.focusTolerance
|
|
147
|
+
|
|
148
|
+
def set_delta_Z(self, delta):
|
|
149
|
+
""" Set the Z increment for calibration stack
|
|
150
|
+
|
|
151
|
+
Parameters
|
|
152
|
+
----------
|
|
153
|
+
|
|
154
|
+
delta : float
|
|
155
|
+
The delta in um. This should be the distance over which changes in PSF intensity with depth
|
|
156
|
+
can be approximated as being linear, with an upper bound of the Nyquist sampling in Z.
|
|
157
|
+
At Nyquist sampling, the linearity assumption is already getting a bit tenuous. Default = 0.2 um,
|
|
158
|
+
which is approximately Nyquist sampled at 1.4NA.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
self.deltaZ = delta
|
|
162
|
+
|
|
163
|
+
def get_delta_Z(self):
|
|
164
|
+
return self.deltaZ
|
|
165
|
+
|
|
166
|
+
def set_stack_halfsize(self, halfsize):
|
|
167
|
+
""" Set the calibration stack half size
|
|
168
|
+
|
|
169
|
+
This dictates the maximum size of z-stack you can record whilst retaining focus lock. The resulting
|
|
170
|
+
calibration range can be calculated as deltaZ*(2*halfsize), and should extend about 1 micron above
|
|
171
|
+
and below the size of the the largest z-stack to ensure that lock can be maintained at the edges of
|
|
172
|
+
the stack. The default of 35 gives about 12 um of axial range.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
|
|
177
|
+
halfsize : int
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
self.stackHalfSize = halfsize
|
|
181
|
+
|
|
182
|
+
def get_stack_halfsize(self):
|
|
183
|
+
return self.stackHalfSize
|
|
184
|
+
|
|
185
|
+
def set_focus_lock(self, lock=True):
|
|
186
|
+
""" Set locking on or off
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
|
|
191
|
+
lock : bool
|
|
192
|
+
whether the lock should be on
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
self.lockFocus = lock
|
|
196
|
+
|
|
197
|
+
def get_focus_lock(self):
|
|
198
|
+
return self.lockFocus
|
|
199
|
+
|
|
200
|
+
def get_history(self, length=1000):
|
|
201
|
+
try:
|
|
202
|
+
return self.history[-length:]
|
|
203
|
+
except AttributeError:
|
|
204
|
+
return []
|
|
205
|
+
|
|
206
|
+
def get_calibration_progress(self):
|
|
207
|
+
""" Returns the current calibration progress:
|
|
208
|
+
calibration is complete when state == State.CALIBRATED
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
if self.state == State.UNCALIBRATED:
|
|
212
|
+
curFrame = 0
|
|
213
|
+
elif self.state in [State.CALIBRATING,State.FINISHING_CALIBRATION]:
|
|
214
|
+
curFrame = self.calibCurFrame
|
|
215
|
+
else:
|
|
216
|
+
curFrame = self.NCalibFrames
|
|
217
|
+
return (self.state, self.NCalibFrames, curFrame)
|
|
218
|
+
|
|
219
|
+
def is_tracking(self):
|
|
220
|
+
return self.tracking
|
|
221
|
+
|
|
222
|
+
def _setRefN(self, frame_data, N):
|
|
223
|
+
d = 1.0*frame_data.squeeze()
|
|
224
|
+
ref = d/d.mean() - 1
|
|
225
|
+
self.refImages[:,:,N] = ref
|
|
226
|
+
self.calFTs[:,:,N] = ifftn(ref)
|
|
227
|
+
self.calImages[:,:,N] = ref*self.mask
|
|
228
|
+
|
|
229
|
+
def _prepare_calibration(self, frame_data):
|
|
230
|
+
# this should not be necessary as we SHOULD only get called if we are uncalibrated
|
|
231
|
+
# self.state = State.UNCALIBRATED #completely uncalibrated
|
|
232
|
+
if self.state != State.UNCALIBRATED:
|
|
233
|
+
raise RuntimeError("this method should only be called when uncalibrated; actual state is %s" % self.state)
|
|
234
|
+
|
|
235
|
+
d = 1.0*frame_data.squeeze()
|
|
236
|
+
|
|
237
|
+
self.X, self.Y = np.mgrid[0.0:d.shape[0], 0.0:d.shape[1]]
|
|
238
|
+
self.X -= np.ceil(d.shape[0]*0.5)
|
|
239
|
+
self.Y -= np.ceil(d.shape[1]*0.5)
|
|
240
|
+
|
|
241
|
+
#we want to discard edges after accounting for x-y drift
|
|
242
|
+
self.mask = np.ones_like(d)
|
|
243
|
+
self.mask[:10, :] = 0
|
|
244
|
+
self.mask[-10:, :] = 0
|
|
245
|
+
self.mask[:, :10] = 0
|
|
246
|
+
self.mask[:,-10:] = 0
|
|
247
|
+
|
|
248
|
+
self.corrRef = 0
|
|
249
|
+
|
|
250
|
+
self.calPositions = self.homePos + self.deltaZ*np.arange(-float(self.stackHalfSize), float(self.stackHalfSize + 1))
|
|
251
|
+
self.NCalibFrames = len(self.calPositions)
|
|
252
|
+
|
|
253
|
+
self.refImages = np.zeros(self.mask.shape[:2] + (self.NCalibFrames,))
|
|
254
|
+
self.calImages = np.zeros(self.mask.shape[:2] + (self.NCalibFrames,))
|
|
255
|
+
self.calFTs = np.zeros(self.mask.shape[:2] + (self.NCalibFrames,), dtype='complex64')
|
|
256
|
+
|
|
257
|
+
self.lockFocus = False
|
|
258
|
+
self.lockActive = False
|
|
259
|
+
self.logShifts = True
|
|
260
|
+
self.lastAdjustment = 5
|
|
261
|
+
|
|
262
|
+
def _finish_calibration(self):
|
|
263
|
+
if self.state != State.FINISHING_CALIBRATION:
|
|
264
|
+
raise RuntimeError("this method should only be called when finishing the calibration; actual state is %s" % self.state)
|
|
265
|
+
|
|
266
|
+
# calculate the gradient info (needed in compare calls) from a valid calImages stack
|
|
267
|
+
self.dz = np.gradient(self.calImages)[2].reshape(-1, self.NCalibFrames)
|
|
268
|
+
self.dzn = np.hstack([1./np.dot(self.dz[:,i], self.dz[:,i]) for i in range(self.NCalibFrames)])
|
|
269
|
+
|
|
270
|
+
def compare(self, frame_data):
|
|
271
|
+
d = 1.0*frame_data.squeeze()
|
|
272
|
+
dm = d/d.mean() - 1
|
|
273
|
+
|
|
274
|
+
#where is the piezo suppposed to be
|
|
275
|
+
#nomPos = self.piezo.GetPos(0)
|
|
276
|
+
nomPos = self.piezo.GetTargetPos(0)
|
|
277
|
+
|
|
278
|
+
#find closest calibration position
|
|
279
|
+
posInd = np.argmin(np.abs(nomPos - self.calPositions))
|
|
280
|
+
|
|
281
|
+
#retrieve calibration information at this location
|
|
282
|
+
calPos = self.calPositions[posInd]
|
|
283
|
+
FA = self.calFTs[:,:,posInd]
|
|
284
|
+
refA = self.calImages[:,:,posInd]
|
|
285
|
+
|
|
286
|
+
ddz = self.dz[:,posInd]
|
|
287
|
+
dzn = self.dzn[posInd]
|
|
288
|
+
|
|
289
|
+
#what is the offset between our target position and the calibration position
|
|
290
|
+
posDelta = nomPos - calPos
|
|
291
|
+
|
|
292
|
+
#print('%s' % [nomPos, posInd, calPos, posDelta])
|
|
293
|
+
|
|
294
|
+
#find x-y drift
|
|
295
|
+
C = ifftshift(np.abs(ifftn(fftn(dm)*FA)))
|
|
296
|
+
|
|
297
|
+
Cm = C.max()
|
|
298
|
+
|
|
299
|
+
Cp = np.maximum(C - 0.5*Cm, 0)
|
|
300
|
+
Cpsum = Cp.sum()
|
|
301
|
+
|
|
302
|
+
dx = (self.X*Cp).sum()/Cpsum
|
|
303
|
+
dy = (self.Y*Cp).sum()/Cpsum
|
|
304
|
+
|
|
305
|
+
ds = ndimage.shift(dm, [-dx, -dy])*self.mask
|
|
306
|
+
|
|
307
|
+
#print A.shape, As.shape
|
|
308
|
+
|
|
309
|
+
self.ds_A = (ds - refA)
|
|
310
|
+
|
|
311
|
+
#calculate z offset between actual position and calibration position
|
|
312
|
+
dz = self.Zfactor*self.deltaZ*np.dot(self.ds_A.ravel(), ddz)*dzn
|
|
313
|
+
|
|
314
|
+
#posInd += np.round(dz / self.deltaZ)
|
|
315
|
+
#posInd = int(np.clip(posInd, 0, self.NCalibStates))
|
|
316
|
+
|
|
317
|
+
#add the offset back to determine how far we are from the target position
|
|
318
|
+
dz = dz - posDelta
|
|
319
|
+
|
|
320
|
+
return dx, dy, dz, Cm, nomPos, posInd, calPos, posDelta
|
|
321
|
+
|
|
322
|
+
def compare_log_and_correct(self,frameData):
|
|
323
|
+
# compare returns pixel coordinates for dx, dy, um for dz
|
|
324
|
+
dx, dy, dz, cCoeff, nomPos, posInd, calPos, posDelta = self.compare(frameData)
|
|
325
|
+
self.corrRef = max(self.corrRef, cCoeff) # keep track of historically maximal correlation amplitude
|
|
326
|
+
|
|
327
|
+
#print dx, dy, dz
|
|
328
|
+
dx_nm, dy_nm, dz_nm = (self.conversion['x']*dx, self.conversion['y']*dy, self.conversion['z']*dz)
|
|
329
|
+
|
|
330
|
+
offset = self.piezo.GetOffset()
|
|
331
|
+
offset_nm = 1e3*offset
|
|
332
|
+
|
|
333
|
+
pos_um = self.piezo.GetPos(0)
|
|
334
|
+
|
|
335
|
+
#FIXME: logging shouldn't call piezo.GetOffset() etc ... for performance reasons
|
|
336
|
+
# (is this still true, we keep the values cached in memory??)
|
|
337
|
+
# this is the local logging, not to the actual localisation data acquiring instance of PYMEAcquire
|
|
338
|
+
self.history.append((time.time(), dx_nm, dy_nm, dz_nm, cCoeff, self.corrRef, offset_nm, pos_um))
|
|
339
|
+
eventLog.logEvent('PYME2ShiftMeasure', '%3.1f, %3.1f, %3.1f' % (dx_nm, dy_nm, dz_nm))
|
|
340
|
+
|
|
341
|
+
self.lockActive = self.lockFocus and (cCoeff > .5*self.corrRef) # we release the lock when the correlation becomes too weak
|
|
342
|
+
if self.lockActive:
|
|
343
|
+
if abs(offset) > self._maxTotalCorrection:
|
|
344
|
+
self.lockFocus = False
|
|
345
|
+
logger.info("focus lock released, maximal Offset value exceeded (%.1f um)" % self._maxTotalCorrection)
|
|
346
|
+
if abs(dz) > self.focusTolerance and self.lastAdjustment >= self.minDelay:
|
|
347
|
+
# this sets the correction on the connected piezo
|
|
348
|
+
self.piezo.SetOffset(offset - dz)
|
|
349
|
+
self.lastAdjustment = 0
|
|
350
|
+
else:
|
|
351
|
+
self.lastAdjustment += 1
|
|
352
|
+
|
|
353
|
+
if self.logShifts:
|
|
354
|
+
# this logs to the connected copy of PYMEAcquire via the RESTServer
|
|
355
|
+
if hasattr(self.piezo, 'LogShiftsCorrelAmp'):
|
|
356
|
+
self.piezo.LogShiftsCorrelAmp(dx_nm, dy_nm, dz_nm, self.lockActive, coramp=cCoeff/self.corrRef)
|
|
357
|
+
else:
|
|
358
|
+
self.piezo.LogShifts(dx_nm, dy_nm, dz_nm, self.lockActive)
|
|
359
|
+
|
|
360
|
+
def tick(self, frameData = None, **kwargs):
|
|
361
|
+
if frameData is None:
|
|
362
|
+
raise ValueError('frameData must be specified')
|
|
363
|
+
else:
|
|
364
|
+
frameData = self._crop_frame(frameData)
|
|
365
|
+
|
|
366
|
+
targetZ = self.piezo.GetTargetPos(0)
|
|
367
|
+
|
|
368
|
+
if not 'mask' in dir(self) or not self.frame_source.shape[:2] == self.mask.shape[:2]:
|
|
369
|
+
# this just sets the UNCALIBRATED state and leaves the rest to the _prepare_calibration call
|
|
370
|
+
self.state = State.UNCALIBRATED
|
|
371
|
+
|
|
372
|
+
#called on a new frame becoming available
|
|
373
|
+
if self.state == State.UNCALIBRATED:
|
|
374
|
+
#print "cal init"
|
|
375
|
+
|
|
376
|
+
#redefine our positions for the calibration
|
|
377
|
+
self.homePos = self.piezo.GetPos(0)
|
|
378
|
+
self._prepare_calibration(frameData)
|
|
379
|
+
self.calibCurFrame = 0
|
|
380
|
+
self.skipcounter = self.skipframes
|
|
381
|
+
# move to our first position in the calib stack
|
|
382
|
+
self.piezo.MoveTo(0, self.calPositions[0])
|
|
383
|
+
|
|
384
|
+
# preps done, now switch state to calibrating
|
|
385
|
+
self.state = State.CALIBRATING
|
|
386
|
+
elif self.state == State.CALIBRATING:
|
|
387
|
+
# print "cal proceed"
|
|
388
|
+
if self.skipcounter >= 1:
|
|
389
|
+
self.skipcounter -= 1 # we let the last move settle...
|
|
390
|
+
else:
|
|
391
|
+
# piezo step completed - record current image and move on to next position
|
|
392
|
+
self._setRefN(frameData, int(self.calibCurFrame))
|
|
393
|
+
if self.calibCurFrame == self.NCalibFrames-1: # we are mostly done, this was our last plane
|
|
394
|
+
self.state = State.FINISHING_CALIBRATION
|
|
395
|
+
else: # not done yet
|
|
396
|
+
self.skipcounter = self.skipframes # reset skip counter
|
|
397
|
+
self.calibCurFrame += 1 # and go to next frame position
|
|
398
|
+
self.piezo.MoveTo(0, self.calPositions[int(self.calibCurFrame)])
|
|
399
|
+
|
|
400
|
+
elif self.state == State.FINISHING_CALIBRATION:
|
|
401
|
+
# print "cal finishing"
|
|
402
|
+
self._finish_calibration()
|
|
403
|
+
self.piezo.MoveTo(0, self.homePos) # move back to where we started
|
|
404
|
+
|
|
405
|
+
#reset our history log
|
|
406
|
+
self.history = []
|
|
407
|
+
self.historyColNames = ['time','dx_nm','dy_nm','dz_nm','corrAmplitude','corrAmpMax','piezoOffset_nm','piezoPos_um']
|
|
408
|
+
self.historyStartTime = time.time()
|
|
409
|
+
|
|
410
|
+
self.state = State.CALIBRATED # now we are fully calibrated
|
|
411
|
+
|
|
412
|
+
elif self.state == State.CALIBRATED:
|
|
413
|
+
# print "fully calibrated"
|
|
414
|
+
if np.allclose(self._last_target_z, targetZ): # check we are on target in z
|
|
415
|
+
self.compare_log_and_correct(frameData)
|
|
416
|
+
|
|
417
|
+
else:
|
|
418
|
+
raise RuntimeError("unknown calibration state %s, giving up" % self.state)
|
|
419
|
+
|
|
420
|
+
self._last_target_z = targetZ
|
|
421
|
+
|
|
422
|
+
def reCalibrate(self):
|
|
423
|
+
self.state = State.UNCALIBRATED
|
|
424
|
+
self.corrRef = 0
|
|
425
|
+
self.lockActive = False
|
|
426
|
+
|
|
427
|
+
def register(self):
|
|
428
|
+
self.frame_source.connect(self.tick)
|
|
429
|
+
self.tracking = True
|
|
430
|
+
|
|
431
|
+
def deregister(self):
|
|
432
|
+
self.frame_source.disconnect(self.tick)
|
|
433
|
+
self.tracking = False
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from PYME.Acquire.Hardware.Simulator.fakeCam import FakeCamera
|
|
2
|
+
from PYME.IO import MetaDataHandler
|
|
3
|
+
|
|
4
|
+
# the only difference of this class is to supply extra metadata to allow testing the
|
|
5
|
+
# camera map processing code
|
|
6
|
+
class FakeCameraX(FakeCamera):
|
|
7
|
+
|
|
8
|
+
def GenStartMetadata(self, mdh):
|
|
9
|
+
super().GenStartMetadata(mdh)
|
|
10
|
+
# these are useful when we want to test camera map making code
|
|
11
|
+
mdh.setEntry('Camera.SerialNumber', 'FAKE-007')
|
|
12
|
+
mdh.setEntry('Camera.SensorWidth',self.GetCCDWidth())
|
|
13
|
+
mdh.setEntry('Camera.SensorHeight',self.GetCCDHeight())
|
|
14
|
+
mdh.setEntry('Camera.Model', 'FakeCameraX')
|
|
15
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from PYME.Acquire.Hardware.Piezos import offsetPiezoREST
|
|
2
|
+
|
|
3
|
+
from PYME.Acquire import eventLog
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from PYME.util import webframework
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
# we modify the OffsetPiezo server and client to do additional logging by preserving the correlation amplitude information
|
|
12
|
+
# this is simply achieved by inheriting from the classes in offsetPiezoREST and adding a couple new methods
|
|
13
|
+
# the final bit is a change in our version of driftTracking.py that checks if the LogShiftsCorrelAmp method is available
|
|
14
|
+
# in the client and uses it in that case for the additional event logging
|
|
15
|
+
|
|
16
|
+
class OffsetPiezoCorrelLog(offsetPiezoREST.OffsetPiezo):
|
|
17
|
+
|
|
18
|
+
@webframework.register_endpoint('/LogShiftsCA', output_is_json=False)
|
|
19
|
+
def LogShiftsCA(self, dx, dy, dz, active=True, coramp=-1.0):
|
|
20
|
+
import wx
|
|
21
|
+
#eventLog.logEvent('ShiftMeasure', '%3.4f, %3.4f, %3.4f' % (dx, dy, dz))
|
|
22
|
+
wx.CallAfter(eventLog.logEvent, 'ShiftMeasure', '%3.4f, %3.4f, %3.4f' % (float(dx), float(dy), float(dz)), time.time())
|
|
23
|
+
wx.CallAfter(eventLog.logEvent, 'PiezoOffset', '%3.4f, %d' % (self.GetOffset(), int(active)), time.time())
|
|
24
|
+
wx.CallAfter(eventLog.logEvent, 'CorrelationAmplitude', '%3.4f' % (float(coramp)), time.time())
|
|
25
|
+
|
|
26
|
+
class OffsetPiezoCorrelLogClient(offsetPiezoREST.OffsetPiezoClient):
|
|
27
|
+
|
|
28
|
+
def LogShiftsCorrelAmp(self, dx, dy, dz, active=True, coramp=-1.0):
|
|
29
|
+
res = self._session.get(self.urlbase +
|
|
30
|
+
'/LogShiftsCA?dx=%3.3f&dy=%3.3f&dz=%3.3f&active=%d&coramp=%3.3f' % (dx, dy, dz, active,coramp))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def getClient():
|
|
34
|
+
#TODO - move away from hard-coded ports!!!
|
|
35
|
+
return OffsetPiezoCorrelLogClient()
|
|
36
|
+
|
|
37
|
+
def getServer():
|
|
38
|
+
return offsetPiezoREST.server_class(offset_piezo_base_class=OffsetPiezoCorrelLog)
|
|
File without changes
|