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,212 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DeviceID:
|
|
6
|
+
class DeviceTypes(Enum):
|
|
7
|
+
'''Values that represent Elliptec Device Types.'''
|
|
8
|
+
|
|
9
|
+
Paddle = 3
|
|
10
|
+
'''OBSOLETE (ELL3)'''
|
|
11
|
+
Rotator = 4
|
|
12
|
+
'''OBSOLETE (ELL4)'''
|
|
13
|
+
Actuator = 5
|
|
14
|
+
'''OBSOLETE (ELL5)'''
|
|
15
|
+
Shutter2 = 6
|
|
16
|
+
'''An Elliptec Shutter/Slider SM1 x 2 (ELL6).'''
|
|
17
|
+
LinearStage25mm = 7
|
|
18
|
+
'''OBSOLETE: An Elliptec Linear Stage (ELL7).'''
|
|
19
|
+
RotaryStage8 = 8
|
|
20
|
+
'''OBSOLETE: An Elliptec Rotary Stage (ELL8).'''
|
|
21
|
+
Shutter4 = 9
|
|
22
|
+
'''An Elliptec Shutter/Slider SM1 x 4 (ELL9).'''
|
|
23
|
+
LinearStage60mm_10 = 10
|
|
24
|
+
'''OBSOLETE: An Elliptec Linear Stage (ELL10).'''
|
|
25
|
+
Shutter6 = 12
|
|
26
|
+
'''An Elliptec Shutter/Slider SM05 x 6 (ELL12).'''
|
|
27
|
+
OpticsRotator = 14
|
|
28
|
+
'''An Elliptec Optics Rotator (ELL14).'''
|
|
29
|
+
MotorizedIris = 15
|
|
30
|
+
'''An Elliptec Motorized Iris (ELL15).'''
|
|
31
|
+
LinearStage28mm = 17
|
|
32
|
+
'''An Elliptec 28mm Linear Stage (ELL17).'''
|
|
33
|
+
RotaryStage18 = 18
|
|
34
|
+
'''An Elliptec Rotary Stage (ELL18).'''
|
|
35
|
+
LinearStage60mm = 20
|
|
36
|
+
'''An Elliptec 60mm Linear Stage (ELL20).'''
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def string_value(self):
|
|
40
|
+
values = {
|
|
41
|
+
3: 'Paddle (OBSOLETE)',
|
|
42
|
+
4: 'Rotator (OBSOLETE)',
|
|
43
|
+
5: 'Actuator (OBSOLETE)',
|
|
44
|
+
6: 'Slider SM1 x 2',
|
|
45
|
+
7: 'Linear Stage 25mm (OBSOLETE)',
|
|
46
|
+
8: 'Rotary Stage (OBSOLETE)',
|
|
47
|
+
9: 'Slider SM1 x 4',
|
|
48
|
+
10: 'Linear Stage 60mm (OBSOLETE)',
|
|
49
|
+
12: 'Slider SM05 x 6',
|
|
50
|
+
14: 'Optics Rotator',
|
|
51
|
+
15: 'Motorized Iris',
|
|
52
|
+
17: 'Linear Stage 28mm',
|
|
53
|
+
18: 'Rotary Stage',
|
|
54
|
+
20: 'Linear Stage 60mm',
|
|
55
|
+
}
|
|
56
|
+
return values[self.value]
|
|
57
|
+
|
|
58
|
+
class UnitTypes(Enum):
|
|
59
|
+
'''Values that represent units in the Elliptec devices.'''
|
|
60
|
+
|
|
61
|
+
MM = 'mm'
|
|
62
|
+
'''An enum constant representing the millimetres option.'''
|
|
63
|
+
Inches = 'in'
|
|
64
|
+
'''An enum constant representing the inches option.'''
|
|
65
|
+
Degrees = 'deg'
|
|
66
|
+
'''An enum constant representing the degrees option.'''
|
|
67
|
+
|
|
68
|
+
def __init__(self, id_string=None):
|
|
69
|
+
self._unit_factor = 0
|
|
70
|
+
self._unit_type = None
|
|
71
|
+
self.FormatStr = ''
|
|
72
|
+
self.Units = ''
|
|
73
|
+
|
|
74
|
+
if id_string:
|
|
75
|
+
self._deserialize(id_string)
|
|
76
|
+
|
|
77
|
+
def _deserialize(self, id_string):
|
|
78
|
+
device_info_format = 'c2s2s8s4s2s2s4s8s'
|
|
79
|
+
device_info = struct.unpack(device_info_format, id_string.encode())
|
|
80
|
+
|
|
81
|
+
self.Address = device_info[0].decode()
|
|
82
|
+
self.SerialNo = device_info[3].decode()
|
|
83
|
+
self.Year = int(device_info[4])
|
|
84
|
+
self.Firmware = float('.'.join(device_info[5].decode()))
|
|
85
|
+
hw = int(device_info[6], 16)
|
|
86
|
+
self.Hardware = hw & 0x7F
|
|
87
|
+
self.Imperial = bool((hw >> 7) & 1)
|
|
88
|
+
self.Travel = int(device_info[7], 16)
|
|
89
|
+
self.PulsePerPosition = int(device_info[8], 16)
|
|
90
|
+
self.DeviceType = DeviceID.DeviceTypes(int(device_info[2], 16))
|
|
91
|
+
|
|
92
|
+
self._set_motor_count()
|
|
93
|
+
self._set_unit_info()
|
|
94
|
+
|
|
95
|
+
def _set_motor_count(self):
|
|
96
|
+
device_type = self.DeviceType
|
|
97
|
+
if device_type in {
|
|
98
|
+
DeviceID.DeviceTypes.Shutter2,
|
|
99
|
+
DeviceID.DeviceTypes.Actuator,
|
|
100
|
+
}:
|
|
101
|
+
self.MotorCount = 1
|
|
102
|
+
elif device_type in {
|
|
103
|
+
DeviceID.DeviceTypes.Shutter4,
|
|
104
|
+
DeviceID.DeviceTypes.Shutter6,
|
|
105
|
+
DeviceID.DeviceTypes.Rotator,
|
|
106
|
+
DeviceID.DeviceTypes.RotaryStage8,
|
|
107
|
+
DeviceID.DeviceTypes.LinearStage25mm,
|
|
108
|
+
DeviceID.DeviceTypes.LinearStage28mm,
|
|
109
|
+
DeviceID.DeviceTypes.OpticsRotator,
|
|
110
|
+
DeviceID.DeviceTypes.MotorizedIris,
|
|
111
|
+
DeviceID.DeviceTypes.RotaryStage18,
|
|
112
|
+
DeviceID.DeviceTypes.LinearStage60mm_10,
|
|
113
|
+
DeviceID.DeviceTypes.LinearStage60mm,
|
|
114
|
+
}:
|
|
115
|
+
self.MotorCount = 2
|
|
116
|
+
elif device_type == DeviceID.DeviceTypes.Paddle:
|
|
117
|
+
self.MotorCount = 3
|
|
118
|
+
else:
|
|
119
|
+
self.MotorCount = 1
|
|
120
|
+
|
|
121
|
+
def _set_unit_info(self):
|
|
122
|
+
device_type = self.DeviceType
|
|
123
|
+
if device_type == DeviceID.DeviceTypes.Paddle:
|
|
124
|
+
self._unit_factor = (self.PulsePerPosition - 1) / self.Travel
|
|
125
|
+
self._unit_type = DeviceID.UnitTypes.Degrees
|
|
126
|
+
self.FormatStr = '{:.3f}'
|
|
127
|
+
elif device_type in {
|
|
128
|
+
DeviceID.DeviceTypes.Rotator,
|
|
129
|
+
DeviceID.DeviceTypes.OpticsRotator,
|
|
130
|
+
DeviceID.DeviceTypes.RotaryStage8,
|
|
131
|
+
DeviceID.DeviceTypes.RotaryStage18,
|
|
132
|
+
}:
|
|
133
|
+
self._unit_factor = self.PulsePerPosition / 360.0
|
|
134
|
+
self._unit_type = DeviceID.UnitTypes.Degrees
|
|
135
|
+
self.FormatStr = '{:.3f}'
|
|
136
|
+
elif device_type == DeviceID.DeviceTypes.MotorizedIris:
|
|
137
|
+
if self.Imperial:
|
|
138
|
+
self.Travel = self._micron_to_mm(self.Travel)
|
|
139
|
+
self._unit_type = DeviceID.UnitTypes.Inches
|
|
140
|
+
self._unit_factor = 25.4 * self.PulsePerPosition
|
|
141
|
+
self.FormatStr = '{:.4f}'
|
|
142
|
+
else:
|
|
143
|
+
self.Travel = self._micron_to_mm(self.Travel)
|
|
144
|
+
self._unit_type = DeviceID.UnitTypes.MM
|
|
145
|
+
self._unit_factor = self.PulsePerPosition
|
|
146
|
+
self.FormatStr = '{:.2f}'
|
|
147
|
+
else:
|
|
148
|
+
if self.Imperial:
|
|
149
|
+
self._unit_type = DeviceID.UnitTypes.Inches
|
|
150
|
+
self._unit_factor = 25.4 * self.PulsePerPosition
|
|
151
|
+
self.FormatStr = '{:.4f}'
|
|
152
|
+
else:
|
|
153
|
+
self._unit_type = DeviceID.UnitTypes.MM
|
|
154
|
+
self._unit_factor = self.PulsePerPosition
|
|
155
|
+
self.FormatStr = '{:.3f}'
|
|
156
|
+
|
|
157
|
+
self.Units = self._unit_type.value
|
|
158
|
+
|
|
159
|
+
def pulse_to_unit(self, pulses):
|
|
160
|
+
if self._unit_factor == 0:
|
|
161
|
+
return pulses
|
|
162
|
+
return pulses / self._unit_factor
|
|
163
|
+
|
|
164
|
+
def unit_to_pulse(self, units):
|
|
165
|
+
if self._unit_factor == 0:
|
|
166
|
+
return int(units)
|
|
167
|
+
return int(round(units * self._unit_factor))
|
|
168
|
+
|
|
169
|
+
def _micron_to_mm(self, micron):
|
|
170
|
+
return micron / 1000.0
|
|
171
|
+
|
|
172
|
+
def format_position(self, position, show_units=False, show_space=False):
|
|
173
|
+
output = self.FormatStr.format(position)
|
|
174
|
+
if show_units:
|
|
175
|
+
if show_space:
|
|
176
|
+
output += ' '
|
|
177
|
+
output += self.Units
|
|
178
|
+
return output
|
|
179
|
+
|
|
180
|
+
def format_specific_position(
|
|
181
|
+
self, position, decimals, show_units=False, show_space=False
|
|
182
|
+
):
|
|
183
|
+
format_str = '{:.' + str(decimals) + 'f}'
|
|
184
|
+
output = format_str.format(position)
|
|
185
|
+
if show_units:
|
|
186
|
+
if show_space:
|
|
187
|
+
output += ' '
|
|
188
|
+
output += self.Units
|
|
189
|
+
return output
|
|
190
|
+
|
|
191
|
+
def description(self):
|
|
192
|
+
desc = [
|
|
193
|
+
f'Address: {self.Address}',
|
|
194
|
+
f'Serial Number: {self.SerialNo}',
|
|
195
|
+
f'Device Type: {self.DeviceType.name}',
|
|
196
|
+
f'Motors: {self.MotorCount}',
|
|
197
|
+
f'Firmware: {self.Firmware}',
|
|
198
|
+
f'Hardware: {self.Hardware}',
|
|
199
|
+
f"Variant: {'Imperial' if self.Imperial else 'Metric'}",
|
|
200
|
+
f'Year: {self.Year}',
|
|
201
|
+
]
|
|
202
|
+
if self._unit_type == DeviceID.UnitTypes.MM:
|
|
203
|
+
desc.append(f'Travel: {self.Travel} {self.Units}')
|
|
204
|
+
desc.append(f'Pulses Per {self.Units}: {self.unit_to_pulse(1.0)}')
|
|
205
|
+
elif self._unit_type == DeviceID.UnitTypes.Inches:
|
|
206
|
+
desc.append(f'Travel: {self._micron_to_mm(self.Travel):.3f} {self.Units}')
|
|
207
|
+
desc.append(f'Pulses Per {self.Units}: {self.unit_to_pulse(1.0)}')
|
|
208
|
+
elif self._unit_type == DeviceID.UnitTypes.Degrees:
|
|
209
|
+
desc.append(f'Travel: {self.Travel} {self.Units}')
|
|
210
|
+
desc.append(f'Pulses Per {self.Units}: {self.unit_to_pulse(1.0)}')
|
|
211
|
+
|
|
212
|
+
return desc
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import struct
|
|
3
|
+
from collections import deque
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Optional, Union
|
|
6
|
+
|
|
7
|
+
from microEye.qt import QDateTime, QtCore, QtSerialPort, Signal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ELLException(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DevicePort(QtCore.QObject):
|
|
15
|
+
_instance = None
|
|
16
|
+
'''Class variable to hold the single instance'''
|
|
17
|
+
_responses: Optional[deque[str]] = None
|
|
18
|
+
'''Class variable to hold the responses'''
|
|
19
|
+
|
|
20
|
+
dataSent = Signal(str)
|
|
21
|
+
'''Signal emitted when data is sent'''
|
|
22
|
+
dataReceived = Signal(str)
|
|
23
|
+
'''Signal emitted when data is received'''
|
|
24
|
+
logUpdated = Signal(str)
|
|
25
|
+
'''Signal to notify when the TX/RX log is updated'''
|
|
26
|
+
errorOccurred = Signal(str)
|
|
27
|
+
'''Signal for error reporting'''
|
|
28
|
+
|
|
29
|
+
COMMAND_RESPONSE_LENGTHS = {
|
|
30
|
+
'GS': 5, # Example: GetStatus command
|
|
31
|
+
'PO': 11, # Example: Position command
|
|
32
|
+
'IN': 33, # Example: Identify command
|
|
33
|
+
'BS': 5, # Button status
|
|
34
|
+
'BO': 11, # Button position
|
|
35
|
+
'I1': 25,
|
|
36
|
+
'I2': 25,
|
|
37
|
+
'I3': 25,
|
|
38
|
+
'HO': 11,
|
|
39
|
+
'GJ': 11,
|
|
40
|
+
'GV': 5,
|
|
41
|
+
'C1': 525,
|
|
42
|
+
'C2': 525,
|
|
43
|
+
# Add more commands as needed
|
|
44
|
+
}
|
|
45
|
+
'''A dictionary mapping commands to their expected response lengths'''
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def instance(cls, port_name='COM1', baud_rate=9600, max_log_entries=1000):
|
|
49
|
+
'''Class method to get the singleton instance'''
|
|
50
|
+
if cls._instance is None:
|
|
51
|
+
cls._instance = cls(port_name, baud_rate, max_log_entries)
|
|
52
|
+
return cls._instance
|
|
53
|
+
|
|
54
|
+
def __init__(self, port_name='COM1', baud_rate=9600, max_entries=1000):
|
|
55
|
+
"""
|
|
56
|
+
Initialize the Elliptec Communication Layer.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
port_name : str, optional
|
|
61
|
+
The name of the serial port to use (default is 'COM1').
|
|
62
|
+
baud_rate : int, optional
|
|
63
|
+
The baud rate for the serial port (default is 9600).
|
|
64
|
+
max_entries : int, optional
|
|
65
|
+
The maximum number of entries to keep (default is 1000).
|
|
66
|
+
"""
|
|
67
|
+
if DevicePort._instance is not None:
|
|
68
|
+
raise Exception('This class is a singleton!')
|
|
69
|
+
super().__init__()
|
|
70
|
+
|
|
71
|
+
self.serial = QtSerialPort.QSerialPort(None, readyRead=self.rx_piezo)
|
|
72
|
+
self.serial.setBaudRate(baud_rate)
|
|
73
|
+
self.serial.setPortName(port_name)
|
|
74
|
+
self.serial.setParity(QtSerialPort.QSerialPort.Parity.NoParity)
|
|
75
|
+
self.serial.setStopBits(QtSerialPort.QSerialPort.StopBits.OneStop)
|
|
76
|
+
self.serial.setDataBits(QtSerialPort.QSerialPort.DataBits.Data8)
|
|
77
|
+
self.serial.setFlowControl(QtSerialPort.QSerialPort.FlowControl.NoFlowControl)
|
|
78
|
+
self.buffer = ''
|
|
79
|
+
|
|
80
|
+
# New attributes for logging
|
|
81
|
+
DevicePort._responses = deque(maxlen=max_entries)
|
|
82
|
+
|
|
83
|
+
self.logger = logging.getLogger(__name__)
|
|
84
|
+
self.logger.setLevel(logging.DEBUG)
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def open(cls, portName: str = None):
|
|
88
|
+
'''
|
|
89
|
+
Open the serial port.
|
|
90
|
+
'''
|
|
91
|
+
if portName:
|
|
92
|
+
cls.instance().serial.setPortName(portName)
|
|
93
|
+
|
|
94
|
+
if not cls.instance().isOpen():
|
|
95
|
+
cls.instance().serial.open(QtCore.QIODevice.OpenModeFlag.ReadWrite)
|
|
96
|
+
|
|
97
|
+
return cls.instance().isOpen()
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def close(cls):
|
|
101
|
+
'''
|
|
102
|
+
Close the serial port.
|
|
103
|
+
'''
|
|
104
|
+
if cls.instance().isOpen():
|
|
105
|
+
cls.instance().serial.close()
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def isOpen(cls):
|
|
109
|
+
'''
|
|
110
|
+
Check if the serial port is open.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
bool
|
|
115
|
+
True if the serial port is open, False otherwise.
|
|
116
|
+
'''
|
|
117
|
+
return cls.instance().serial.isOpen()
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def portName(cls) -> str:
|
|
121
|
+
'''
|
|
122
|
+
Get the name of the serial port.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
str
|
|
127
|
+
The name of the serial port.
|
|
128
|
+
'''
|
|
129
|
+
return cls.instance().serial.portName()
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def setPortName(cls, name: str):
|
|
133
|
+
'''
|
|
134
|
+
Set the name of the serial port.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
name : str
|
|
139
|
+
The new port name.
|
|
140
|
+
'''
|
|
141
|
+
if not cls.instance().isOpen():
|
|
142
|
+
cls.instance().serial.setPortName(name)
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def baudRate(cls) -> int:
|
|
146
|
+
'''
|
|
147
|
+
Get the current baud rate.
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
int
|
|
152
|
+
The current baud rate.
|
|
153
|
+
'''
|
|
154
|
+
return cls.instance().serial.baudRate()
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def setBaudRate(cls, baudRate: int):
|
|
158
|
+
'''
|
|
159
|
+
Set the baud rate for the serial port.
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
baudRate : int
|
|
164
|
+
The new baud rate.
|
|
165
|
+
'''
|
|
166
|
+
if not cls.instance().isOpen():
|
|
167
|
+
cls.instance().serial.setBaudRate(baudRate)
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def log_communication(cls, direction, message):
|
|
171
|
+
"""
|
|
172
|
+
Log a communication event.
|
|
173
|
+
|
|
174
|
+
Parameters:
|
|
175
|
+
direction (str): Either 'TX' for transmitted or 'RX' for received.
|
|
176
|
+
message (str): The content of the message.
|
|
177
|
+
"""
|
|
178
|
+
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
|
|
179
|
+
log_entry = f'{timestamp} - {direction}: {message}'
|
|
180
|
+
cls.instance().logUpdated.emit(log_entry)
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def write(cls, value: str):
|
|
184
|
+
'''
|
|
185
|
+
Write a value to the serial port.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
value : bytes
|
|
190
|
+
The value to write.
|
|
191
|
+
'''
|
|
192
|
+
if cls.instance().isOpen():
|
|
193
|
+
cls.instance().serial.write(value.encode('utf-8'))
|
|
194
|
+
cls.instance().dataSent.emit(value)
|
|
195
|
+
cls.instance().log_communication('TX', value)
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def addResponse(cls, response: str):
|
|
199
|
+
'''
|
|
200
|
+
Add a response to the response buffer.
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
response : str
|
|
205
|
+
The response to add.
|
|
206
|
+
'''
|
|
207
|
+
cls._responses.append(response)
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def hasResponse(cls, addresses: Optional[list[str]] = None):
|
|
211
|
+
'''
|
|
212
|
+
Check if there is a response in the buffer.
|
|
213
|
+
|
|
214
|
+
Returns
|
|
215
|
+
-------
|
|
216
|
+
bool
|
|
217
|
+
True if there is a response, False otherwise.
|
|
218
|
+
'''
|
|
219
|
+
if (
|
|
220
|
+
addresses is not None
|
|
221
|
+
and isinstance(addresses, list)
|
|
222
|
+
and all(isinstance(address, str) for address in addresses)
|
|
223
|
+
):
|
|
224
|
+
return any(response[0] in addresses for response in cls._responses)
|
|
225
|
+
else:
|
|
226
|
+
return len(cls._responses) > 0
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
def getNextResponse(cls, address: Optional[Union[str, list[str]]] = None):
|
|
230
|
+
'''
|
|
231
|
+
Get the next response from the buffer.
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
address : str or list[str], optional
|
|
236
|
+
The address of the response to get
|
|
237
|
+
(default is None, which gets the next response).
|
|
238
|
+
|
|
239
|
+
Returns
|
|
240
|
+
-------
|
|
241
|
+
str
|
|
242
|
+
The next response from the buffer.
|
|
243
|
+
'''
|
|
244
|
+
if address is not None:
|
|
245
|
+
if isinstance(address, str):
|
|
246
|
+
address = [address]
|
|
247
|
+
|
|
248
|
+
if not cls.hasResponse(address):
|
|
249
|
+
raise ELLException('No Responses available')
|
|
250
|
+
|
|
251
|
+
if address is None:
|
|
252
|
+
return cls._responses.popleft()
|
|
253
|
+
elif isinstance(address, list) and all(isinstance(add, str) for add in address):
|
|
254
|
+
for i, response in enumerate(cls._responses):
|
|
255
|
+
if response[0] in address:
|
|
256
|
+
result = cls._responses[i]
|
|
257
|
+
del cls._responses[i]
|
|
258
|
+
return result
|
|
259
|
+
|
|
260
|
+
raise ELLException('No Responses available for the given address')
|
|
261
|
+
else:
|
|
262
|
+
raise ELLException('Invalid address')
|
|
263
|
+
|
|
264
|
+
@classmethod
|
|
265
|
+
def clearResponses(cls, address: Optional[Union[str, list[str]]] = None):
|
|
266
|
+
'''
|
|
267
|
+
Clear the response buffer.
|
|
268
|
+
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
address : str or list[str], optional
|
|
272
|
+
The address of the response to clear
|
|
273
|
+
(default is None, which clears all responses).
|
|
274
|
+
'''
|
|
275
|
+
if address is not None:
|
|
276
|
+
if isinstance(address, str):
|
|
277
|
+
address = [address]
|
|
278
|
+
|
|
279
|
+
if address is None:
|
|
280
|
+
cls._responses.clear()
|
|
281
|
+
elif isinstance(address, list) and all(isinstance(add, str) for add in address):
|
|
282
|
+
cls._responses = deque(
|
|
283
|
+
response for response in cls._responses if response[0] not in address
|
|
284
|
+
)
|
|
285
|
+
else:
|
|
286
|
+
raise ELLException('Invalid address')
|
|
287
|
+
|
|
288
|
+
def rx_piezo(self):
|
|
289
|
+
'''
|
|
290
|
+
Handle incoming data from the serial port.
|
|
291
|
+
'''
|
|
292
|
+
try:
|
|
293
|
+
self.buffer += str(self.serial.readAll(), encoding='utf8')
|
|
294
|
+
|
|
295
|
+
eol = self.buffer.find('\n')
|
|
296
|
+
while eol >= 0:
|
|
297
|
+
message = self.buffer[: eol + 1].strip(' \r\n')
|
|
298
|
+
self.buffer = self.buffer[eol + 1 :]
|
|
299
|
+
eol = self.buffer.find('\n')
|
|
300
|
+
|
|
301
|
+
if message:
|
|
302
|
+
self.log_communication('RX', message)
|
|
303
|
+
DevicePort.addResponse(message)
|
|
304
|
+
self.dataReceived.emit(message)
|
|
305
|
+
except Exception as e:
|
|
306
|
+
self.logger.error(f'Error in rx_piezo: {str(e)}')
|
|
307
|
+
self.errorOccurred.emit(str(e))
|
|
308
|
+
|
|
309
|
+
@classmethod
|
|
310
|
+
def wait_for_response(
|
|
311
|
+
cls,
|
|
312
|
+
addresses: Union[str, list[str]],
|
|
313
|
+
responses: Union[str, list[str]],
|
|
314
|
+
timeout: int,
|
|
315
|
+
):
|
|
316
|
+
if isinstance(responses, str):
|
|
317
|
+
responses = [responses]
|
|
318
|
+
if isinstance(addresses, str):
|
|
319
|
+
addresses = [addresses]
|
|
320
|
+
|
|
321
|
+
headers = [
|
|
322
|
+
f'{address}{response}' for address in addresses for response in responses
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
loop = QtCore.QEventLoop()
|
|
326
|
+
QtCore.QTimer.singleShot(timeout, loop.quit)
|
|
327
|
+
cls.instance().dataReceived.connect(loop.quit)
|
|
328
|
+
loop.exec()
|
|
329
|
+
cls.instance().dataReceived.disconnect(loop.quit)
|
|
330
|
+
|
|
331
|
+
if cls.hasResponse(addresses):
|
|
332
|
+
message = cls.getNextResponse(addresses)
|
|
333
|
+
if message[:3] in headers:
|
|
334
|
+
return message
|
|
335
|
+
|
|
336
|
+
raise ELLException('Response timeout')
|
|
337
|
+
|
|
338
|
+
@classmethod
|
|
339
|
+
def send_command(
|
|
340
|
+
cls, address: str, command: str, param: str = '', clearResponses=True
|
|
341
|
+
) -> Optional[str]:
|
|
342
|
+
'''
|
|
343
|
+
Send a command to the stage.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
address : str
|
|
348
|
+
The stage address 0-F.
|
|
349
|
+
command : str
|
|
350
|
+
The command to send.
|
|
351
|
+
param : str, optional
|
|
352
|
+
The parameter to send (default is "").
|
|
353
|
+
'''
|
|
354
|
+
try:
|
|
355
|
+
full_command = f'{address}{command}{param}'
|
|
356
|
+
|
|
357
|
+
if clearResponses:
|
|
358
|
+
cls.clearResponses(address)
|
|
359
|
+
|
|
360
|
+
cls.instance().write(full_command)
|
|
361
|
+
except Exception as e:
|
|
362
|
+
cls.instance().logger.error(f'Error in send_command: {str(e)}')
|
|
363
|
+
cls.instance().errorOccurred.emit(str(e))
|
|
364
|
+
|
|
365
|
+
@classmethod
|
|
366
|
+
def sendFreeString(cls, text: str):
|
|
367
|
+
cls.instance().send_command(text, '')
|
|
368
|
+
|
|
369
|
+
@classmethod
|
|
370
|
+
def send_string_i16(cls, address: str, command: str, i: int):
|
|
371
|
+
cls.instance().send_command(address, command, f'{i:04X}')
|
|
372
|
+
|
|
373
|
+
@classmethod
|
|
374
|
+
def send_string_i32(cls, address: str, command: str, i: int):
|
|
375
|
+
cls.instance().send_command(address, command, f'{i:08X}')
|
|
376
|
+
|
|
377
|
+
@classmethod
|
|
378
|
+
def send_string_b(cls, address: str, command: str, b: int):
|
|
379
|
+
cls.instance().send_command(address, command, f'{chr(b)}')
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DeviceStatusValues(Enum):
|
|
6
|
+
'''Values that represent Device Status.'''
|
|
7
|
+
|
|
8
|
+
OK = 0
|
|
9
|
+
CommunicationError = 1
|
|
10
|
+
MechanicalTimeOut = 2
|
|
11
|
+
CommandError = 3
|
|
12
|
+
ValueOutOfRange = 4
|
|
13
|
+
ModuleIsolated = 5
|
|
14
|
+
ModuleOutOfIsolation = 6
|
|
15
|
+
InitializationError = 7
|
|
16
|
+
ThermalError = 8
|
|
17
|
+
Busy = 9
|
|
18
|
+
SensorError = 10
|
|
19
|
+
MotorError = 11
|
|
20
|
+
OutOfRangeError = 12
|
|
21
|
+
OverCurrentError = 13
|
|
22
|
+
GeneralError = 14
|
|
23
|
+
|
|
24
|
+
def __str__(self):
|
|
25
|
+
return {
|
|
26
|
+
0: 'Ok',
|
|
27
|
+
1: 'Communication Error',
|
|
28
|
+
2: 'Mechanical Time Out',
|
|
29
|
+
3: 'Command Error',
|
|
30
|
+
4: 'Value Out Of Range',
|
|
31
|
+
5: 'Module Isolated',
|
|
32
|
+
6: 'Module Out Of Isolation',
|
|
33
|
+
7: 'Initialization Error',
|
|
34
|
+
8: 'Thermal Error',
|
|
35
|
+
9: 'Busy',
|
|
36
|
+
10: 'Sensor Error',
|
|
37
|
+
11: 'Motor Error',
|
|
38
|
+
12: 'Out of range Error',
|
|
39
|
+
13: 'Over current Error',
|
|
40
|
+
14: 'General Error',
|
|
41
|
+
}[self.value]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DeviceStatusStruct:
|
|
45
|
+
'''Structure containing device stage axis parameters.'''
|
|
46
|
+
|
|
47
|
+
FORMAT = 'c2s2s'
|
|
48
|
+
|
|
49
|
+
def __init__(self, address, command, status):
|
|
50
|
+
self.address: str = address
|
|
51
|
+
self.command: str = command
|
|
52
|
+
self.status: int = status
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_bytes(cls, data):
|
|
56
|
+
address, command, status = struct.unpack(cls.FORMAT, data)
|
|
57
|
+
return cls(address.decode('utf-8'), command.decode('utf-8'), int(status, 16))
|
|
58
|
+
|
|
59
|
+
class DeviceStatus:
|
|
60
|
+
def __init__(self, message: str = None) -> None:
|
|
61
|
+
if message is None:
|
|
62
|
+
self.Status = DeviceStatusValues.OK
|
|
63
|
+
else:
|
|
64
|
+
ds = DeviceStatusStruct.from_bytes(message.encode('utf-8'))
|
|
65
|
+
self.Status = DeviceStatusValues(ds.status)
|