microeye 2.3.2__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.
- microEye/__init__.py +47 -0
- microEye/_version.py +2 -0
- microEye/analysis/__init__.py +1 -0
- microEye/analysis/checklist_dialog.py +143 -0
- microEye/analysis/cmosMaps.py +228 -0
- microEye/analysis/filters/__init__.py +9 -0
- microEye/analysis/filters/base.py +21 -0
- microEye/analysis/filters/spatial.py +338 -0
- microEye/analysis/filters/temporal.py +76 -0
- microEye/analysis/fitting/__init__.py +0 -0
- microEye/analysis/fitting/fit.py +680 -0
- microEye/analysis/fitting/nena.py +375 -0
- microEye/analysis/fitting/phasor_fit.py +90 -0
- microEye/analysis/fitting/processing.py +317 -0
- microEye/analysis/fitting/psf/__init__.py +6 -0
- microEye/analysis/fitting/psf/extract.py +1129 -0
- microEye/analysis/fitting/psf/rubost_mean.py +150 -0
- microEye/analysis/fitting/psf/spline.py +167 -0
- microEye/analysis/fitting/psf/stats/__init__.py +12 -0
- microEye/analysis/fitting/psf/stats/core.py +295 -0
- microEye/analysis/fitting/psf/stats/curve_fit.py +708 -0
- microEye/analysis/fitting/psf/stats/io.py +104 -0
- microEye/analysis/fitting/psf/stats/slope_fit.py +171 -0
- microEye/analysis/fitting/psf/temp.py +147 -0
- microEye/analysis/fitting/psf/test.py +47 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUfunctions.py +657 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUmleFit_LM.py +1336 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUsplineLib.py +270 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/__init__.py +1 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUfunctions.py +609 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_EMCCD.py +1396 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_sCMOS.py +1426 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUsplineLib.py +231 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/__init__.py +2 -0
- microEye/analysis/fitting/pyfit3Dcspline/__init__.py +10 -0
- microEye/analysis/fitting/pyfit3Dcspline/constants.py +27 -0
- microEye/analysis/fitting/pyfit3Dcspline/mainfunctions.py +903 -0
- microEye/analysis/fitting/results.py +917 -0
- microEye/analysis/fitting/results_stats.py +251 -0
- microEye/analysis/fitting/tardis.py +209 -0
- microEye/analysis/multi_viewer.py +568 -0
- microEye/analysis/processing/__init__.py +1 -0
- microEye/analysis/processing/frc.py +120 -0
- microEye/analysis/rendering/__init__.py +3 -0
- microEye/analysis/rendering/base.py +452 -0
- microEye/analysis/rendering/cloud.py +215 -0
- microEye/analysis/rendering/core.py +47 -0
- microEye/analysis/rendering/volumetric.py +198 -0
- microEye/analysis/tools/__init__.py +0 -0
- microEye/analysis/tools/kymograms.py +1079 -0
- microEye/analysis/tools/roi_selectors.py +556 -0
- microEye/analysis/utils/__init__.py +3 -0
- microEye/analysis/utils/coordinates.py +18 -0
- microEye/analysis/utils/images.py +68 -0
- microEye/analysis/utils/windows.py +19 -0
- microEye/analysis/viewer/__init__.py +3 -0
- microEye/analysis/viewer/image_options_widget.py +615 -0
- microEye/analysis/viewer/images.py +1225 -0
- microEye/analysis/viewer/layers_widget.py +415 -0
- microEye/analysis/viewer/localizations.py +1304 -0
- microEye/analysis/viewer/psf.py +1003 -0
- microEye/analysis/viewer/volume.py +456 -0
- microEye/hardware/__init__.py +3 -0
- microEye/hardware/cams/__init__.py +29 -0
- microEye/hardware/cams/camera_calibration.py +99 -0
- microEye/hardware/cams/camera_list.py +528 -0
- microEye/hardware/cams/camera_options.py +694 -0
- microEye/hardware/cams/camera_panel.py +941 -0
- microEye/hardware/cams/dummy/__init__.py +1 -0
- microEye/hardware/cams/dummy/dummy_panel.py +759 -0
- microEye/hardware/cams/jobs.py +497 -0
- microEye/hardware/cams/line_profiler.py +99 -0
- microEye/hardware/cams/linescan/IR_Cam.py +474 -0
- microEye/hardware/cams/linescan/__init__.py +1 -0
- microEye/hardware/cams/micam.py +524 -0
- microEye/hardware/cams/pco/__init__.py +58 -0
- microEye/hardware/cams/pco/enums.py +382 -0
- microEye/hardware/cams/pco/pco_cam.py +761 -0
- microEye/hardware/cams/pco/pco_panel.py +477 -0
- microEye/hardware/cams/shortcuts.py +312 -0
- microEye/hardware/cams/thorlabs/__init__.py +1 -0
- microEye/hardware/cams/thorlabs/thorlabs.py +1508 -0
- microEye/hardware/cams/thorlabs/thorlabs_panel.py +850 -0
- microEye/hardware/cams/ueye/__init__.py +1 -0
- microEye/hardware/cams/ueye/ueye_camera.py +1023 -0
- microEye/hardware/cams/ueye/ueye_panel.py +861 -0
- microEye/hardware/cams/vimba/__init__.py +1 -0
- microEye/hardware/cams/vimba/vimba_cam.py +1000 -0
- microEye/hardware/cams/vimba/vimba_panel.py +813 -0
- microEye/hardware/device.py +60 -0
- microEye/hardware/lasers/__init__.py +13 -0
- microEye/hardware/lasers/io_matchbox.py +791 -0
- microEye/hardware/lasers/io_params.py +85 -0
- microEye/hardware/lasers/io_single_laser.py +742 -0
- microEye/hardware/lasers/laser_relay.py +594 -0
- microEye/hardware/mieye/__init__.py +1 -0
- microEye/hardware/mieye/acquisition_manager.py +467 -0
- microEye/hardware/mieye/devices_manager.py +533 -0
- microEye/hardware/mieye/miEye.py +659 -0
- microEye/hardware/misc/__init__.py +0 -0
- microEye/hardware/misc/acquisition_view.py +71 -0
- microEye/hardware/misc/reglo.py +761 -0
- microEye/hardware/misc/temp.py +188 -0
- microEye/hardware/port_config.py +59 -0
- microEye/hardware/protocols/__init__.py +2 -0
- microEye/hardware/protocols/actions.py +402 -0
- microEye/hardware/protocols/actions_items.py +703 -0
- microEye/hardware/protocols/designer.py +244 -0
- microEye/hardware/protocols/scene_manager.py +191 -0
- microEye/hardware/protocols/serialization.py +97 -0
- microEye/hardware/pycromanager/__init__.py +16 -0
- microEye/hardware/pycromanager/core.py +1433 -0
- microEye/hardware/pycromanager/devices.py +461 -0
- microEye/hardware/pycromanager/enums.py +107 -0
- microEye/hardware/pycromanager/headless.py +153 -0
- microEye/hardware/pycromanager/utils.py +34 -0
- microEye/hardware/pycromanager/widgets/__init__.py +5 -0
- microEye/hardware/pycromanager/widgets/bridges.py +407 -0
- microEye/hardware/pycromanager/widgets/headless_manager.py +258 -0
- microEye/hardware/pycromanager/widgets/headless_options.py +224 -0
- microEye/hardware/pycromanager/widgets/pycro_panel.py +455 -0
- microEye/hardware/stages/__init__.py +18 -0
- microEye/hardware/stages/elliptec/__init__.py +5 -0
- microEye/hardware/stages/elliptec/baseDevice.py +314 -0
- microEye/hardware/stages/elliptec/device.py +384 -0
- microEye/hardware/stages/elliptec/deviceID.py +212 -0
- microEye/hardware/stages/elliptec/devicePort.py +379 -0
- microEye/hardware/stages/elliptec/deviceStatus.py +65 -0
- microEye/hardware/stages/elliptec/devicesView.py +706 -0
- microEye/hardware/stages/elliptec/ellDevices.py +134 -0
- microEye/hardware/stages/elliptec/messageUpdater.py +34 -0
- microEye/hardware/stages/elliptec/motorInfo.py +153 -0
- microEye/hardware/stages/elliptec/stage.py +62 -0
- microEye/hardware/stages/elliptec/test.py +139 -0
- microEye/hardware/stages/kinesis/__init__.py +1 -0
- microEye/hardware/stages/kinesis/kdc101/__init__.py +1 -0
- microEye/hardware/stages/kinesis/kdc101/enums.py +1002 -0
- microEye/hardware/stages/kinesis/kdc101/factory.py +171 -0
- microEye/hardware/stages/kinesis/kdc101/kdc101.py +718 -0
- microEye/hardware/stages/kinesis/kinesis.py +776 -0
- microEye/hardware/stages/piezo_concept.py +607 -0
- microEye/hardware/stages/stabilizer.py +785 -0
- microEye/hardware/stages/stage.py +89 -0
- microEye/hardware/widgets/__init__.py +10 -0
- microEye/hardware/widgets/controller.py +246 -0
- microEye/hardware/widgets/devices.py +133 -0
- microEye/hardware/widgets/focusWidget.py +264 -0
- microEye/hardware/widgets/qlist_slider.py +113 -0
- microEye/hardware/widgets/scan_acquisition.py +424 -0
- microEye/icons/1024.png +0 -0
- microEye/icons/128.png +0 -0
- microEye/icons/16.png +0 -0
- microEye/icons/24.png +0 -0
- microEye/icons/256.png +0 -0
- microEye/icons/32.png +0 -0
- microEye/icons/48.png +0 -0
- microEye/icons/512.png +0 -0
- microEye/icons/64.png +0 -0
- microEye/icons/__init__.py +0 -0
- microEye/icons/close.svg +88 -0
- microEye/icons/mieye.png +0 -0
- microEye/icons/min.svg +83 -0
- microEye/icons/viewer.png +0 -0
- microEye/launcher.py +42 -0
- microEye/qt.py +181 -0
- microEye/utils/__init__.py +2 -0
- microEye/utils/enum_encoder.py +10 -0
- microEye/utils/expandable_groupbox.py +93 -0
- microEye/utils/gui_helper.py +457 -0
- microEye/utils/hid/__init__.py +8 -0
- microEye/utils/hid/controller.py +153 -0
- microEye/utils/hid/device.py +63 -0
- microEye/utils/hid/enums.py +140 -0
- microEye/utils/hid/utils.py +58 -0
- microEye/utils/labelled_slider.py +134 -0
- microEye/utils/metadata.py +599 -0
- microEye/utils/metadata_tree.py +718 -0
- microEye/utils/micro_launcher.py +245 -0
- microEye/utils/parameter_tree.py +325 -0
- microEye/utils/pyscripting.py +444 -0
- microEye/utils/retry_exec.py +35 -0
- microEye/utils/start_gui.py +112 -0
- microEye/utils/thread_worker.py +115 -0
- microEye/utils/uImage.py +1512 -0
- microeye-2.3.2.dist-info/METADATA +401 -0
- microeye-2.3.2.dist-info/RECORD +190 -0
- microeye-2.3.2.dist-info/WHEEL +5 -0
- microeye-2.3.2.dist-info/entry_points.txt +2 -0
- microeye-2.3.2.dist-info/licenses/LICENSE +674 -0
- microeye-2.3.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import traceback
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from queue import LifoQueue
|
|
5
|
+
from threading import Event
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from pyqtgraph.parametertree import Parameter
|
|
10
|
+
from scipy.optimize import curve_fit
|
|
11
|
+
from scipy.signal import find_peaks
|
|
12
|
+
|
|
13
|
+
from microEye.analysis.tools.kymograms import get_kymogram_row
|
|
14
|
+
from microEye.qt import (
|
|
15
|
+
QApplication,
|
|
16
|
+
QDateTime,
|
|
17
|
+
QtCore,
|
|
18
|
+
QtWidgets,
|
|
19
|
+
Signal,
|
|
20
|
+
getSaveFileName,
|
|
21
|
+
)
|
|
22
|
+
from microEye.utils import Tree
|
|
23
|
+
from microEye.utils.gui_helper import GaussianOffSet
|
|
24
|
+
from microEye.utils.thread_worker import QThreadWorker
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FocusStabilizerParams(Enum):
|
|
28
|
+
'''
|
|
29
|
+
Enum class defining FocusStabilizer parameters.
|
|
30
|
+
'''
|
|
31
|
+
|
|
32
|
+
ROI = 'Line ROI'
|
|
33
|
+
X1 = 'X1'
|
|
34
|
+
Y1 = 'Y1'
|
|
35
|
+
X2 = 'X2'
|
|
36
|
+
Y2 = 'Y2'
|
|
37
|
+
LINE_WIDTH = 'Line Width'
|
|
38
|
+
LENGTH = 'Length'
|
|
39
|
+
ANGLE = 'Angle [deg]'
|
|
40
|
+
SET_ROI = 'Set ROI'
|
|
41
|
+
SAVE = 'Save'
|
|
42
|
+
LOAD = 'Load'
|
|
43
|
+
|
|
44
|
+
FOCUS_TRACKING = 'Focus Stabilization'
|
|
45
|
+
FOCUS_PEAK = 'Focus Peak [pixel]'
|
|
46
|
+
PIXEL_CAL = 'Calibration [pixel/nm]'
|
|
47
|
+
USE_CAL = 'Use Calibration'
|
|
48
|
+
INVERTED = 'Inverted'
|
|
49
|
+
TRACKING_ENABLED = 'Stabilization Enabled'
|
|
50
|
+
FT_P = 'P'
|
|
51
|
+
FT_I = 'I'
|
|
52
|
+
FT_D = 'D'
|
|
53
|
+
FT_TAU = 'Tau'
|
|
54
|
+
FT_ERROR_TH = 'Error Threshold'
|
|
55
|
+
PEAK_ACQUIRE = 'Start Peak Acquisition'
|
|
56
|
+
PEAK_STOP = 'Stop Peak Acquisition'
|
|
57
|
+
|
|
58
|
+
PREVIEW_IMAGE = 'Preview Image'
|
|
59
|
+
|
|
60
|
+
def __str__(self):
|
|
61
|
+
'''
|
|
62
|
+
Return the last part of the enum value (Param name).
|
|
63
|
+
'''
|
|
64
|
+
return self.value.split('.')[-1]
|
|
65
|
+
|
|
66
|
+
def get_path(self):
|
|
67
|
+
'''
|
|
68
|
+
Return the full parameter path.
|
|
69
|
+
'''
|
|
70
|
+
return self.value.split('.')
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class FocusStabilizer(QtCore.QObject):
|
|
74
|
+
# Class attribute to hold the single instance
|
|
75
|
+
_instance = None
|
|
76
|
+
|
|
77
|
+
TIME_POINTS = 500
|
|
78
|
+
|
|
79
|
+
updateViewBox = Signal(object)
|
|
80
|
+
updatePlots = Signal(object)
|
|
81
|
+
moveStage = Signal(bool, int)
|
|
82
|
+
peakPositionChanged = Signal(float)
|
|
83
|
+
pixelCalChanged = Signal(float)
|
|
84
|
+
|
|
85
|
+
def __new__(cls, *args, **kwargs):
|
|
86
|
+
# If the single instance doesn't exist, create a new one
|
|
87
|
+
if not cls._instance:
|
|
88
|
+
cls._instance = super().__new__(cls, *args, **kwargs)
|
|
89
|
+
# Return the single instance
|
|
90
|
+
return cls._instance
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def instance(cls):
|
|
94
|
+
if cls._instance is None:
|
|
95
|
+
return FocusStabilizer()
|
|
96
|
+
|
|
97
|
+
return cls._instance
|
|
98
|
+
|
|
99
|
+
def __init__(self):
|
|
100
|
+
super().__init__()
|
|
101
|
+
|
|
102
|
+
self.__buffer = LifoQueue()
|
|
103
|
+
self.peak_time = np.array(list(range(self.TIME_POINTS)))
|
|
104
|
+
self.peak_positions = np.ones((self.TIME_POINTS,))
|
|
105
|
+
self.error_buffer = np.zeros((20,))
|
|
106
|
+
self.error_integral = 0
|
|
107
|
+
self.fit_params = None
|
|
108
|
+
|
|
109
|
+
self.X = np.array([25.5, 25.5])
|
|
110
|
+
self.Y = np.array([25.5, 281.5])
|
|
111
|
+
self.line_width = 1
|
|
112
|
+
|
|
113
|
+
self.__pixel_cal_coeff = 0.01
|
|
114
|
+
self.__use_cal = False
|
|
115
|
+
self.__inverted = False
|
|
116
|
+
self.__peak_position = 64
|
|
117
|
+
self.__stabilization = False
|
|
118
|
+
self.__P = 6.45
|
|
119
|
+
self.__I = 0.0
|
|
120
|
+
self.__D = 0.0
|
|
121
|
+
self.__tau = 0.0
|
|
122
|
+
self.__err_th = 0.001
|
|
123
|
+
|
|
124
|
+
self.__preview = True
|
|
125
|
+
|
|
126
|
+
self._exec_time = 0
|
|
127
|
+
|
|
128
|
+
self.file = None
|
|
129
|
+
self.num_frames_saved = 0
|
|
130
|
+
|
|
131
|
+
self.exit_event = Event()
|
|
132
|
+
|
|
133
|
+
# Stop worker when app is closing
|
|
134
|
+
app = QApplication.instance()
|
|
135
|
+
app.aboutToQuit.connect(FocusStabilizer.instance().exit_event.set)
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def buffer(self):
|
|
139
|
+
return self.__buffer
|
|
140
|
+
|
|
141
|
+
def pixelCalCoeff(self):
|
|
142
|
+
'''
|
|
143
|
+
Gets the current pixel calibration coefficient.
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
float
|
|
148
|
+
The current pixel calibration coefficient.
|
|
149
|
+
'''
|
|
150
|
+
return self.__pixel_cal_coeff
|
|
151
|
+
|
|
152
|
+
def setPixelCalCoeff(self, value: float):
|
|
153
|
+
'''
|
|
154
|
+
Sets the pixel calibration coefficient.
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
value : float
|
|
159
|
+
The new pixel calibration coefficient.
|
|
160
|
+
'''
|
|
161
|
+
self.__pixel_cal_coeff = value
|
|
162
|
+
self.pixelCalChanged.emit(value)
|
|
163
|
+
|
|
164
|
+
def preview(self):
|
|
165
|
+
'''
|
|
166
|
+
Check if preview image is enabled.
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
bool
|
|
171
|
+
True if preview image is enabled, False otherwise.
|
|
172
|
+
'''
|
|
173
|
+
return self.__preview
|
|
174
|
+
|
|
175
|
+
def setPreview(self, value: bool):
|
|
176
|
+
'''
|
|
177
|
+
Set the flag indicating whether to preview image.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
value : bool
|
|
182
|
+
The flag value.
|
|
183
|
+
'''
|
|
184
|
+
self.__preview = value
|
|
185
|
+
|
|
186
|
+
def useCal(self):
|
|
187
|
+
'''
|
|
188
|
+
Check if pixel calibration coefficient is used.
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
bool
|
|
193
|
+
True if pixel calibration coefficient is used, False otherwise.
|
|
194
|
+
'''
|
|
195
|
+
return self.__use_cal
|
|
196
|
+
|
|
197
|
+
def setUseCal(self, use_cal: bool):
|
|
198
|
+
'''
|
|
199
|
+
Set the flag indicating whether to use pixel calibration coefficient.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
use_cal : bool
|
|
204
|
+
The flag value.
|
|
205
|
+
'''
|
|
206
|
+
self.__use_cal = use_cal
|
|
207
|
+
|
|
208
|
+
def isInverted(self):
|
|
209
|
+
'''
|
|
210
|
+
Check if the direction is inverted.
|
|
211
|
+
|
|
212
|
+
Returns
|
|
213
|
+
-------
|
|
214
|
+
bool
|
|
215
|
+
True if the direction is inverted, False otherwise.
|
|
216
|
+
'''
|
|
217
|
+
return self.__inverted
|
|
218
|
+
|
|
219
|
+
def setInverted(self, inverted: bool):
|
|
220
|
+
'''
|
|
221
|
+
Set the flag indicating whether the direction is inverted.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
inverted : bool
|
|
226
|
+
The flag value.
|
|
227
|
+
'''
|
|
228
|
+
self.__inverted = inverted
|
|
229
|
+
|
|
230
|
+
def __len__(self):
|
|
231
|
+
return len(self.__buffer.queue)
|
|
232
|
+
|
|
233
|
+
def put(self, value: np.ndarray):
|
|
234
|
+
self.__buffer.put(value)
|
|
235
|
+
|
|
236
|
+
def bufferSize(self):
|
|
237
|
+
return len(self.__buffer.queue)
|
|
238
|
+
|
|
239
|
+
def isEmpty(self):
|
|
240
|
+
return self.__buffer.empty()
|
|
241
|
+
|
|
242
|
+
def getImage(self) -> np.ndarray:
|
|
243
|
+
'''Gets image from frames Queue in LIFO manner.
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
np.ndarray
|
|
248
|
+
The image last in buffer.
|
|
249
|
+
'''
|
|
250
|
+
res = None
|
|
251
|
+
if not self.__buffer.empty():
|
|
252
|
+
res = self.__buffer.get()
|
|
253
|
+
self.__buffer.queue.clear()
|
|
254
|
+
return res
|
|
255
|
+
|
|
256
|
+
def isImage(self, data: np.ndarray):
|
|
257
|
+
if data.ndim > 2:
|
|
258
|
+
raise ValueError('Data should be one or two dimensional array.')
|
|
259
|
+
|
|
260
|
+
if data.ndim == 1:
|
|
261
|
+
return False
|
|
262
|
+
elif data.ndim == 2:
|
|
263
|
+
return True
|
|
264
|
+
|
|
265
|
+
def setP(self, P: float):
|
|
266
|
+
'''
|
|
267
|
+
Set the P value.
|
|
268
|
+
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
P : float
|
|
272
|
+
The P value.
|
|
273
|
+
'''
|
|
274
|
+
self.__P = P
|
|
275
|
+
|
|
276
|
+
def setI(self, _I: float):
|
|
277
|
+
'''
|
|
278
|
+
Set the I value.
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
_I : float
|
|
283
|
+
The I value.
|
|
284
|
+
'''
|
|
285
|
+
self.__I = _I
|
|
286
|
+
|
|
287
|
+
def setD(self, D: float):
|
|
288
|
+
'''
|
|
289
|
+
Set the D value.
|
|
290
|
+
|
|
291
|
+
Parameters
|
|
292
|
+
----------
|
|
293
|
+
D : float
|
|
294
|
+
The D value.
|
|
295
|
+
'''
|
|
296
|
+
self.__D = D
|
|
297
|
+
|
|
298
|
+
def setTau(self, tau: float):
|
|
299
|
+
'''
|
|
300
|
+
Set the tau value.
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
tau : float
|
|
305
|
+
The tau value.
|
|
306
|
+
'''
|
|
307
|
+
self.__tau = tau
|
|
308
|
+
|
|
309
|
+
def setErrorTh(self, error_th: float):
|
|
310
|
+
'''
|
|
311
|
+
Set the error threshold value.
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
error_th : float
|
|
316
|
+
The error threshold value.
|
|
317
|
+
'''
|
|
318
|
+
self.__err_th = error_th
|
|
319
|
+
|
|
320
|
+
def getPID(self):
|
|
321
|
+
'''
|
|
322
|
+
Get the current PID values.
|
|
323
|
+
|
|
324
|
+
Returns
|
|
325
|
+
-------
|
|
326
|
+
tuple of float
|
|
327
|
+
The current PID values as a tuple (P, I, D, tau, error_th).
|
|
328
|
+
'''
|
|
329
|
+
return (self.__P, self.__I, self.__D, self.__tau, self.__err_th)
|
|
330
|
+
|
|
331
|
+
def getPeakPosition(self):
|
|
332
|
+
'''
|
|
333
|
+
Get the current focus peak position.
|
|
334
|
+
|
|
335
|
+
Returns
|
|
336
|
+
-------
|
|
337
|
+
float
|
|
338
|
+
The current focus peak.
|
|
339
|
+
'''
|
|
340
|
+
return self.__peak_position
|
|
341
|
+
|
|
342
|
+
def setPeakPosition(self, value: float, incerement: bool = False):
|
|
343
|
+
'''
|
|
344
|
+
Set the focus peak position.
|
|
345
|
+
|
|
346
|
+
Parameters
|
|
347
|
+
----------
|
|
348
|
+
value : float
|
|
349
|
+
The new focus peak value.
|
|
350
|
+
incerement : bool, optional
|
|
351
|
+
If True, increment the current focus peak value by the given value,
|
|
352
|
+
otherwise set the focus peak to the given value.
|
|
353
|
+
'''
|
|
354
|
+
if incerement:
|
|
355
|
+
self.__peak_position += value
|
|
356
|
+
else:
|
|
357
|
+
self.__peak_position = value
|
|
358
|
+
self.peakPositionChanged.emit(self.__peak_position)
|
|
359
|
+
|
|
360
|
+
def isFocusStabilized(self):
|
|
361
|
+
'''
|
|
362
|
+
Check if automatic Focus Stabilization is enabled.
|
|
363
|
+
|
|
364
|
+
Returns
|
|
365
|
+
-------
|
|
366
|
+
bool
|
|
367
|
+
True if automatic Focus Stabilization is enabled, False otherwise.
|
|
368
|
+
'''
|
|
369
|
+
return self.__stabilization
|
|
370
|
+
|
|
371
|
+
def toggleFocusStabilization(self, value: bool = None):
|
|
372
|
+
'''
|
|
373
|
+
Toggles automatic Focus Stabilization.
|
|
374
|
+
|
|
375
|
+
If `value` is not provided or set to `None`, the method toggles the
|
|
376
|
+
autofocus tracking option based on its current state. If `value` is a
|
|
377
|
+
Boolean value, the method sets the option to that state.
|
|
378
|
+
|
|
379
|
+
Parameters
|
|
380
|
+
----------
|
|
381
|
+
value : bool, optional
|
|
382
|
+
The new state of the automatic Focus Stabilization. Default is `None`.
|
|
383
|
+
|
|
384
|
+
Returns
|
|
385
|
+
-------
|
|
386
|
+
bool
|
|
387
|
+
The current state of the automatic Focus Stabilization.
|
|
388
|
+
'''
|
|
389
|
+
if value is None:
|
|
390
|
+
self.__stabilization = not self.__stabilization
|
|
391
|
+
else:
|
|
392
|
+
self.__stabilization = value
|
|
393
|
+
|
|
394
|
+
return self.__stabilization
|
|
395
|
+
|
|
396
|
+
def startWorker(self):
|
|
397
|
+
self.worker = QThreadWorker(FocusStabilizer.instance().worker_function)
|
|
398
|
+
# Execute
|
|
399
|
+
QtCore.QThreadPool.globalInstance().start(self.worker)
|
|
400
|
+
|
|
401
|
+
def worker_function(self, **kwargs):
|
|
402
|
+
'''A worker function running in the threadpool.
|
|
403
|
+
|
|
404
|
+
Handles the IR peak fitting and piezo autofocus tracking.
|
|
405
|
+
'''
|
|
406
|
+
counter = 0
|
|
407
|
+
self._exec_time = 0
|
|
408
|
+
now = QDateTime.currentDateTime()
|
|
409
|
+
QtCore.QThread.msleep(100)
|
|
410
|
+
while not self.exit_event.isSet():
|
|
411
|
+
try:
|
|
412
|
+
# proceed only if the buffer is not empty
|
|
413
|
+
if not self.isEmpty():
|
|
414
|
+
self._exec_time = now.msecsTo(QDateTime.currentDateTime())
|
|
415
|
+
now = QDateTime.currentDateTime()
|
|
416
|
+
|
|
417
|
+
image = self.getImage()
|
|
418
|
+
|
|
419
|
+
if self.isImage(image):
|
|
420
|
+
if self.preview():
|
|
421
|
+
self.updateViewBox.emit(image.copy())
|
|
422
|
+
image = get_kymogram_row(image, self.X, self.Y, self.line_width)
|
|
423
|
+
self.peak_fit(image.copy())
|
|
424
|
+
if self.file is not None:
|
|
425
|
+
with open(self.file, 'ab') as file:
|
|
426
|
+
np.savetxt(
|
|
427
|
+
file,
|
|
428
|
+
np.array((self._exec_time, self.fit_params[1])).reshape(
|
|
429
|
+
(1, 2)
|
|
430
|
+
),
|
|
431
|
+
delimiter=';',
|
|
432
|
+
)
|
|
433
|
+
self.num_frames_saved = 1 + self.num_frames_saved
|
|
434
|
+
counter = counter + 1
|
|
435
|
+
self.updatePlots.emit(image.copy())
|
|
436
|
+
QtCore.QThread.usleep(100) # sleep for 100us
|
|
437
|
+
except Exception as e:
|
|
438
|
+
traceback.print_exc()
|
|
439
|
+
|
|
440
|
+
print(f'{FocusStabilizer.__name__} worker is terminating.')
|
|
441
|
+
|
|
442
|
+
def peak_fit(self, data: np.ndarray):
|
|
443
|
+
'''
|
|
444
|
+
Finds the peak position through fitting and adjusts the piezostage accordingly.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
data : np.ndarray
|
|
449
|
+
The input data to fit.
|
|
450
|
+
|
|
451
|
+
Raises
|
|
452
|
+
------
|
|
453
|
+
Exception
|
|
454
|
+
If an error occurs during the fitting process.
|
|
455
|
+
'''
|
|
456
|
+
try:
|
|
457
|
+
# find IR peaks above a specific height
|
|
458
|
+
peaks = find_peaks(data, height=1)
|
|
459
|
+
nPeaks = len(peaks[0]) # number of peaks
|
|
460
|
+
maxPeakIdx = np.argmax(peaks[1]['peak_heights']) # highest peak
|
|
461
|
+
x0 = 64 if nPeaks == 0 else peaks[0][maxPeakIdx]
|
|
462
|
+
a0 = 1 if nPeaks == 0 else peaks[1]['peak_heights'][maxPeakIdx]
|
|
463
|
+
|
|
464
|
+
# curve_fit to GaussianOffSet
|
|
465
|
+
self.fit_params, _ = curve_fit(
|
|
466
|
+
GaussianOffSet, np.arange(data.shape[0]), data, p0=[a0, x0, 1, 0]
|
|
467
|
+
)
|
|
468
|
+
self.peak_positions = np.roll(self.peak_positions, -1)
|
|
469
|
+
self.peak_positions[-1] = self.fit_params[1]
|
|
470
|
+
|
|
471
|
+
if self.isFocusStabilized():
|
|
472
|
+
err = np.average(self.peak_positions[-1] - self.getPeakPosition())
|
|
473
|
+
tau = self.__tau if self.__tau > 0 else (max(self._exec_time, 1) / 1000)
|
|
474
|
+
self.error_integral += err * tau
|
|
475
|
+
diff = (err - self.error_buffer[-1]) / tau
|
|
476
|
+
self.error_buffer = np.roll(self.error_buffer, -1)
|
|
477
|
+
self.error_buffer[-1] = err
|
|
478
|
+
step = int(
|
|
479
|
+
(err * self.__P)
|
|
480
|
+
+ (self.error_integral * self.__I)
|
|
481
|
+
+ (diff * self.__D)
|
|
482
|
+
)
|
|
483
|
+
if abs(err) > self.__err_th:
|
|
484
|
+
direction = (step <= 0) if not self.isInverted() else (step > 0)
|
|
485
|
+
self.moveStage.emit(direction, abs(step))
|
|
486
|
+
else:
|
|
487
|
+
self.setPeakPosition(self.peak_positions[-1])
|
|
488
|
+
self.error_buffer = np.zeros((20,))
|
|
489
|
+
self.error_integral = 0
|
|
490
|
+
|
|
491
|
+
except Exception as e:
|
|
492
|
+
pass
|
|
493
|
+
# print('Failed Gauss. fit: ' + str(e))
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
class FocusStabilizerView(Tree):
|
|
497
|
+
PARAMS = FocusStabilizerParams
|
|
498
|
+
setRoiActivated = Signal()
|
|
499
|
+
saveActivated = Signal()
|
|
500
|
+
loadActivated = Signal()
|
|
501
|
+
|
|
502
|
+
def __init__(
|
|
503
|
+
self,
|
|
504
|
+
parent: Optional['QtWidgets.QWidget'] = None,
|
|
505
|
+
focusStabilizer: FocusStabilizer = None,
|
|
506
|
+
):
|
|
507
|
+
'''
|
|
508
|
+
Initialize the FocusStabilizerView instance.
|
|
509
|
+
|
|
510
|
+
This method initializes the `FocusStabilizer` instance, sets up the signals,
|
|
511
|
+
creates the parameter tree, and sets up the GUI elements.
|
|
512
|
+
|
|
513
|
+
Parameters
|
|
514
|
+
----------
|
|
515
|
+
parent : Optional[QWidget]
|
|
516
|
+
The parent widget.
|
|
517
|
+
focusStabilizer : Optional[`FocusStabilizer`]
|
|
518
|
+
The Focus Stabilizer to be controlled by the GUI.
|
|
519
|
+
If None, a new `FocusStabilizer` instance is created.
|
|
520
|
+
'''
|
|
521
|
+
super().__init__(parent=parent)
|
|
522
|
+
|
|
523
|
+
FocusStabilizer.instance().peakPositionChanged.connect(
|
|
524
|
+
lambda value: self.set_param_value(FocusStabilizerParams.FOCUS_PEAK, value)
|
|
525
|
+
)
|
|
526
|
+
FocusStabilizer.instance().pixelCalChanged.connect(
|
|
527
|
+
lambda value: self.set_param_value(FocusStabilizerParams.PIXEL_CAL, value)
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
self.setMinimumWidth(250)
|
|
531
|
+
|
|
532
|
+
def create_parameters(self):
|
|
533
|
+
'''
|
|
534
|
+
Create the parameter tree structure.
|
|
535
|
+
|
|
536
|
+
This method creates and sets up the parameter tree structure for the
|
|
537
|
+
`PzFocView` class.
|
|
538
|
+
'''
|
|
539
|
+
params = [
|
|
540
|
+
{
|
|
541
|
+
'name': str(FocusStabilizerParams.PREVIEW_IMAGE),
|
|
542
|
+
'type': 'bool',
|
|
543
|
+
'value': True,
|
|
544
|
+
},
|
|
545
|
+
{'name': str(FocusStabilizerParams.ROI), 'type': 'group', 'children': []},
|
|
546
|
+
{
|
|
547
|
+
'name': str(FocusStabilizerParams.X1),
|
|
548
|
+
'type': 'float',
|
|
549
|
+
'value': 25.5,
|
|
550
|
+
'step': 1,
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
'name': str(FocusStabilizerParams.Y1),
|
|
554
|
+
'type': 'float',
|
|
555
|
+
'value': 25.5,
|
|
556
|
+
'step': 1,
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
'name': str(FocusStabilizerParams.X2),
|
|
560
|
+
'type': 'float',
|
|
561
|
+
'value': 25.5,
|
|
562
|
+
'step': 1,
|
|
563
|
+
'readonly': True,
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
'name': str(FocusStabilizerParams.Y2),
|
|
567
|
+
'type': 'float',
|
|
568
|
+
'value': 281.5,
|
|
569
|
+
'step': 1,
|
|
570
|
+
'readonly': True,
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
'name': str(FocusStabilizerParams.LINE_WIDTH),
|
|
574
|
+
'type': 'int',
|
|
575
|
+
'value': 1,
|
|
576
|
+
'limits': [1, 100],
|
|
577
|
+
'step': 1,
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
'name': str(FocusStabilizerParams.LENGTH),
|
|
581
|
+
'type': 'int',
|
|
582
|
+
'value': 256,
|
|
583
|
+
'limits': [64, 4096],
|
|
584
|
+
'step': 1,
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
'name': str(FocusStabilizerParams.ANGLE),
|
|
588
|
+
'type': 'float',
|
|
589
|
+
'value': 0,
|
|
590
|
+
'limits': [0, 360],
|
|
591
|
+
'step': 1,
|
|
592
|
+
},
|
|
593
|
+
{'name': str(FocusStabilizerParams.SET_ROI), 'type': 'action'},
|
|
594
|
+
{'name': str(FocusStabilizerParams.SAVE), 'type': 'action'},
|
|
595
|
+
{'name': str(FocusStabilizerParams.LOAD), 'type': 'action'},
|
|
596
|
+
{
|
|
597
|
+
'name': str(FocusStabilizerParams.FOCUS_TRACKING),
|
|
598
|
+
'type': 'group',
|
|
599
|
+
'children': [],
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
'name': str(FocusStabilizerParams.FOCUS_PEAK),
|
|
603
|
+
'type': 'float',
|
|
604
|
+
'value': 0.0,
|
|
605
|
+
'step': 0.01,
|
|
606
|
+
'dec': False,
|
|
607
|
+
'decimals': 6,
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
'name': str(FocusStabilizerParams.PIXEL_CAL),
|
|
611
|
+
'type': 'float',
|
|
612
|
+
'value': 0.01,
|
|
613
|
+
'step': 0.01,
|
|
614
|
+
'dec': False,
|
|
615
|
+
'decimals': 6,
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
'name': str(FocusStabilizerParams.USE_CAL),
|
|
619
|
+
'type': 'bool',
|
|
620
|
+
'value': False,
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
'name': str(FocusStabilizerParams.INVERTED),
|
|
624
|
+
'type': 'bool',
|
|
625
|
+
'value': False,
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
'name': str(FocusStabilizerParams.TRACKING_ENABLED),
|
|
629
|
+
'type': 'bool',
|
|
630
|
+
'value': False,
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
'name': str(FocusStabilizerParams.FT_P),
|
|
634
|
+
'type': 'float',
|
|
635
|
+
'value': 6.450,
|
|
636
|
+
'step': 0.01,
|
|
637
|
+
'dec': False,
|
|
638
|
+
'decimals': 6,
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
'name': str(FocusStabilizerParams.FT_I),
|
|
642
|
+
'type': 'float',
|
|
643
|
+
'value': 0.0,
|
|
644
|
+
'step': 0.01,
|
|
645
|
+
'dec': False,
|
|
646
|
+
'decimals': 6,
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
'name': str(FocusStabilizerParams.FT_D),
|
|
650
|
+
'type': 'float',
|
|
651
|
+
'value': 0.0,
|
|
652
|
+
'step': 0.01,
|
|
653
|
+
'dec': False,
|
|
654
|
+
'decimals': 6,
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
'name': str(FocusStabilizerParams.FT_TAU),
|
|
658
|
+
'type': 'float',
|
|
659
|
+
'value': 0.0,
|
|
660
|
+
'step': 0.01,
|
|
661
|
+
'dec': False,
|
|
662
|
+
'decimals': 6,
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
'name': str(FocusStabilizerParams.FT_ERROR_TH),
|
|
666
|
+
'type': 'float',
|
|
667
|
+
'value': 0.001,
|
|
668
|
+
'step': 0.001,
|
|
669
|
+
'dec': False,
|
|
670
|
+
'decimals': 6,
|
|
671
|
+
},
|
|
672
|
+
{'name': str(FocusStabilizerParams.PEAK_ACQUIRE), 'type': 'action'},
|
|
673
|
+
{'name': str(FocusStabilizerParams.PEAK_STOP), 'type': 'action'},
|
|
674
|
+
]
|
|
675
|
+
|
|
676
|
+
self.param_tree = Parameter.create(name='', type='group', children=params)
|
|
677
|
+
self.param_tree.sigTreeStateChanged.connect(self.change)
|
|
678
|
+
self.header().setSectionResizeMode(
|
|
679
|
+
QtWidgets.QHeaderView.ResizeMode.ResizeToContents
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
self.get_param(FocusStabilizerParams.SET_ROI).sigActivated.connect(
|
|
683
|
+
lambda: self.setRoiActivated.emit()
|
|
684
|
+
)
|
|
685
|
+
self.get_param(FocusStabilizerParams.SAVE).sigActivated.connect(
|
|
686
|
+
self.export_json
|
|
687
|
+
)
|
|
688
|
+
self.get_param(FocusStabilizerParams.LOAD).sigActivated.connect(self.load_json)
|
|
689
|
+
|
|
690
|
+
self.get_param(FocusStabilizerParams.PEAK_ACQUIRE).sigActivated.connect(
|
|
691
|
+
self.start_IR
|
|
692
|
+
)
|
|
693
|
+
self.get_param(FocusStabilizerParams.PEAK_STOP).sigActivated.connect(
|
|
694
|
+
self.stop_IR
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
def change(self, param: Parameter, changes: list):
|
|
698
|
+
'''
|
|
699
|
+
Handle parameter changes as needed.
|
|
700
|
+
|
|
701
|
+
This method handles the changes made to the parameters in the parameter
|
|
702
|
+
tree.
|
|
703
|
+
|
|
704
|
+
Parameters
|
|
705
|
+
----------
|
|
706
|
+
param : Parameter
|
|
707
|
+
The parameter that triggered the change.
|
|
708
|
+
changes : list
|
|
709
|
+
List of changes.
|
|
710
|
+
|
|
711
|
+
Returns
|
|
712
|
+
-------
|
|
713
|
+
None
|
|
714
|
+
'''
|
|
715
|
+
# Handle parameter changes as needed
|
|
716
|
+
for p, _, data in changes:
|
|
717
|
+
path = self.param_tree.childPath(p)
|
|
718
|
+
|
|
719
|
+
if path == FocusStabilizerParams.get_path(
|
|
720
|
+
FocusStabilizerParams.TRACKING_ENABLED
|
|
721
|
+
):
|
|
722
|
+
FocusStabilizer.instance().toggleFocusStabilization(data)
|
|
723
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.FT_P):
|
|
724
|
+
FocusStabilizer.instance().setP(data)
|
|
725
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.FT_I):
|
|
726
|
+
FocusStabilizer.instance().setI(data)
|
|
727
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.FT_D):
|
|
728
|
+
FocusStabilizer.instance().setD(data)
|
|
729
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.FT_TAU):
|
|
730
|
+
FocusStabilizer.instance().setTau(data)
|
|
731
|
+
if path == FocusStabilizerParams.get_path(
|
|
732
|
+
FocusStabilizerParams.FT_ERROR_TH
|
|
733
|
+
):
|
|
734
|
+
FocusStabilizer.instance().setErrorTh(data)
|
|
735
|
+
|
|
736
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.X1):
|
|
737
|
+
FocusStabilizer.instance().X[0] = data
|
|
738
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.X2):
|
|
739
|
+
FocusStabilizer.instance().X[1] = data
|
|
740
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.Y1):
|
|
741
|
+
FocusStabilizer.instance().Y[0] = data
|
|
742
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.Y2):
|
|
743
|
+
FocusStabilizer.instance().Y[1] = data
|
|
744
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.LINE_WIDTH):
|
|
745
|
+
FocusStabilizer.instance().line_width = data
|
|
746
|
+
|
|
747
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.INVERTED):
|
|
748
|
+
FocusStabilizer.instance().setInverted(data)
|
|
749
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.USE_CAL):
|
|
750
|
+
FocusStabilizer.instance().setUseCal(data)
|
|
751
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.FOCUS_PEAK):
|
|
752
|
+
FocusStabilizer.instance().setPeakPosition(data)
|
|
753
|
+
if path == FocusStabilizerParams.get_path(FocusStabilizerParams.PIXEL_CAL):
|
|
754
|
+
FocusStabilizer.instance().setPixelCalCoeff(data)
|
|
755
|
+
if path == FocusStabilizerParams.get_path(
|
|
756
|
+
FocusStabilizerParams.PREVIEW_IMAGE
|
|
757
|
+
):
|
|
758
|
+
FocusStabilizer.instance().setPreview(data)
|
|
759
|
+
|
|
760
|
+
def toggleFocusStabilization(self):
|
|
761
|
+
'''Toggles the focus stabilization.'''
|
|
762
|
+
self.set_param_value(
|
|
763
|
+
FocusStabilizerParams.TRACKING_ENABLED,
|
|
764
|
+
not self.get_param_value(FocusStabilizerParams.TRACKING_ENABLED),
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
def start_IR(self):
|
|
768
|
+
'''Starts the IR peak position acquisition and
|
|
769
|
+
creates a file in the current directory.
|
|
770
|
+
'''
|
|
771
|
+
if FocusStabilizer.instance().file is None:
|
|
772
|
+
filename = None
|
|
773
|
+
if filename is None:
|
|
774
|
+
filename, _ = getSaveFileName(
|
|
775
|
+
self, 'Save Focus Peak Data', filter='CSV Files (*.csv)'
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
if len(filename) > 0:
|
|
779
|
+
FocusStabilizer.instance().file = filename
|
|
780
|
+
|
|
781
|
+
def stop_IR(self):
|
|
782
|
+
'''Stops the IR peak position acquisition and closes the file.'''
|
|
783
|
+
if FocusStabilizer.instance().file is not None:
|
|
784
|
+
FocusStabilizer.instance().file = None
|
|
785
|
+
FocusStabilizer.instance().num_frames_saved = 0
|