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.
Files changed (190) hide show
  1. microEye/__init__.py +47 -0
  2. microEye/_version.py +2 -0
  3. microEye/analysis/__init__.py +1 -0
  4. microEye/analysis/checklist_dialog.py +143 -0
  5. microEye/analysis/cmosMaps.py +228 -0
  6. microEye/analysis/filters/__init__.py +9 -0
  7. microEye/analysis/filters/base.py +21 -0
  8. microEye/analysis/filters/spatial.py +338 -0
  9. microEye/analysis/filters/temporal.py +76 -0
  10. microEye/analysis/fitting/__init__.py +0 -0
  11. microEye/analysis/fitting/fit.py +680 -0
  12. microEye/analysis/fitting/nena.py +375 -0
  13. microEye/analysis/fitting/phasor_fit.py +90 -0
  14. microEye/analysis/fitting/processing.py +317 -0
  15. microEye/analysis/fitting/psf/__init__.py +6 -0
  16. microEye/analysis/fitting/psf/extract.py +1129 -0
  17. microEye/analysis/fitting/psf/rubost_mean.py +150 -0
  18. microEye/analysis/fitting/psf/spline.py +167 -0
  19. microEye/analysis/fitting/psf/stats/__init__.py +12 -0
  20. microEye/analysis/fitting/psf/stats/core.py +295 -0
  21. microEye/analysis/fitting/psf/stats/curve_fit.py +708 -0
  22. microEye/analysis/fitting/psf/stats/io.py +104 -0
  23. microEye/analysis/fitting/psf/stats/slope_fit.py +171 -0
  24. microEye/analysis/fitting/psf/temp.py +147 -0
  25. microEye/analysis/fitting/psf/test.py +47 -0
  26. microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUfunctions.py +657 -0
  27. microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUmleFit_LM.py +1336 -0
  28. microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUsplineLib.py +270 -0
  29. microEye/analysis/fitting/pyfit3Dcspline/CPU/__init__.py +1 -0
  30. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUfunctions.py +609 -0
  31. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_EMCCD.py +1396 -0
  32. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_sCMOS.py +1426 -0
  33. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUsplineLib.py +231 -0
  34. microEye/analysis/fitting/pyfit3Dcspline/GPU/__init__.py +2 -0
  35. microEye/analysis/fitting/pyfit3Dcspline/__init__.py +10 -0
  36. microEye/analysis/fitting/pyfit3Dcspline/constants.py +27 -0
  37. microEye/analysis/fitting/pyfit3Dcspline/mainfunctions.py +903 -0
  38. microEye/analysis/fitting/results.py +917 -0
  39. microEye/analysis/fitting/results_stats.py +251 -0
  40. microEye/analysis/fitting/tardis.py +209 -0
  41. microEye/analysis/multi_viewer.py +568 -0
  42. microEye/analysis/processing/__init__.py +1 -0
  43. microEye/analysis/processing/frc.py +120 -0
  44. microEye/analysis/rendering/__init__.py +3 -0
  45. microEye/analysis/rendering/base.py +452 -0
  46. microEye/analysis/rendering/cloud.py +215 -0
  47. microEye/analysis/rendering/core.py +47 -0
  48. microEye/analysis/rendering/volumetric.py +198 -0
  49. microEye/analysis/tools/__init__.py +0 -0
  50. microEye/analysis/tools/kymograms.py +1079 -0
  51. microEye/analysis/tools/roi_selectors.py +556 -0
  52. microEye/analysis/utils/__init__.py +3 -0
  53. microEye/analysis/utils/coordinates.py +18 -0
  54. microEye/analysis/utils/images.py +68 -0
  55. microEye/analysis/utils/windows.py +19 -0
  56. microEye/analysis/viewer/__init__.py +3 -0
  57. microEye/analysis/viewer/image_options_widget.py +615 -0
  58. microEye/analysis/viewer/images.py +1225 -0
  59. microEye/analysis/viewer/layers_widget.py +415 -0
  60. microEye/analysis/viewer/localizations.py +1304 -0
  61. microEye/analysis/viewer/psf.py +1003 -0
  62. microEye/analysis/viewer/volume.py +456 -0
  63. microEye/hardware/__init__.py +3 -0
  64. microEye/hardware/cams/__init__.py +29 -0
  65. microEye/hardware/cams/camera_calibration.py +99 -0
  66. microEye/hardware/cams/camera_list.py +528 -0
  67. microEye/hardware/cams/camera_options.py +694 -0
  68. microEye/hardware/cams/camera_panel.py +941 -0
  69. microEye/hardware/cams/dummy/__init__.py +1 -0
  70. microEye/hardware/cams/dummy/dummy_panel.py +759 -0
  71. microEye/hardware/cams/jobs.py +497 -0
  72. microEye/hardware/cams/line_profiler.py +99 -0
  73. microEye/hardware/cams/linescan/IR_Cam.py +474 -0
  74. microEye/hardware/cams/linescan/__init__.py +1 -0
  75. microEye/hardware/cams/micam.py +524 -0
  76. microEye/hardware/cams/pco/__init__.py +58 -0
  77. microEye/hardware/cams/pco/enums.py +382 -0
  78. microEye/hardware/cams/pco/pco_cam.py +761 -0
  79. microEye/hardware/cams/pco/pco_panel.py +477 -0
  80. microEye/hardware/cams/shortcuts.py +312 -0
  81. microEye/hardware/cams/thorlabs/__init__.py +1 -0
  82. microEye/hardware/cams/thorlabs/thorlabs.py +1508 -0
  83. microEye/hardware/cams/thorlabs/thorlabs_panel.py +850 -0
  84. microEye/hardware/cams/ueye/__init__.py +1 -0
  85. microEye/hardware/cams/ueye/ueye_camera.py +1023 -0
  86. microEye/hardware/cams/ueye/ueye_panel.py +861 -0
  87. microEye/hardware/cams/vimba/__init__.py +1 -0
  88. microEye/hardware/cams/vimba/vimba_cam.py +1000 -0
  89. microEye/hardware/cams/vimba/vimba_panel.py +813 -0
  90. microEye/hardware/device.py +60 -0
  91. microEye/hardware/lasers/__init__.py +13 -0
  92. microEye/hardware/lasers/io_matchbox.py +791 -0
  93. microEye/hardware/lasers/io_params.py +85 -0
  94. microEye/hardware/lasers/io_single_laser.py +742 -0
  95. microEye/hardware/lasers/laser_relay.py +594 -0
  96. microEye/hardware/mieye/__init__.py +1 -0
  97. microEye/hardware/mieye/acquisition_manager.py +467 -0
  98. microEye/hardware/mieye/devices_manager.py +533 -0
  99. microEye/hardware/mieye/miEye.py +659 -0
  100. microEye/hardware/misc/__init__.py +0 -0
  101. microEye/hardware/misc/acquisition_view.py +71 -0
  102. microEye/hardware/misc/reglo.py +761 -0
  103. microEye/hardware/misc/temp.py +188 -0
  104. microEye/hardware/port_config.py +59 -0
  105. microEye/hardware/protocols/__init__.py +2 -0
  106. microEye/hardware/protocols/actions.py +402 -0
  107. microEye/hardware/protocols/actions_items.py +703 -0
  108. microEye/hardware/protocols/designer.py +244 -0
  109. microEye/hardware/protocols/scene_manager.py +191 -0
  110. microEye/hardware/protocols/serialization.py +97 -0
  111. microEye/hardware/pycromanager/__init__.py +16 -0
  112. microEye/hardware/pycromanager/core.py +1433 -0
  113. microEye/hardware/pycromanager/devices.py +461 -0
  114. microEye/hardware/pycromanager/enums.py +107 -0
  115. microEye/hardware/pycromanager/headless.py +153 -0
  116. microEye/hardware/pycromanager/utils.py +34 -0
  117. microEye/hardware/pycromanager/widgets/__init__.py +5 -0
  118. microEye/hardware/pycromanager/widgets/bridges.py +407 -0
  119. microEye/hardware/pycromanager/widgets/headless_manager.py +258 -0
  120. microEye/hardware/pycromanager/widgets/headless_options.py +224 -0
  121. microEye/hardware/pycromanager/widgets/pycro_panel.py +455 -0
  122. microEye/hardware/stages/__init__.py +18 -0
  123. microEye/hardware/stages/elliptec/__init__.py +5 -0
  124. microEye/hardware/stages/elliptec/baseDevice.py +314 -0
  125. microEye/hardware/stages/elliptec/device.py +384 -0
  126. microEye/hardware/stages/elliptec/deviceID.py +212 -0
  127. microEye/hardware/stages/elliptec/devicePort.py +379 -0
  128. microEye/hardware/stages/elliptec/deviceStatus.py +65 -0
  129. microEye/hardware/stages/elliptec/devicesView.py +706 -0
  130. microEye/hardware/stages/elliptec/ellDevices.py +134 -0
  131. microEye/hardware/stages/elliptec/messageUpdater.py +34 -0
  132. microEye/hardware/stages/elliptec/motorInfo.py +153 -0
  133. microEye/hardware/stages/elliptec/stage.py +62 -0
  134. microEye/hardware/stages/elliptec/test.py +139 -0
  135. microEye/hardware/stages/kinesis/__init__.py +1 -0
  136. microEye/hardware/stages/kinesis/kdc101/__init__.py +1 -0
  137. microEye/hardware/stages/kinesis/kdc101/enums.py +1002 -0
  138. microEye/hardware/stages/kinesis/kdc101/factory.py +171 -0
  139. microEye/hardware/stages/kinesis/kdc101/kdc101.py +718 -0
  140. microEye/hardware/stages/kinesis/kinesis.py +776 -0
  141. microEye/hardware/stages/piezo_concept.py +607 -0
  142. microEye/hardware/stages/stabilizer.py +785 -0
  143. microEye/hardware/stages/stage.py +89 -0
  144. microEye/hardware/widgets/__init__.py +10 -0
  145. microEye/hardware/widgets/controller.py +246 -0
  146. microEye/hardware/widgets/devices.py +133 -0
  147. microEye/hardware/widgets/focusWidget.py +264 -0
  148. microEye/hardware/widgets/qlist_slider.py +113 -0
  149. microEye/hardware/widgets/scan_acquisition.py +424 -0
  150. microEye/icons/1024.png +0 -0
  151. microEye/icons/128.png +0 -0
  152. microEye/icons/16.png +0 -0
  153. microEye/icons/24.png +0 -0
  154. microEye/icons/256.png +0 -0
  155. microEye/icons/32.png +0 -0
  156. microEye/icons/48.png +0 -0
  157. microEye/icons/512.png +0 -0
  158. microEye/icons/64.png +0 -0
  159. microEye/icons/__init__.py +0 -0
  160. microEye/icons/close.svg +88 -0
  161. microEye/icons/mieye.png +0 -0
  162. microEye/icons/min.svg +83 -0
  163. microEye/icons/viewer.png +0 -0
  164. microEye/launcher.py +42 -0
  165. microEye/qt.py +181 -0
  166. microEye/utils/__init__.py +2 -0
  167. microEye/utils/enum_encoder.py +10 -0
  168. microEye/utils/expandable_groupbox.py +93 -0
  169. microEye/utils/gui_helper.py +457 -0
  170. microEye/utils/hid/__init__.py +8 -0
  171. microEye/utils/hid/controller.py +153 -0
  172. microEye/utils/hid/device.py +63 -0
  173. microEye/utils/hid/enums.py +140 -0
  174. microEye/utils/hid/utils.py +58 -0
  175. microEye/utils/labelled_slider.py +134 -0
  176. microEye/utils/metadata.py +599 -0
  177. microEye/utils/metadata_tree.py +718 -0
  178. microEye/utils/micro_launcher.py +245 -0
  179. microEye/utils/parameter_tree.py +325 -0
  180. microEye/utils/pyscripting.py +444 -0
  181. microEye/utils/retry_exec.py +35 -0
  182. microEye/utils/start_gui.py +112 -0
  183. microEye/utils/thread_worker.py +115 -0
  184. microEye/utils/uImage.py +1512 -0
  185. microeye-2.3.2.dist-info/METADATA +401 -0
  186. microeye-2.3.2.dist-info/RECORD +190 -0
  187. microeye-2.3.2.dist-info/WHEEL +5 -0
  188. microeye-2.3.2.dist-info/entry_points.txt +2 -0
  189. microeye-2.3.2.dist-info/licenses/LICENSE +674 -0
  190. 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