microeye 2.3.2__py3-none-any.whl

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