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,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
|