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,314 @@
1
+ from enum import Enum
2
+ from threading import Lock
3
+ from typing import Any, Optional
4
+
5
+ from microEye.hardware.stages.elliptec.deviceID import DeviceID
6
+ from microEye.hardware.stages.elliptec.devicePort import DevicePort, ELLException
7
+ from microEye.hardware.stages.elliptec.deviceStatus import (
8
+ DeviceStatus,
9
+ DeviceStatusValues,
10
+ )
11
+ from microEye.hardware.stages.elliptec.messageUpdater import MessageUpdater
12
+ from microEye.hardware.stages.elliptec.motorInfo import MotorInfo
13
+ from microEye.qt import QtCore
14
+
15
+
16
+ class StatusResult(Enum):
17
+ '''Values that represent status results.'''
18
+
19
+ OK = 1
20
+ Busy = 2
21
+ Error = 3
22
+ Fail = 4
23
+
24
+
25
+ class DeviceDirection(Enum):
26
+ '''Values that represent Device Direction.
27
+
28
+ These are used to set the homing direction for a device
29
+ '''
30
+
31
+ Linear = '0'
32
+ Clockwise = '0'
33
+ AntiClockwise = '1'
34
+
35
+ def __str__(self):
36
+ return {
37
+ '0': 'Linear',
38
+ '0': 'Clockwise',
39
+ '1': 'AntiClockwise',
40
+ }[self.value]
41
+
42
+
43
+ class BaseDevice(QtCore.QObject):
44
+ VALID_ADDRESSES = tuple(
45
+ [
46
+ '0',
47
+ '1',
48
+ '2',
49
+ '3',
50
+ '4',
51
+ '5',
52
+ '6',
53
+ '7',
54
+ '8',
55
+ '9',
56
+ 'A',
57
+ 'B',
58
+ 'C',
59
+ 'D',
60
+ 'E',
61
+ 'F',
62
+ ]
63
+ )
64
+
65
+ CLEAN_TIMEOUT = 10000 # The clean timeout
66
+ MOVE_TIMEOUT = 4000 # The move timeout
67
+ SEARCH_TIMEOUT = 8000 # The search timeout
68
+ HOME_TIMEOUT = 6000 # The home timeout
69
+ STATUS_TIMEOUT = 1000 # The status timeout
70
+
71
+ def __init__(
72
+ self,
73
+ device_id: DeviceID,
74
+ motor_count: int,
75
+ message_updater: Optional[MessageUpdater] = None,
76
+ ):
77
+ super().__init__()
78
+ self._lock = Lock()
79
+ self._message_updater = message_updater
80
+ self._motor_info: dict[Any, MotorInfo] = {}
81
+ self.deviceInfo = device_id
82
+ self.address = device_id.Address
83
+ self.deviceStatus = DeviceStatusValues.OK
84
+ self.auto_save = False
85
+ self._initialize_class(device_id, motor_count)
86
+
87
+ def _initialize_class(self, device_id, motor_count: int):
88
+ for c in range(1, motor_count + 1):
89
+ self._motor_info[str(c)] = MotorInfo(str(c))
90
+
91
+ @staticmethod
92
+ def configure(device_id, message_updater: MessageUpdater):
93
+ if not device_id:
94
+ return None
95
+ address = device_id[0]
96
+ if not BaseDevice.is_valid_address(address):
97
+ return None
98
+ device_id_obj = DeviceID(device_id)
99
+ if message_updater:
100
+ message_updater.update_parameter(
101
+ MessageUpdater.UpdateTypes.DeviceInfo, address, device_id_obj
102
+ )
103
+ return device_id_obj
104
+
105
+ @property
106
+ def valid_addresses(self):
107
+ return self.VALID_ADDRESSES
108
+
109
+ @staticmethod
110
+ def is_valid_address(address):
111
+ return address in BaseDevice.VALID_ADDRESSES
112
+
113
+ @property
114
+ def is_address_valid(self):
115
+ return self.address in self.VALID_ADDRESSES
116
+
117
+ def save_user_data(self):
118
+ self._update_output('Save user status...')
119
+ DevicePort.send_command(self.address, 'us')
120
+ return self._wait_for_status()
121
+
122
+ def set_address(self, new_address):
123
+ if new_address == self.address:
124
+ return True
125
+ self._update_output(f'changing address to {new_address}...')
126
+ msg = DevicePort.send_string_b(self.address, 'ca', new_address)
127
+ self.address = new_address
128
+ return msg
129
+
130
+ def is_motor_id_valid(self, c):
131
+ return c in self._motor_info
132
+
133
+ def get_motor_info(self, motor_id):
134
+ if not self.is_motor_id_valid(motor_id):
135
+ return False
136
+ self._update_output(f'Requesting Motor{motor_id} info...')
137
+ DevicePort.send_command(self.address, 'i', motor_id)
138
+ try:
139
+ msg = DevicePort.wait_for_response(
140
+ self.address, 'I' + motor_id, self.MOVE_TIMEOUT
141
+ )
142
+ if msg:
143
+ info = MotorInfo.from_string(msg)
144
+ self._motor_info[motor_id] = info
145
+ self._update_output('Get Device Status: ', info.description())
146
+ self._update_parameter(
147
+ MessageUpdater.UpdateTypes.MotorInfo, self.address, info
148
+ )
149
+ return True
150
+ except ELLException:
151
+ self._update_output(f'Requesting Motor{motor_id}: FAILED')
152
+ return False
153
+
154
+ def set_period(self, motor_id, fwd, frequency, permanent, hard_save_frequency):
155
+ if not self.is_motor_id_valid(motor_id):
156
+ return False
157
+ period = int(14740 / frequency)
158
+ self._update_output(
159
+ f"Motor{motor_id} - Setting {'Fwd' if fwd else 'Bwd'} period to {period:X}"
160
+ f"({frequency}kHz) ..."
161
+ )
162
+ if hard_save_frequency:
163
+ period |= 0x8000
164
+ DevicePort.send_string_i16(
165
+ self.address, ('f' if fwd else 'b') + motor_id, period
166
+ )
167
+ if not self._wait_for_status():
168
+ return False
169
+ if permanent:
170
+ self.save_user_data()
171
+ return True
172
+
173
+ def search_period(self, motor_id, permanent):
174
+ if not self.is_motor_id_valid(motor_id):
175
+ return False
176
+ self._update_output(f'Motor{motor_id} - Search period ...')
177
+ DevicePort.send_command(self.address, 's', motor_id)
178
+ if not self._wait_for_status(self.SEARCH_TIMEOUT):
179
+ return False
180
+ self.get_motor_info(motor_id)
181
+ if permanent:
182
+ self.save_user_data()
183
+ return True
184
+
185
+ def reset_period(self):
186
+ motors = [
187
+ key
188
+ for key in self._motor_info
189
+ if self._motor_info[key] and self._motor_info[key].is_valid
190
+ ]
191
+ for motor_id in motors:
192
+ self._update_output(f'Motor{motor_id} - Reset default period ...')
193
+ period = int(14740 / 100) | 0x8000
194
+ DevicePort.send_string_i16(self.address, 'f' + motor_id, period)
195
+ if not self._wait_for_status(self.SEARCH_TIMEOUT):
196
+ return False
197
+ DevicePort.send_string_i16(self.address, 'b' + motor_id, period)
198
+ if not self._wait_for_status(self.SEARCH_TIMEOUT):
199
+ return False
200
+ self.get_motor_info(motor_id)
201
+ self.save_user_data()
202
+ return True
203
+
204
+ def scan_current_curve(self, motor_id):
205
+ if not self.is_motor_id_valid(motor_id):
206
+ return False
207
+ self._update_output(f'Motor{motor_id} - Scan Current ...')
208
+ DevicePort.send_command(self.address, 'c', motor_id)
209
+ if not self._wait_for_status(6000):
210
+ return False
211
+ self.get_motor_info(motor_id)
212
+ return True
213
+
214
+ def _update_parameter(self, update_type, address, data):
215
+ if self._message_updater:
216
+ self._message_updater.update_parameter(update_type, address, data)
217
+
218
+ def _update_output(self, message, error=False):
219
+ if self._message_updater:
220
+ self._message_updater.update_output(message, error)
221
+
222
+ def _update_device_status(self, device_status: DeviceStatusValues):
223
+ error = device_status not in [
224
+ DeviceStatusValues.OK,
225
+ DeviceStatusValues.Busy,
226
+ ]
227
+ self._update_output('Get Device Status: ' + device_status.name, error)
228
+ self._update_parameter(
229
+ MessageUpdater.UpdateTypes.Status, self.address, device_status
230
+ )
231
+ self.deviceStatus = device_status
232
+
233
+ def get_device_status(self):
234
+ self._update_output('Get Device Status...')
235
+ DevicePort.send_command(self.address, 'gs')
236
+ return self._wait_for_status()
237
+
238
+ def _wait_for_status(self, ms_timeout=MOVE_TIMEOUT):
239
+ return self._wait_for_status_internal(self.address, ms_timeout)
240
+
241
+ def _wait_for_status_internal(self, address, ms_timeout=MOVE_TIMEOUT, getter=False):
242
+ try:
243
+ counter = 10
244
+ while True:
245
+ msg = DevicePort.wait_for_response(address, 'GS', ms_timeout)
246
+ if msg:
247
+ return_value, counter = self._test_status(
248
+ msg, 'gs', getter, counter
249
+ )
250
+ if return_value is not None:
251
+ return return_value
252
+ except ELLException as ex:
253
+ self._update_output(f'Get Device Status: {ex}', True)
254
+ return False
255
+
256
+ def _test_status(self, msg: str, cmd: str, getter: bool, counter: int):
257
+ if msg[1:3] == 'GS':
258
+ result = self.process_device_status(DeviceStatus(msg).Status, cmd, getter)
259
+ if result in [
260
+ StatusResult.Fail,
261
+ StatusResult.Busy,
262
+ ]:
263
+ return False, counter
264
+ if result == StatusResult.OK and not getter:
265
+ return True, counter
266
+ if result == StatusResult.Error:
267
+ return False, counter
268
+ counter -= 1
269
+ if counter == 0:
270
+ return False, counter
271
+ return None, counter
272
+
273
+ def process_device_status(
274
+ self, device_status: DeviceStatus, resend_cmd: str, getter: bool
275
+ ):
276
+ raise NotImplementedError('This method should be implemented by subclasses.')
277
+
278
+ def set_sleep_delay_counter(self, delay_counter: int):
279
+ with self._lock:
280
+ self._sleep_duration_counter = delay_counter
281
+
282
+ def get_sleep_delay_counter(self):
283
+ with self._lock:
284
+ return self._sleep_duration_counter
285
+
286
+ def wait_for_cleaning(self, ms_timeout=None):
287
+ if ms_timeout is None:
288
+ ms_timeout = BaseDevice.CLEAN_TIMEOUT
289
+
290
+ try:
291
+ counter = 60
292
+ while True:
293
+ msg = DevicePort.wait_for_response(self.address, 'GS', ms_timeout)
294
+ if msg:
295
+ if msg[1:5] == 'GS09':
296
+ self.set_sleep_delay_counter(10)
297
+ while self.get_sleep_delay_counter() >= 0:
298
+ self.set_sleep_delay_counter(
299
+ self.get_sleep_delay_counter() - 1
300
+ )
301
+ QtCore.QThread.msleep(1000)
302
+ DevicePort.send_command(self.address, 'gs')
303
+ else:
304
+ return_value, counter = self._test_status(
305
+ msg, 'gs', False, counter
306
+ )
307
+ if return_value is not None:
308
+ return return_value
309
+ except ELLException as ex:
310
+ if self._message_updater:
311
+ self._message_updater.update_output(
312
+ f'Get Device Status: {str(ex)}', True
313
+ )
314
+ return False
@@ -0,0 +1,384 @@
1
+ from collections import defaultdict
2
+ from typing import Optional
3
+
4
+ from microEye.hardware.stages.elliptec.baseDevice import (
5
+ BaseDevice,
6
+ DeviceDirection,
7
+ StatusResult,
8
+ )
9
+ from microEye.hardware.stages.elliptec.deviceID import DeviceID
10
+ from microEye.hardware.stages.elliptec.devicePort import DevicePort
11
+ from microEye.hardware.stages.elliptec.deviceStatus import (
12
+ DeviceStatusValues,
13
+ )
14
+ from microEye.hardware.stages.elliptec.messageUpdater import MessageUpdater
15
+ from microEye.qt import QtCore, Signal
16
+
17
+ SHUTTER_POSITION_FACTOR = defaultdict(
18
+ lambda: 1,
19
+ {
20
+ DeviceID.DeviceTypes.Shutter2: 32,
21
+ DeviceID.DeviceTypes.Shutter4: 32,
22
+ DeviceID.DeviceTypes.Shutter6: 19,
23
+ },
24
+ )
25
+ '''
26
+ Dictionary mapping shutter device types to their respective position factors.
27
+
28
+ This factor is used for shutters when performing absolute movements. For example,
29
+ if the position index `i` is 2, the actual position `pos` is calculated as:
30
+ pos = i * factor
31
+
32
+ Where `factor` is the value from this dictionary corresponding to the device type.
33
+ '''
34
+
35
+
36
+ class EllDevice(BaseDevice):
37
+ cleaningUpdate = Signal(bool)
38
+
39
+ def __init__(
40
+ self,
41
+ device_id: DeviceID,
42
+ motor_count: int,
43
+ message_updater: Optional[MessageUpdater] = None,
44
+ ):
45
+ super().__init__(device_id, motor_count, message_updater)
46
+ self._position = 0
47
+ self._home_offset = 0
48
+ self._jogstep_size = 0
49
+
50
+ self._is_jogging = False
51
+ self._is_thermal_locked = False
52
+ self._is_cleaning = False
53
+
54
+ # self._jog_timer = None
55
+
56
+ @property
57
+ def home_offset(self):
58
+ return self._home_offset
59
+
60
+ @property
61
+ def jogstep_size(self):
62
+ return self._jogstep_size
63
+
64
+ @property
65
+ def position(self):
66
+ return self._position
67
+
68
+ def set_home_offset(self, offset: float) -> bool:
69
+ self._update_output(
70
+ 'Set Home Offset to '
71
+ f'{self.deviceInfo.format_position(offset, True, True)}...'
72
+ )
73
+ pulses = self.deviceInfo.unit_to_pulse(offset)
74
+ DevicePort.send_string_i32(self.address, 'so', pulses)
75
+ if not self.wait_for_home_offset('gs', False):
76
+ return False
77
+ self._home_offset = offset
78
+ return True
79
+
80
+ def get_home_offset(self) -> bool:
81
+ self._update_output('Get Home Offset...')
82
+ DevicePort.send_command(self.address, 'go')
83
+ return self.wait_for_home_offset('go', True)
84
+
85
+ def home(self, direction: DeviceDirection = DeviceDirection.Linear) -> bool:
86
+ if self.is_device_busy():
87
+ return False
88
+ self._update_output('Homing device ...')
89
+ DevicePort.send_command(self.address, f'ho{direction.value}')
90
+ return self.wait_for_position(BaseDevice.HOME_TIMEOUT)
91
+
92
+ def home_multiple(
93
+ self, addresses: list[str], direction: DeviceDirection = DeviceDirection.Linear
94
+ ) -> bool:
95
+ if self.is_device_busy():
96
+ return False
97
+ if not addresses:
98
+ return self.home(direction)
99
+ self.set_to_group_address(addresses)
100
+ self._update_output('Homing device ...')
101
+ DevicePort.send_command(self.address, f'ho{direction.value}')
102
+ return self.wait_for_positions(addresses)
103
+
104
+ def set_jogstep_size(self, jogstep_size: float) -> bool:
105
+ self._update_output(
106
+ 'Set Jog Step to ' f'{self.deviceInfo.format_position(jogstep_size)}...'
107
+ )
108
+ pulses = self.deviceInfo.unit_to_pulse(jogstep_size)
109
+ DevicePort.send_string_i32(self.address, 'sj', pulses)
110
+ if not self.wait_for_jogstep_size('gs', False):
111
+ return False
112
+ self._jogstep_size = jogstep_size
113
+
114
+ return True
115
+
116
+ def get_jogstep_size(self) -> bool:
117
+ self._update_output('Get Jogstep Size...')
118
+ DevicePort.send_command(self.address, 'gj')
119
+ return self.wait_for_jogstep_size('gj', True)
120
+
121
+ def start_jogging(self, command: str, message: str):
122
+ self._is_jogging = True
123
+ self._update_output(message)
124
+ DevicePort.send_command(self.address, command)
125
+
126
+ QtCore.QTimer.singleShot(100, self.jog_time_update)
127
+
128
+ def jog_time_update(self):
129
+ if self._is_jogging:
130
+ self.get_position()
131
+ QtCore.QTimer.singleShot(500, self.jog_time_update)
132
+
133
+ def jog_stop_internal(self):
134
+ self._is_jogging = False
135
+
136
+ def jog_forward_start(self) -> bool:
137
+ if self.is_device_busy() or self._is_jogging:
138
+ return False
139
+ self.start_jogging('fw', 'Jog Forward Start')
140
+ return True
141
+
142
+ def jog_backward_start(self) -> bool:
143
+ if self.is_device_busy() or self._is_jogging:
144
+ return False
145
+ self.start_jogging('bw', 'Jog Backward Start')
146
+ return True
147
+
148
+ def jog_stop(self) -> bool:
149
+ if not self._is_jogging:
150
+ return False
151
+ self.jog_stop_internal()
152
+ self._update_output('Jog Stop')
153
+ DevicePort.send_command(self.address, 'ms')
154
+ return self.get_position()
155
+
156
+ def jog_forward(self) -> bool:
157
+ if self.is_device_busy():
158
+ return False
159
+ value = self.deviceInfo.format_position(self._jogstep_size, True, True)
160
+ self._update_output(f'Jog Forward {value}')
161
+ DevicePort.send_command(self.address, 'fw')
162
+ return self.wait_for_position(BaseDevice.MOVE_TIMEOUT)
163
+
164
+ def jog_backward(self) -> bool:
165
+ if self.is_device_busy():
166
+ return False
167
+ value = self.deviceInfo.format_position(self._jogstep_size, True, True)
168
+ self._update_output(f'Jog Backward {value}')
169
+ DevicePort.send_command(self.address, 'bw')
170
+ return self.wait_for_position(BaseDevice.MOVE_TIMEOUT)
171
+
172
+ def get_position(self) -> bool:
173
+ self._update_output('Get Positions...')
174
+ DevicePort.send_command(self.address, 'gp')
175
+ return self.wait_for_position(BaseDevice.MOVE_TIMEOUT)
176
+
177
+ def move_absolute(self, position: float) -> bool:
178
+ if self.is_device_busy():
179
+ return False
180
+ value = self.deviceInfo.format_position(position, True, True)
181
+ self._update_output(f'Move device to {value}...')
182
+ pulses = self.deviceInfo.unit_to_pulse(position)
183
+ DevicePort.send_string_i32(self.address, 'ma', pulses)
184
+ return self.wait_for_position(BaseDevice.MOVE_TIMEOUT)
185
+
186
+ def move_relative(self, step: float) -> bool:
187
+ if self.is_device_busy():
188
+ return False
189
+ self._update_output(
190
+ f'Move device by {self.deviceInfo.format_position(step, True, True)}...'
191
+ )
192
+ pulses = self.deviceInfo.unit_to_pulse(step)
193
+ if pulses == 0:
194
+ return True
195
+ DevicePort.send_string_i32(self.address, 'mr', pulses)
196
+ return self.wait_for_position(BaseDevice.MOVE_TIMEOUT)
197
+
198
+ def is_device_busy(self) -> bool:
199
+ return self.test_thermal_initialized() or self.test_cleaning_state()
200
+
201
+ def test_thermal_initialized(self) -> bool:
202
+ if self._is_thermal_locked:
203
+ self._update_output('Thermal Lockout')
204
+ return True
205
+ return False
206
+
207
+ def test_cleaning_state(self) -> bool:
208
+ if self._is_cleaning:
209
+ self._update_output('Cleaning, Please wait...')
210
+ return True
211
+ return False
212
+
213
+ def set_cleaning_state(self, is_cleaning: bool):
214
+ self._is_cleaning = is_cleaning
215
+ self.cleaningUpdate.emit(self._is_cleaning)
216
+
217
+ def send_clean_machine(self) -> bool:
218
+ if self.is_device_busy():
219
+ return False
220
+ self.set_cleaning_state(True)
221
+ self._update_output('Performing clean mechanics...')
222
+ DevicePort.send_command(self.address, 'cm')
223
+ retval = self.wait_for_cleaning()
224
+ self.set_cleaning_state(False)
225
+ return retval
226
+
227
+ def send_clean_and_optimize(self) -> bool:
228
+ if self.is_device_busy():
229
+ return False
230
+ self.set_cleaning_state(True)
231
+ self._update_output('Performing clean & optimize...')
232
+ DevicePort.send_command(self.address, 'om')
233
+ retval = self.wait_for_cleaning()
234
+ self.set_cleaning_state(False)
235
+ return retval
236
+
237
+ def send_stop_cleaning(self) -> bool:
238
+ if not self._is_cleaning:
239
+ return False
240
+ self._update_output('Stop cleaning...')
241
+ DevicePort.send_command(self.address, 'st')
242
+ self.set_sleep_delay_counter(2)
243
+ DevicePort.send_command(self.address, 'gs')
244
+ return True
245
+
246
+ def set_to_group_address(self, addresses: list[str]) -> bool:
247
+ for address in addresses:
248
+ if address != self.address:
249
+ self._update_output(f'Set GroupAddress {address}->{self.address}...')
250
+ DevicePort.send_string_b(address, 'ga', ord(self.address))
251
+ if not self._wait_for_status(self.address):
252
+ return False
253
+ return True
254
+
255
+ def process_device_status(
256
+ self, device_status: DeviceStatusValues, resend_cmd: str, getter: bool
257
+ ) -> StatusResult:
258
+ if device_status == DeviceStatusValues.Busy:
259
+ self._update_device_status(device_status)
260
+ if getter:
261
+ DevicePort.send_command(self.address, resend_cmd)
262
+ return StatusResult.Busy
263
+ elif device_status == DeviceStatusValues.OK:
264
+ self._is_thermal_locked = False
265
+ self._update_device_status(device_status)
266
+ if getter:
267
+ DevicePort.send_command(self.address, resend_cmd)
268
+ return StatusResult.OK
269
+ elif device_status == DeviceStatusValues.ThermalError:
270
+ self.jog_stop_internal()
271
+ self._is_thermal_locked = True
272
+ self._update_device_status(device_status)
273
+ QtCore.QThread.msleep(2000)
274
+ DevicePort.send_command(self.address, 'gs')
275
+ elif device_status == DeviceStatusValues.ModuleIsolated:
276
+ self._update_device_status(device_status)
277
+ return StatusResult.Fail
278
+ else:
279
+ self._update_device_status(device_status)
280
+ QtCore.QThread.msleep(500)
281
+ DevicePort.send_command(self.address, resend_cmd)
282
+ return StatusResult.Error
283
+
284
+ def wait_for_position(self, ms_timeout: int) -> bool:
285
+ temp_to = ms_timeout
286
+ if self.deviceInfo.DeviceType == DeviceID.DeviceTypes.Actuator:
287
+ temp_to = 10000
288
+ return self.wait_for_positions([self.address], temp_to)
289
+
290
+ def wait_for_positions(self, addresses: list[str], ms_timeout: int = 30000) -> bool:
291
+ temp_to = ms_timeout
292
+ try:
293
+ is_completed_list = {c: False for c in addresses}
294
+ responses = ['GS', 'PO']
295
+ counter = 10 * len(addresses)
296
+
297
+ while True:
298
+ if self.deviceInfo.DeviceType == DeviceID.DeviceTypes.Actuator:
299
+ temp_to = 10000
300
+
301
+ msg = DevicePort.wait_for_response(addresses, responses, temp_to)
302
+ if msg:
303
+ return_value, counter = self._test_status(msg, 'gp', True, counter)
304
+ if return_value:
305
+ return return_value
306
+
307
+ address = msg[0]
308
+ if msg[1:3] == 'PO' and address in addresses:
309
+ if len(msg) != 11:
310
+ return False
311
+ self._position = self.deviceInfo.pulse_to_unit(
312
+ int.from_bytes(
313
+ bytes.fromhex(msg[3:]), byteorder='big', signed=True
314
+ )
315
+ )
316
+ self._update_parameter(
317
+ MessageUpdater.UpdateTypes.Position, address, self._position
318
+ )
319
+ is_completed_list[address] = True
320
+ if all(is_completed_list.values()):
321
+ return True
322
+ except Exception as ex:
323
+ self._update_output(f'Get Device Status: {str(ex)}', True)
324
+ return False
325
+
326
+ def wait_for_home_offset(
327
+ self, cmd: str, getter: bool, ms_timeout: int = 30000
328
+ ) -> bool:
329
+ try:
330
+ responses = ['GS', 'HO']
331
+ counter = 10
332
+ while True:
333
+ msg = DevicePort.wait_for_response(self.address, responses, ms_timeout)
334
+ if msg:
335
+ return_value, counter = self._test_status(msg, cmd, getter, counter)
336
+ if return_value:
337
+ return return_value
338
+ if msg[1:3] == 'HO':
339
+ if len(msg) != 11:
340
+ return False
341
+ self._home_offset = self.deviceInfo.pulse_to_unit(
342
+ int.from_bytes(
343
+ bytes.fromhex(msg[3:]), byteorder='big', signed=True
344
+ )
345
+ )
346
+ self._update_parameter(
347
+ MessageUpdater.UpdateTypes.HomeOffset,
348
+ self.address,
349
+ self._home_offset,
350
+ )
351
+ return True
352
+ except Exception as ex:
353
+ self._update_output(f'Get Device Status: {str(ex)}', True)
354
+ return False
355
+
356
+ def wait_for_jogstep_size(
357
+ self, cmd: str, getter: bool, ms_timeout: int = 30000
358
+ ) -> bool:
359
+ try:
360
+ responses = ['GS', 'GJ']
361
+ counter = 10
362
+ while True:
363
+ msg = DevicePort.wait_for_response(self.address, responses, ms_timeout)
364
+ if msg:
365
+ return_value, counter = self._test_status(msg, cmd, getter, counter)
366
+ if return_value:
367
+ return return_value
368
+ if msg[1:3] == 'GJ':
369
+ if len(msg) != 11:
370
+ return False
371
+ self._jogstep_size = self.deviceInfo.pulse_to_unit(
372
+ int.from_bytes(
373
+ bytes.fromhex(msg[3:]), byteorder='big', signed=True
374
+ )
375
+ )
376
+ self._update_parameter(
377
+ MessageUpdater.UpdateTypes.JogstepSize,
378
+ self.address,
379
+ self._jogstep_size,
380
+ )
381
+ return True
382
+ except Exception as ex:
383
+ self._update_output(f'Get Device Status: {str(ex)}', True)
384
+ return False