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,188 @@
|
|
|
1
|
+
from os import name
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from pyqtgraph.widgets.PlotWidget import PlotWidget
|
|
5
|
+
|
|
6
|
+
from microEye.hardware.port_config import port_config
|
|
7
|
+
from microEye.qt import (
|
|
8
|
+
QApplication,
|
|
9
|
+
QDateTime,
|
|
10
|
+
QMainWindow,
|
|
11
|
+
QtCore,
|
|
12
|
+
QtSerialPort,
|
|
13
|
+
QtWidgets,
|
|
14
|
+
Slot,
|
|
15
|
+
)
|
|
16
|
+
from microEye.utils import StartGUI
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class temperature_monitor(QMainWindow):
|
|
20
|
+
def __init__(self, parent=None) -> None:
|
|
21
|
+
super().__init__(parent)
|
|
22
|
+
|
|
23
|
+
# Define main window layout
|
|
24
|
+
self.main_widget = QtWidgets.QWidget()
|
|
25
|
+
self.main_layout = QtWidgets.QHBoxLayout()
|
|
26
|
+
self.main_widget.setLayout(self.main_layout)
|
|
27
|
+
self.setCentralWidget(self.main_widget)
|
|
28
|
+
|
|
29
|
+
self.temp_log_X = np.arange(100)
|
|
30
|
+
self.temp_log = np.array([20.0] * 100)
|
|
31
|
+
|
|
32
|
+
self.serial = QtSerialPort.QSerialPort(None, readyRead=self.receive)
|
|
33
|
+
self.serial.setBaudRate(115200)
|
|
34
|
+
self.serial.setPortName('COM4')
|
|
35
|
+
|
|
36
|
+
self.labelStyle = {'color': '#FFF', 'font-size': '10pt'}
|
|
37
|
+
self.temp_log_graph = PlotWidget()
|
|
38
|
+
self.temp_log_graph.setLabel('bottom', 'Frame', **self.labelStyle)
|
|
39
|
+
self.temp_log_graph.setLabel('left', 'Temperature [C]', **self.labelStyle)
|
|
40
|
+
self.temp_log_graph_ref = None
|
|
41
|
+
|
|
42
|
+
self.buttons_layout = QtWidgets.QHBoxLayout()
|
|
43
|
+
self.connect_btn = QtWidgets.QPushButton(
|
|
44
|
+
'Connect', clicked=lambda: self.OpenCOM()
|
|
45
|
+
)
|
|
46
|
+
self.disconnect_btn = QtWidgets.QPushButton(
|
|
47
|
+
'Disconnect', clicked=lambda: self.CloseCOM()
|
|
48
|
+
)
|
|
49
|
+
self.config_btn = QtWidgets.QPushButton(
|
|
50
|
+
'Config.', clicked=lambda: self.open_dialog(self.serial)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
self.buttons_layout.addWidget(self.connect_btn)
|
|
54
|
+
self.buttons_layout.addWidget(self.disconnect_btn)
|
|
55
|
+
self.buttons_layout.addWidget(self.config_btn)
|
|
56
|
+
|
|
57
|
+
self.settings_group = QtWidgets.QGroupBox('Settings')
|
|
58
|
+
self.settings_layout = QtWidgets.QFormLayout()
|
|
59
|
+
self.settings_group.setLayout(self.settings_layout)
|
|
60
|
+
self.settings_group.setMinimumWidth(250)
|
|
61
|
+
|
|
62
|
+
self.setT = QtWidgets.QDoubleSpinBox()
|
|
63
|
+
self.setT.setMinimum(-2000)
|
|
64
|
+
self.setT.setMaximum(10000)
|
|
65
|
+
self.setT.setValue(10)
|
|
66
|
+
|
|
67
|
+
self.kP = QtWidgets.QDoubleSpinBox()
|
|
68
|
+
self.kP.setMinimum(-2000)
|
|
69
|
+
self.kP.setMaximum(10000)
|
|
70
|
+
self.kP.setValue(90)
|
|
71
|
+
self.kI = QtWidgets.QDoubleSpinBox()
|
|
72
|
+
self.kI.setMinimum(-2000)
|
|
73
|
+
self.kI.setMaximum(10000)
|
|
74
|
+
self.kI.setValue(0)
|
|
75
|
+
self.kD = QtWidgets.QDoubleSpinBox()
|
|
76
|
+
self.kD.setMinimum(-2000)
|
|
77
|
+
self.kD.setMaximum(10000)
|
|
78
|
+
self.kD.setValue(0)
|
|
79
|
+
|
|
80
|
+
self.send_btn = QtWidgets.QPushButton(
|
|
81
|
+
'Send Settings', clicked=lambda: self.SendCommand()
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
self.settings_layout.addRow(self.connect_btn)
|
|
85
|
+
self.settings_layout.addRow(self.disconnect_btn)
|
|
86
|
+
self.settings_layout.addRow(self.config_btn)
|
|
87
|
+
self.settings_layout.addRow('Set T [C]:', self.setT)
|
|
88
|
+
self.settings_layout.addRow('kP:', self.kP)
|
|
89
|
+
self.settings_layout.addRow('kI:', self.kI)
|
|
90
|
+
self.settings_layout.addRow('kD:', self.kD)
|
|
91
|
+
self.settings_layout.addRow(self.send_btn)
|
|
92
|
+
|
|
93
|
+
self.main_layout.addWidget(self.settings_group)
|
|
94
|
+
self.main_layout.addWidget(self.temp_log_graph, 3)
|
|
95
|
+
|
|
96
|
+
# Statues Bar Timer
|
|
97
|
+
self.timer = QtCore.QTimer()
|
|
98
|
+
self.timer.setInterval(10)
|
|
99
|
+
self.timer.timeout.connect(self.update)
|
|
100
|
+
self.timer.start()
|
|
101
|
+
|
|
102
|
+
self.show()
|
|
103
|
+
self.center()
|
|
104
|
+
|
|
105
|
+
def center(self):
|
|
106
|
+
'''Centers the window within the screen.'''
|
|
107
|
+
qtRectangle = self.frameGeometry()
|
|
108
|
+
centerPoint = QApplication.primaryScreen().availableGeometry().center()
|
|
109
|
+
qtRectangle.moveCenter(centerPoint)
|
|
110
|
+
self.move(qtRectangle.topLeft())
|
|
111
|
+
|
|
112
|
+
def receive(self):
|
|
113
|
+
while self.serial.canReadLine():
|
|
114
|
+
line = self.serial.readLine().data().decode('utf-8')
|
|
115
|
+
temp = line.rstrip('\r\n')
|
|
116
|
+
try:
|
|
117
|
+
temp = float(temp)
|
|
118
|
+
self.temp_log = np.roll(self.temp_log, -1)
|
|
119
|
+
self.temp_log[-1] = temp
|
|
120
|
+
except ValueError:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
def update(self):
|
|
124
|
+
self.statusBar().showMessage(
|
|
125
|
+
'Time: ' + QDateTime.currentDateTime().toString('hh:mm:ss,zzz')
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if self.temp_log_graph_ref is None:
|
|
129
|
+
# create plot reference when None
|
|
130
|
+
self.temp_log_graph_ref = self.temp_log_graph.plot(
|
|
131
|
+
self.temp_log_X, self.temp_log
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
# use the plot reference to update the data for that line.
|
|
135
|
+
self.temp_log_graph_ref.setData(self.temp_log_X, self.temp_log)
|
|
136
|
+
|
|
137
|
+
@Slot()
|
|
138
|
+
def open_dialog(self, serial: QtSerialPort.QSerialPort):
|
|
139
|
+
'''Opens a port config dialog for the provided serial port.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
serial : QSerialPort
|
|
144
|
+
the serial port to be configured.
|
|
145
|
+
'''
|
|
146
|
+
dialog = port_config()
|
|
147
|
+
if not serial.isOpen():
|
|
148
|
+
if dialog.exec():
|
|
149
|
+
portname, baudrate = dialog.get_results()
|
|
150
|
+
serial.setPortName(portname)
|
|
151
|
+
serial.setBaudRate(baudrate)
|
|
152
|
+
|
|
153
|
+
def OpenCOM(self):
|
|
154
|
+
'''Opens the serial port and initializes the combiner.'''
|
|
155
|
+
if not self.serial.isOpen():
|
|
156
|
+
self.serial.open(
|
|
157
|
+
QtCore.QIODevice.OpenModeFlag.ReadWrite)
|
|
158
|
+
self.serial.flush()
|
|
159
|
+
|
|
160
|
+
def CloseCOM(self):
|
|
161
|
+
'''Closes the serial port.'''
|
|
162
|
+
self.serial.close()
|
|
163
|
+
|
|
164
|
+
def SendCommand(self):
|
|
165
|
+
if self.serial.isOpen():
|
|
166
|
+
data = dict()
|
|
167
|
+
data['P'] = self.kP.value()
|
|
168
|
+
data['I'] = self.kI.value()
|
|
169
|
+
data['D'] = self.kD.value()
|
|
170
|
+
data['T'] = self.setT.value()
|
|
171
|
+
command = str(data) + '\r'
|
|
172
|
+
self.serial.write(command.encode('utf-8'))
|
|
173
|
+
|
|
174
|
+
def StartGUI():
|
|
175
|
+
'''Initializes a new QApplication and temperature_monitor.
|
|
176
|
+
|
|
177
|
+
Use
|
|
178
|
+
-------
|
|
179
|
+
app, window = temperature_monitor.StartGUI()
|
|
180
|
+
|
|
181
|
+
app.exec()
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
tuple (QApplication, temperature_monitor)
|
|
186
|
+
Returns a tuple with QApp and temperature_monitor main window.
|
|
187
|
+
'''
|
|
188
|
+
return StartGUI(temperature_monitor)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from microEye.qt import Qt, QtSerialPort, QtWidgets
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class port_config(QtWidgets.QDialog):
|
|
5
|
+
'''A dialog for setting the serial port config | Inherits QDialog'''
|
|
6
|
+
|
|
7
|
+
def __init__(self, parent=None, title='Serial Port Config.', **kwargs):
|
|
8
|
+
super().__init__(parent)
|
|
9
|
+
|
|
10
|
+
self.setWindowTitle(title)
|
|
11
|
+
self.portname_comboBox = QtWidgets.QComboBox() # port name combobox
|
|
12
|
+
self.baudrate_comboBox = QtWidgets.QComboBox() # baudrate combobox
|
|
13
|
+
|
|
14
|
+
# adding available serial ports
|
|
15
|
+
for info in QtSerialPort.QSerialPortInfo.availablePorts():
|
|
16
|
+
self.portname_comboBox.addItem(info.portName())
|
|
17
|
+
|
|
18
|
+
self.portname_comboBox.setCurrentText(
|
|
19
|
+
kwargs.get(
|
|
20
|
+
'portname', QtSerialPort.QSerialPortInfo.availablePorts()[0].portName()
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# adding default baudrates (default 115200)
|
|
25
|
+
for baudrate in QtSerialPort.QSerialPortInfo.standardBaudRates():
|
|
26
|
+
# if baudrate == 115200:
|
|
27
|
+
self.baudrate_comboBox.addItem(str(baudrate), baudrate)
|
|
28
|
+
|
|
29
|
+
self.baudrate_comboBox.setCurrentText(kwargs.get('baudrate', '115200'))
|
|
30
|
+
|
|
31
|
+
# dialog buttons
|
|
32
|
+
buttonBox = QtWidgets.QDialogButtonBox()
|
|
33
|
+
buttonBox.setOrientation(Qt.Orientation.Horizontal)
|
|
34
|
+
buttonBox.setStandardButtons(
|
|
35
|
+
QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok
|
|
36
|
+
)
|
|
37
|
+
buttonBox.accepted.connect(self.accept)
|
|
38
|
+
buttonBox.rejected.connect(self.reject)
|
|
39
|
+
|
|
40
|
+
# dialog layout
|
|
41
|
+
lay = QtWidgets.QFormLayout(self)
|
|
42
|
+
lay.addRow('Port Name:', self.portname_comboBox)
|
|
43
|
+
lay.addRow('BaudRate:', self.baudrate_comboBox)
|
|
44
|
+
lay.addRow(buttonBox)
|
|
45
|
+
self.setFixedSize(self.sizeHint().width() + 50, self.sizeHint().height())
|
|
46
|
+
self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowTitleHint)
|
|
47
|
+
|
|
48
|
+
def get_results(self):
|
|
49
|
+
'''Get the selected serial port config.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
tuple (str, int)
|
|
54
|
+
the selected port and baudrate respectively.
|
|
55
|
+
'''
|
|
56
|
+
return (
|
|
57
|
+
self.portname_comboBox.currentText(),
|
|
58
|
+
self.baudrate_comboBox.currentData(),
|
|
59
|
+
)
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import re
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
import weakref
|
|
6
|
+
from typing import Any, Callable
|
|
7
|
+
|
|
8
|
+
from pyqtgraph.parametertree import Parameter
|
|
9
|
+
from pyqtgraph.parametertree.parameterTypes import ActionParameter
|
|
10
|
+
|
|
11
|
+
from microEye.hardware.cams import Camera_Panel
|
|
12
|
+
from microEye.utils.parameter_tree import Tree
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WeakObjects:
|
|
16
|
+
OBJECTS = weakref.WeakSet()
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def addObject(cls, object):
|
|
20
|
+
cls.OBJECTS.add(object)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def removeObject(cls, object):
|
|
24
|
+
cls.OBJECTS.discard(object)
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def getObject(cls, name: str):
|
|
28
|
+
for ref in cls.OBJECTS:
|
|
29
|
+
if str(ref) == name:
|
|
30
|
+
return ref
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def getObjectNames(cls):
|
|
35
|
+
return [str(obj) for obj in cls.OBJECTS]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BaseAction:
|
|
39
|
+
'''
|
|
40
|
+
Base class for all actions in the editor.
|
|
41
|
+
|
|
42
|
+
Attributes
|
|
43
|
+
----------
|
|
44
|
+
id_counter : int
|
|
45
|
+
Global counter for action IDs.
|
|
46
|
+
NAME : str
|
|
47
|
+
The name of the action type.
|
|
48
|
+
id : int
|
|
49
|
+
The unique identifier for each action instance.
|
|
50
|
+
name : str
|
|
51
|
+
The name of the action instance.
|
|
52
|
+
'''
|
|
53
|
+
|
|
54
|
+
id_counter = 1 # Global counter for action IDs
|
|
55
|
+
NAME = 'Base Action'
|
|
56
|
+
|
|
57
|
+
def __init__(self):
|
|
58
|
+
'''
|
|
59
|
+
Initialize a new BaseAction instance.
|
|
60
|
+
'''
|
|
61
|
+
self.id = BaseAction.id_counter
|
|
62
|
+
BaseAction.id_counter += 1
|
|
63
|
+
self.name = self.__class__.__name__
|
|
64
|
+
|
|
65
|
+
def output(self, text: str, **kwargs):
|
|
66
|
+
output = kwargs.get('output', None)
|
|
67
|
+
if output is not None:
|
|
68
|
+
output.emit(text, kwargs.get('level', 0))
|
|
69
|
+
|
|
70
|
+
def execute(self, **kwargs):
|
|
71
|
+
'''
|
|
72
|
+
Execute the action. To be implemented by subclasses.
|
|
73
|
+
|
|
74
|
+
Raises
|
|
75
|
+
------
|
|
76
|
+
NotImplementedError
|
|
77
|
+
If subclasses do not implement the execute method.
|
|
78
|
+
'''
|
|
79
|
+
raise NotImplementedError('Subclasses must implement the execute method')
|
|
80
|
+
|
|
81
|
+
def __str__(self) -> str:
|
|
82
|
+
'''Return a string representation of the action.'''
|
|
83
|
+
return f'{self.name} {self.id}'
|
|
84
|
+
|
|
85
|
+
def toHTML(self) -> str:
|
|
86
|
+
'''Return an HTML representation of the action.'''
|
|
87
|
+
return '<span style="color:#0f0;">' + f'<b>{self.name} {self.id}</b></span>'
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class FunctionCall(BaseAction):
|
|
91
|
+
NAME = 'Function Call'
|
|
92
|
+
|
|
93
|
+
def __init__(self, function: Callable = None, Args: tuple = ()):
|
|
94
|
+
'''
|
|
95
|
+
Initialize a new FunctionCall instance.
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
function : Callable, optional
|
|
100
|
+
The function to be called, by default None.
|
|
101
|
+
Args : tuple, optional
|
|
102
|
+
The arguments to be passed to the function, by default ().
|
|
103
|
+
|
|
104
|
+
Raises
|
|
105
|
+
------
|
|
106
|
+
TypeError
|
|
107
|
+
If the provided function is not callable.
|
|
108
|
+
'''
|
|
109
|
+
super().__init__()
|
|
110
|
+
self.function = function
|
|
111
|
+
self.function_args = Args
|
|
112
|
+
|
|
113
|
+
def execute(self, **kwargs):
|
|
114
|
+
event: threading.Event = kwargs.get('event')
|
|
115
|
+
if event and event.is_set():
|
|
116
|
+
self.output(f'{str(self)}: Execution stopped.', **kwargs)
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
self.output(f'{str(self)}: Executing...', **kwargs)
|
|
120
|
+
if self.function is not None:
|
|
121
|
+
self.function(*self.function_args, **kwargs)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ActionGroup(BaseAction):
|
|
125
|
+
'''
|
|
126
|
+
Action that groups multiple child actions.
|
|
127
|
+
|
|
128
|
+
Subclass of BaseAction.
|
|
129
|
+
|
|
130
|
+
Attributes
|
|
131
|
+
----------
|
|
132
|
+
NAME : str
|
|
133
|
+
The name of the action type.
|
|
134
|
+
child_actions : list
|
|
135
|
+
List of child actions.
|
|
136
|
+
'''
|
|
137
|
+
|
|
138
|
+
NAME = 'Action Group'
|
|
139
|
+
|
|
140
|
+
def __init__(self):
|
|
141
|
+
'''
|
|
142
|
+
Initialize a new ActionGroup instance.
|
|
143
|
+
'''
|
|
144
|
+
super().__init__()
|
|
145
|
+
self.child_actions: list[BaseAction] = []
|
|
146
|
+
|
|
147
|
+
def execute(self, **kwargs):
|
|
148
|
+
'''
|
|
149
|
+
Execute the action group by executing all child actions.
|
|
150
|
+
'''
|
|
151
|
+
event: threading.Event = kwargs.get('event')
|
|
152
|
+
if event and event.is_set():
|
|
153
|
+
self.output(f'{str(self)}: Execution stopped.', **kwargs)
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
self.output(f'{str(self)}: Executing...', **kwargs)
|
|
157
|
+
kwargs['level'] = kwargs.get('level', 0) + 1
|
|
158
|
+
for child_action in self.child_actions:
|
|
159
|
+
if event and event.is_set():
|
|
160
|
+
break
|
|
161
|
+
child_action.execute(**kwargs)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class ForLoop(ActionGroup):
|
|
165
|
+
'''
|
|
166
|
+
Action that repeats the execution of its child actions.
|
|
167
|
+
|
|
168
|
+
Subclass of ActionGroup.
|
|
169
|
+
|
|
170
|
+
Attributes
|
|
171
|
+
----------
|
|
172
|
+
NAME : str
|
|
173
|
+
The name of the action type.
|
|
174
|
+
repeat_count : int
|
|
175
|
+
The number of times to repeat the child actions.
|
|
176
|
+
'''
|
|
177
|
+
|
|
178
|
+
NAME = 'For Loop'
|
|
179
|
+
|
|
180
|
+
def __init__(self, repeat_count=3):
|
|
181
|
+
'''
|
|
182
|
+
Initialize a new ForLoop instance.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
repeat_count : int, optional
|
|
187
|
+
The number of times to repeat the child actions, by default 3.
|
|
188
|
+
'''
|
|
189
|
+
super().__init__()
|
|
190
|
+
self.repeat_count = repeat_count
|
|
191
|
+
|
|
192
|
+
def setRepeatCount(self, repeat_count):
|
|
193
|
+
'''
|
|
194
|
+
Set the number of times to repeat the child actions.
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
repeat_count : int
|
|
199
|
+
The number of times to repeat the child actions.
|
|
200
|
+
'''
|
|
201
|
+
self.repeat_count = repeat_count
|
|
202
|
+
|
|
203
|
+
def execute(self, **kwargs):
|
|
204
|
+
'''
|
|
205
|
+
Execute the for loop by repeating the child actions.
|
|
206
|
+
|
|
207
|
+
Notes
|
|
208
|
+
-----
|
|
209
|
+
Overrides the execute method in ActionGroup.
|
|
210
|
+
'''
|
|
211
|
+
event: threading.Event = kwargs.get('event')
|
|
212
|
+
if event and event.is_set():
|
|
213
|
+
self.output(f'{str(self)}: Execution stopped.', **kwargs)
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
new_kwargs = kwargs.copy()
|
|
217
|
+
new_kwargs['level'] = kwargs.get('level', 0) + 1
|
|
218
|
+
for i in range(self.repeat_count):
|
|
219
|
+
if event and event.is_set():
|
|
220
|
+
break
|
|
221
|
+
self.output(f'{str(self)} ({i+1})', **kwargs)
|
|
222
|
+
new_kwargs[f'i{self.id}'] = i
|
|
223
|
+
for child_action in self.child_actions:
|
|
224
|
+
if event and event.is_set():
|
|
225
|
+
break
|
|
226
|
+
child_action.execute(**new_kwargs)
|
|
227
|
+
|
|
228
|
+
def __str__(self) -> str:
|
|
229
|
+
'''
|
|
230
|
+
Return a string representation of the for loop.
|
|
231
|
+
'''
|
|
232
|
+
return f'{self.__class__.NAME} {self.id}: range({self.repeat_count})'
|
|
233
|
+
|
|
234
|
+
def toHTML(self) -> str:
|
|
235
|
+
'''
|
|
236
|
+
Return a string representation of the for loop.
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
str
|
|
241
|
+
String representation of the for loop.
|
|
242
|
+
'''
|
|
243
|
+
return (
|
|
244
|
+
f'<span style="color:#c586c0;">for</span> '
|
|
245
|
+
f'<span style="color:#9cdcfe;">i{self.id}</span> '
|
|
246
|
+
f'<span style="color:#c586c0;">in</span> '
|
|
247
|
+
f'<span style="color:#4ec9b0;">range</span>'
|
|
248
|
+
'<span style="color:#cccccc;">(</span>'
|
|
249
|
+
f'<span style="color:#9cdcfe;">{self.repeat_count}</span>'
|
|
250
|
+
'<span style="color:#cccccc;">)</span>'
|
|
251
|
+
'<span style="color:#cccccc;">:</span>'
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class ParameterAdjustmentAction(BaseAction):
|
|
256
|
+
NAME = 'Parameter'
|
|
257
|
+
|
|
258
|
+
def __init__(
|
|
259
|
+
self,
|
|
260
|
+
target_object: Tree = None,
|
|
261
|
+
parameter_name: str = 'N/A',
|
|
262
|
+
parameter_value: Any = None,
|
|
263
|
+
delay: float = 0.0,
|
|
264
|
+
is_expression: bool = False,
|
|
265
|
+
):
|
|
266
|
+
super().__init__()
|
|
267
|
+
self.target_object = target_object
|
|
268
|
+
self.parameter_name = parameter_name
|
|
269
|
+
self.parameter_value = parameter_value
|
|
270
|
+
self.delay = delay
|
|
271
|
+
self.is_expression = is_expression
|
|
272
|
+
|
|
273
|
+
def get_paramtree(self):
|
|
274
|
+
target_object = WeakObjects.getObject(self.target_object)
|
|
275
|
+
|
|
276
|
+
if target_object is None:
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
if isinstance(target_object, Tree):
|
|
280
|
+
return target_object
|
|
281
|
+
if isinstance(target_object, Camera_Panel):
|
|
282
|
+
return target_object.camera_options
|
|
283
|
+
for attr_name in dir(target_object):
|
|
284
|
+
attr = getattr(target_object, attr_name)
|
|
285
|
+
if isinstance(attr, Tree):
|
|
286
|
+
return attr
|
|
287
|
+
|
|
288
|
+
def get_event(self, attr: str):
|
|
289
|
+
target_object = WeakObjects.getObject(self.target_object)
|
|
290
|
+
|
|
291
|
+
if hasattr(target_object, attr):
|
|
292
|
+
event = getattr(target_object, attr)
|
|
293
|
+
if isinstance(event, threading.Event):
|
|
294
|
+
return event
|
|
295
|
+
else:
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
def safe_eval(self, expr: str, variables: dict) -> Any:
|
|
299
|
+
# Basic safety check: ensure the expression is a valid Python expression
|
|
300
|
+
try:
|
|
301
|
+
ast.parse(expr, mode='eval')
|
|
302
|
+
except SyntaxError:
|
|
303
|
+
raise
|
|
304
|
+
|
|
305
|
+
# Evaluate the expression with only the provided variables
|
|
306
|
+
return eval(expr, {'__builtins__': {}}, variables)
|
|
307
|
+
|
|
308
|
+
def execute(self, **kwargs):
|
|
309
|
+
target_object = self.get_paramtree()
|
|
310
|
+
if target_object is None:
|
|
311
|
+
self.output(f'{str(self)}: object not found ...', **kwargs)
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
event: threading.Event = kwargs.get('event')
|
|
315
|
+
if event and event.is_set():
|
|
316
|
+
self.output(f'{str(self)}: Execution stopped.', **kwargs)
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
self.output(f'{str(self)}: Executing...', **kwargs)
|
|
320
|
+
param = target_object.get_param(self.parameter_name)
|
|
321
|
+
if param is not None:
|
|
322
|
+
if isinstance(param, ActionParameter):
|
|
323
|
+
param.activate()
|
|
324
|
+
if self.parameter_value and 'event' in param.opts: # Wait for event
|
|
325
|
+
time.sleep(0.5)
|
|
326
|
+
param_event = self.get_event(param.opts['event'])
|
|
327
|
+
if param_event is not None: # Wait for event
|
|
328
|
+
param_event.wait()
|
|
329
|
+
time.sleep(0.2)
|
|
330
|
+
elif isinstance(param, Parameter):
|
|
331
|
+
if self.is_expression:
|
|
332
|
+
try:
|
|
333
|
+
value = self.safe_eval(self.parameter_value, kwargs)
|
|
334
|
+
except Exception as e:
|
|
335
|
+
self.output(
|
|
336
|
+
f'{str(self)}: Expression evaluation failed: {str(e)}',
|
|
337
|
+
**kwargs,
|
|
338
|
+
)
|
|
339
|
+
event.set()
|
|
340
|
+
return
|
|
341
|
+
else:
|
|
342
|
+
value = self.parameter_value
|
|
343
|
+
param.setValue(value)
|
|
344
|
+
if self.delay > 0:
|
|
345
|
+
self.output(f'{str(self)}: waiting {self.delay:.3f} secs ...', **kwargs)
|
|
346
|
+
# time.sleep(self.delay)
|
|
347
|
+
start_time = time.time()
|
|
348
|
+
while time.time() - start_time < self.delay:
|
|
349
|
+
if event and event.is_set():
|
|
350
|
+
self.output(
|
|
351
|
+
f'{str(self)}: Execution stopped during delay.', **kwargs
|
|
352
|
+
)
|
|
353
|
+
return
|
|
354
|
+
time.sleep(0.001) # Short sleep to prevent busy waiting
|
|
355
|
+
|
|
356
|
+
def __str__(self) -> str:
|
|
357
|
+
return f'{self.__class__.NAME} {self.id}'
|
|
358
|
+
|
|
359
|
+
def toHTML(self) -> str:
|
|
360
|
+
param_name = self.parameter_name.replace('.', ' → ')
|
|
361
|
+
target_object = self.get_paramtree()
|
|
362
|
+
param_type = (
|
|
363
|
+
target_object.get_param(self.parameter_name).type()
|
|
364
|
+
if target_object
|
|
365
|
+
else None
|
|
366
|
+
)
|
|
367
|
+
param_suffix = (
|
|
368
|
+
target_object.get_param(self.parameter_name).opts.get('suffix', '')
|
|
369
|
+
if target_object
|
|
370
|
+
else ''
|
|
371
|
+
)
|
|
372
|
+
if param_type == 'action':
|
|
373
|
+
value_label = '(<span style="color: #0f0">' + (
|
|
374
|
+
'wait</span>)' if self.parameter_value else 'no wait</span>)'
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
value = (
|
|
378
|
+
f'"{self.parameter_value}"'
|
|
379
|
+
if not isinstance(self.parameter_value, (int, float, bool))
|
|
380
|
+
and not self.is_expression
|
|
381
|
+
else f'{self.parameter_value}'
|
|
382
|
+
)
|
|
383
|
+
value_label = (
|
|
384
|
+
'= <span style="color: #0f0">'
|
|
385
|
+
+ f'{"expr: " if self.is_expression else ""}'
|
|
386
|
+
+ value
|
|
387
|
+
+ f'</span> {param_suffix}'
|
|
388
|
+
if self.parameter_value is not None
|
|
389
|
+
else ''
|
|
390
|
+
)
|
|
391
|
+
return (
|
|
392
|
+
# '<span style="color:#0f0;">'
|
|
393
|
+
# f'<b>{self.__class__.NAME} {self.id}</b></span>:<br>'
|
|
394
|
+
'<span style="color:#9cdcfe;">'
|
|
395
|
+
f'<i>{target_object.__class__.__name__}</i> {self.id}</span>:<br>'
|
|
396
|
+
f'<span style="font-size: 0.8em; color:#4ec9b0;">{param_name}</span> '
|
|
397
|
+
+ value_label
|
|
398
|
+
+ f'<br><span style="font-size: 0.8em; color:#9cdcfe;">Type</span>'
|
|
399
|
+
f': <span style="color: #0f0">{param_type}</span><br>'
|
|
400
|
+
f'<span style="font-size: 0.8em; color:#9cdcfe;">Delay</span>'
|
|
401
|
+
f': <span style="color: #0f0">{self.delay}</span> Seconds'
|
|
402
|
+
)
|