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,703 @@
1
+ from typing import Union
2
+
3
+ from pyqtgraph.parametertree import Parameter
4
+ from pyqtgraph.parametertree.parameterTypes import ActionParameter, GroupParameter
5
+
6
+ from microEye.hardware.cams import Camera_Panel
7
+ from microEye.hardware.protocols.actions import (
8
+ ActionGroup,
9
+ BaseAction,
10
+ ForLoop,
11
+ FunctionCall,
12
+ ParameterAdjustmentAction,
13
+ WeakObjects,
14
+ )
15
+ from microEye.qt import QtCore, QtGui, QtWidgets
16
+ from microEye.utils.parameter_tree import Tree
17
+
18
+ # Define color constants
19
+ BACKGROUND_COLOR = QtGui.QColor('#262626') # Dark gray #1E1E1E
20
+ FOREGROUND_COLOR = QtGui.QColor('#D4D4D4') # Light gray
21
+ BORDER_COLOR = QtGui.QColor('#808080') # Gray
22
+ HIGHLIGHT_COLOR = QtGui.QColor('#4D4D4D') # Darker gray
23
+
24
+ # Define font constants
25
+ FONT_FAMILY = ', '.join(
26
+ [
27
+ 'Courier New',
28
+ 'Courier',
29
+ 'Monaco',
30
+ 'Menlo',
31
+ 'system-ui',
32
+ '-apple-system',
33
+ 'BlinkMacSystemFont',
34
+ "'Segoe UI'",
35
+ 'Roboto',
36
+ 'Oxygen',
37
+ 'Ubuntu',
38
+ 'Cantarell',
39
+ "'Open Sans'",
40
+ "'Helvetica Neue'",
41
+ 'sans-serif',
42
+ ]
43
+ )
44
+ FONT_SIZE = 10
45
+ FONT_BOLD = QtGui.QFont.Weight.Bold
46
+
47
+
48
+ class BaseActionItem(QtWidgets.QGraphicsRectItem):
49
+ '''
50
+ Base class for graphical representation of action items in the scene.
51
+
52
+ Subclass of QGraphicsRectItem.
53
+
54
+ Attributes
55
+ ----------
56
+ TOP_MARGIN : int
57
+ Top margin for layout.
58
+ H_MARGIN : int
59
+ Horizontal margin for layout.
60
+ VSPACING : int
61
+ Vertical spacing for layout.
62
+ MIN_WIDTH : int
63
+ Minimum width of the item.
64
+ MIN_HEIGHT : int
65
+ Minimum height of the item.
66
+ '''
67
+
68
+ TOP_MARGIN = 30
69
+ MARGIN = 20
70
+ SPACING = 10
71
+ MIN_WIDTH = 150
72
+ MIN_HEIGHT = 50
73
+
74
+ def __init__(
75
+ self,
76
+ action: Union[ActionGroup, ForLoop, FunctionCall, ParameterAdjustmentAction],
77
+ parent=None,
78
+ ):
79
+ '''
80
+ Initialize a new BaseActionItem instance.
81
+
82
+ Parameters
83
+ ----------
84
+ action : Union[ActionGroup, ForLoop, FunctionCall, ParameterAdjustmentAction]
85
+ The associated action for this item.
86
+ parent : QGraphicsItem, optional
87
+ The parent item, by default None.
88
+ '''
89
+ super().__init__(parent)
90
+ self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
91
+ self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
92
+
93
+ self.action = action
94
+ self.text_item = QtWidgets.QGraphicsTextItem(self)
95
+ self.text_item.setPos(5, 5)
96
+ # Set font for the text item
97
+ font = QtGui.QFont(FONT_FAMILY, FONT_SIZE)
98
+ self.text_item.setFont(font)
99
+ self.text_item.setDefaultTextColor(FOREGROUND_COLOR)
100
+ self.text_item.setHtml(action.toHTML())
101
+
102
+ bounding_rect = self.text_item.boundingRect()
103
+
104
+ self.MIN_WIDTH = max(self.MIN_WIDTH, bounding_rect.width() + self.MARGIN)
105
+ self.MIN_HEIGHT = max(self.MIN_HEIGHT, bounding_rect.height() + self.MARGIN)
106
+
107
+ self.setRect(QtCore.QRectF(0, 0, self.MIN_WIDTH, self.MIN_HEIGHT))
108
+
109
+ # Set dark background color
110
+ self.setBrush(BACKGROUND_COLOR)
111
+ self.setPen(QtGui.QPen(BORDER_COLOR, 1))
112
+
113
+ # Add a subtle shadow effect
114
+ shadow = QtWidgets.QGraphicsDropShadowEffect()
115
+ shadow.setBlurRadius(5)
116
+ shadow.setColor(QtGui.QColor('#000000')) # Adjust the shadow color as needed
117
+ shadow.setOffset(2, 2)
118
+ self.setGraphicsEffect(shadow)
119
+
120
+ def mouseDoubleClickEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent):
121
+ '''
122
+ Handle mouse double-click events for editing properties.
123
+
124
+ Opens a dialog to edit customizable properties.
125
+
126
+ Parameters
127
+ ----------
128
+ event : QGraphicsSceneMouseEvent
129
+ The mouse event.
130
+ '''
131
+ if isinstance(self.action, ForLoop):
132
+ text, ok = QtWidgets.QInputDialog.getInt(
133
+ None,
134
+ 'Edit Repeat Count',
135
+ 'Enter new repeat count:',
136
+ value=self.action.repeat_count,
137
+ )
138
+ if ok:
139
+ self.action.setRepeatCount(text)
140
+ self.text_item.setHtml(self.action.toHTML())
141
+ else:
142
+ text, ok = QtWidgets.QInputDialog.getText(
143
+ None, 'Edit Properties', 'Enter new name:', text=self.action.name
144
+ )
145
+ if ok and text:
146
+ self.action.name = text
147
+ self.text_item.setHtml(self.action.toHTML())
148
+
149
+ self.update_layout()
150
+
151
+ def mouseMoveEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent):
152
+ """
153
+ Handle mouse move events for dragging.
154
+
155
+ Update the item's position and recursively update layout.
156
+
157
+ Parameters
158
+ ----------
159
+ event : QGraphicsSceneMouseEvent
160
+ The mouse event.
161
+ """
162
+ # Check if the item being moved is the root item
163
+ if self.parentItem() is None:
164
+ # This is the root item, handle the move event
165
+ super().mouseMoveEvent(event)
166
+ new_pos = event.scenePos()
167
+
168
+ parent = self.parentItem()
169
+
170
+ if parent is not None:
171
+ parent_rect = parent.boundingRect()
172
+ new_pos.setX(parent_rect.left() + self.MARGIN // 2)
173
+ new_pos.setY(
174
+ max(
175
+ parent_rect.top() + self.TOP_MARGIN,
176
+ min(
177
+ new_pos.y(),
178
+ parent_rect.bottom()
179
+ - self.boundingRect().height()
180
+ - self.SPACING,
181
+ ),
182
+ )
183
+ )
184
+
185
+ self.setPos(new_pos)
186
+ parent.update_layout_recursive()
187
+ else:
188
+ # This is not the root item, skip the move event
189
+ return
190
+
191
+ def max_neighbor_width(self):
192
+ '''
193
+ Calculate the maximum width of the neighboring items.
194
+
195
+ Returns
196
+ -------
197
+ int
198
+ The maximum width of the neighboring items.
199
+ '''
200
+ parent = self.parentItem()
201
+ if isinstance(parent, ActionGroupItem):
202
+ return parent.max_child_width()
203
+ else:
204
+ return self.MIN_WIDTH
205
+
206
+ def calculate_layout(self):
207
+ '''
208
+ Calculate the layout dimensions based on the provided parameters
209
+ and neighboring items widths.
210
+
211
+ Returns
212
+ -------
213
+ tuple
214
+ A tuple containing the calculated width and height for the layout.
215
+ '''
216
+ parent = self.parentItem()
217
+ if isinstance(self, ActionGroupItem):
218
+ content_width = self.max_child_width() + 2 * self.MARGIN
219
+ content_height = self.children_height()
220
+ else:
221
+ content_width = self.text_item.boundingRect().width()
222
+ content_height = self.text_item.boundingRect().height()
223
+
224
+ if isinstance(parent, ActionGroupItem):
225
+ neighbor_width = parent.max_child_width() + self.MARGIN
226
+ else:
227
+ neighbor_width = content_width
228
+
229
+ width = max([neighbor_width, content_width, self.MIN_WIDTH]) + self.MARGIN
230
+ height = max(self.MIN_HEIGHT, content_height)
231
+ return width, height
232
+
233
+ def update_layout(self, update_parent=True):
234
+ '''
235
+ Update the layout of the item.
236
+ '''
237
+ parent = self.parentItem()
238
+ if isinstance(parent, ActionGroupItem):
239
+ max_width, max_height = self.calculate_layout()
240
+
241
+ self.setRect(0, 0, max_width, max_height)
242
+ if update_parent:
243
+ parent.update_layout_recursive(False)
244
+
245
+ def update_layout_recursive(self, update_self=True):
246
+ '''
247
+ Recursively update layout starting from the current
248
+ item and going up the hierarchy.
249
+ '''
250
+ current_item = self if update_self else self.parentItem()
251
+ while current_item is not None:
252
+ current_item.update_layout()
253
+ current_item = current_item.parentItem()
254
+
255
+
256
+ class ActionGroupItem(BaseActionItem):
257
+ '''
258
+ Graphical representation of an ActionGroup or ForLoop in the scene.
259
+
260
+ Subclass of BaseActionItem.
261
+
262
+ Attributes
263
+ ----------
264
+ child_items : list
265
+ List of child items.
266
+ '''
267
+
268
+ def __init__(self, action: Union[ActionGroup, ForLoop], parent=None):
269
+ '''
270
+ Initialize a new ActionGroupItem instance.
271
+
272
+ Parameters
273
+ ----------
274
+ action : Union[ActionGroup, ForLoop]
275
+ The associated action for this item.
276
+ parent : QGraphicsItem, optional
277
+ The parent item, by default None.
278
+ '''
279
+ super().__init__(action, parent)
280
+
281
+ self.child_items: list[BaseActionItem] = []
282
+
283
+ def add_child_item(self, child_item: BaseActionItem):
284
+ '''
285
+ Add a child item to the group.
286
+
287
+ Parameters
288
+ ----------
289
+ child_item : BaseActionItem
290
+ The child item to be added.
291
+ '''
292
+ self.child_items.append(child_item)
293
+ self.action.child_actions.append(child_item.action)
294
+ child_item.setParentItem(self)
295
+ self.update_layout_recursive()
296
+
297
+ def remove_child_item(self, child_item):
298
+ '''
299
+ Remove a child item from the group.
300
+
301
+ Parameters
302
+ ----------
303
+ child_item : BaseActionItem
304
+ The child item to be removed.
305
+ '''
306
+ self.child_items.remove(child_item)
307
+ self.action.child_actions.remove(child_item.action)
308
+ child_item.setParentItem(None)
309
+ self.update_layout_recursive()
310
+
311
+ def children_widths(self):
312
+ '''
313
+ Get the widths of the child items.
314
+
315
+ Returns
316
+ -------
317
+ list[int]
318
+ The width of the child items.
319
+ '''
320
+ if self.child_items:
321
+ return [
322
+ child_item.max_child_width() + self.MARGIN
323
+ if isinstance(child_item, ActionGroupItem)
324
+ else child_item.text_item.boundingRect().width()
325
+ for child_item in self.child_items
326
+ ]
327
+ else:
328
+ return [self.MIN_WIDTH]
329
+
330
+ def children_height(self):
331
+ '''
332
+ Get the heights of the child items.
333
+
334
+ Returns
335
+ -------
336
+ list[int]
337
+ The heights of the child items.
338
+ '''
339
+ if self.child_items:
340
+ return (
341
+ sum(
342
+ [
343
+ child_item.boundingRect().height()
344
+ for child_item in self.child_items
345
+ ]
346
+ )
347
+ + self.text_item.boundingRect().height()
348
+ + (len(self.child_items) + 1) * self.SPACING
349
+ )
350
+ else:
351
+ return self.MIN_HEIGHT
352
+
353
+ def max_child_width(self):
354
+ '''
355
+ Calculate the maximum width of the child items.
356
+
357
+ Returns
358
+ -------
359
+ int
360
+ The maximum width of the child items.
361
+ '''
362
+ return max(self.children_widths())
363
+
364
+ def update_layout(self, update_parent=True):
365
+ '''
366
+ Update the layout of the item and its child items.
367
+ '''
368
+ max_width, max_height = self.calculate_layout()
369
+ self.setRect(0, 0, max_width, max_height)
370
+
371
+ total_height = self.text_item.boundingRect().height() + self.SPACING
372
+ for child_item in self.child_items:
373
+ height = child_item.boundingRect().height()
374
+ child_item.setPos(self.SPACING, total_height)
375
+ total_height += height + self.SPACING
376
+ child_item.update_layout(False)
377
+
378
+ def add_child_item_by_type(self, action_type):
379
+ '''
380
+ Add a child item of a specific action type to the group.
381
+
382
+ Parameters
383
+ ----------
384
+ action_type : type
385
+ The type of action for the child item.
386
+ '''
387
+ action = action_type()
388
+ action_item = get_action_item(action)
389
+ self.add_child_item(action_item)
390
+
391
+ def swap_children(self, index1, index2):
392
+ '''
393
+ Swap positions of two child items in the group.
394
+
395
+ Parameters
396
+ ----------
397
+ index1 : int
398
+ Index of the first child item.
399
+ index2 : int
400
+ Index of the second child item.
401
+ '''
402
+ self.child_items[index1], self.child_items[index2] = (
403
+ self.child_items[index2],
404
+ self.child_items[index1],
405
+ )
406
+
407
+ self.action.child_actions[index1], self.action.child_actions[index2] = (
408
+ self.action.child_actions[index2],
409
+ self.action.child_actions[index1],
410
+ )
411
+
412
+ self.update_layout_recursive()
413
+
414
+
415
+ class ParameterAdjustmentDialog(QtWidgets.QDialog):
416
+ def __init__(self, parent=None):
417
+ super().__init__(parent)
418
+ self.setMinimumWidth(400)
419
+ self.setWindowTitle('Configure Parameter Adjustment')
420
+
421
+ layout = QtWidgets.QVBoxLayout(self)
422
+
423
+ # Target Object
424
+ self.target_object_combo = QtWidgets.QComboBox()
425
+ layout.addWidget(QtWidgets.QLabel('Target Object:'))
426
+ layout.addWidget(self.target_object_combo)
427
+
428
+ # Parameter Name
429
+ self.parameter_name_combo = QtWidgets.QComboBox()
430
+ layout.addWidget(QtWidgets.QLabel('Parameter Name:'))
431
+ layout.addWidget(self.parameter_name_combo)
432
+
433
+ # Value Editor
434
+ self.value_editor_stack = QtWidgets.QStackedWidget()
435
+ layout.addWidget(self.value_editor_stack)
436
+
437
+ # Expression Mode
438
+ self.expression_mode_checkbox = QtWidgets.QCheckBox('Expression Mode')
439
+ layout.addWidget(self.expression_mode_checkbox)
440
+
441
+ # Expression Editor
442
+ self.expression_editor = QtWidgets.QLineEdit()
443
+ self.expression_editor.setPlaceholderText(
444
+ 'Enter expression (e.g., f"Experiment_{i2+1:03d}")'
445
+ )
446
+ layout.addWidget(self.expression_editor)
447
+ self.expression_editor.hide()
448
+
449
+ # Delay
450
+ self.delay_spin = QtWidgets.QDoubleSpinBox()
451
+ self.delay_spin.setRange(0.0, 1e6)
452
+ self.delay_spin.setSingleStep(0.1)
453
+ self.delay_spin.setDecimals(3)
454
+ layout.addWidget(QtWidgets.QLabel('Delay (seconds):'))
455
+ layout.addWidget(self.delay_spin)
456
+
457
+ # Buttons
458
+ self.buttons = QtWidgets.QDialogButtonBox(
459
+ QtWidgets.QDialogButtonBox.StandardButton.Ok
460
+ | QtWidgets.QDialogButtonBox.StandardButton.Cancel
461
+ )
462
+ self.buttons.accepted.connect(self.accept)
463
+ self.buttons.rejected.connect(self.reject)
464
+ layout.addWidget(self.buttons)
465
+
466
+ # Connect signals
467
+ self.target_object_combo.currentIndexChanged.connect(
468
+ self.update_parameter_names
469
+ )
470
+ self.parameter_name_combo.currentIndexChanged.connect(
471
+ self.update_parameter_value_editor
472
+ )
473
+ self.expression_mode_checkbox.stateChanged.connect(self.toggle_expression_mode)
474
+
475
+ # Initialize
476
+ self.target_object_combo.addItems(WeakObjects.getObjectNames())
477
+ self.update_parameter_names()
478
+
479
+ def update_parameter_names(self):
480
+ tree = self.get_paramtree()
481
+ if tree:
482
+ parameter_names = [
483
+ param
484
+ for param in self.get_params()
485
+ if not isinstance(tree.get_param(param), GroupParameter)
486
+ and tree.get_param(param) is not None
487
+ ]
488
+ self.parameter_name_combo.clear()
489
+ self.parameter_name_combo.addItems(parameter_names)
490
+ self.update_parameter_value_editor()
491
+
492
+ def update_parameter_value_editor(self):
493
+ parameter_name = self.parameter_name_combo.currentText()
494
+
495
+ while self.value_editor_stack.count() > 0:
496
+ self.value_editor_stack.removeWidget(self.value_editor_stack.widget(0))
497
+
498
+ tree = self.get_paramtree()
499
+ if tree:
500
+ param = tree.get_param(parameter_name)
501
+ if param is not None:
502
+ value_editor = self.create_value_editor(param)
503
+ if value_editor is not None:
504
+ self.value_editor_stack.addWidget(value_editor)
505
+ self.value_editor_stack.setCurrentWidget(value_editor)
506
+
507
+ # Disable expression mode for action parameters
508
+ is_action = isinstance(param, ActionParameter)
509
+ self.expression_mode_checkbox.setEnabled(not is_action)
510
+ if is_action:
511
+ self.expression_mode_checkbox.setChecked(False)
512
+ self.expression_editor.hide()
513
+ self.value_editor_stack.show()
514
+
515
+ def toggle_expression_mode(self, state):
516
+ is_expression = state == QtCore.Qt.CheckState.Checked.value
517
+ self.value_editor_stack.setVisible(not is_expression)
518
+ self.expression_editor.setVisible(is_expression)
519
+
520
+ def create_value_editor(self, param: Parameter):
521
+ param_type = param.opts.get('type', None)
522
+ param_limits = param.opts.get('limits', [])
523
+
524
+ if param_type in ['int', 'float']:
525
+ value_editor = (
526
+ QtWidgets.QDoubleSpinBox()
527
+ if param_type == 'float'
528
+ else QtWidgets.QSpinBox()
529
+ )
530
+ if param_limits:
531
+ value_editor.setMinimum(param_limits[0])
532
+ value_editor.setMaximum(param_limits[1])
533
+ else:
534
+ value_editor.setMinimum(0)
535
+ value_editor.setMaximum(int(1e9))
536
+ value_editor.setValue(param.value())
537
+ elif param_type == 'bool':
538
+ value_editor = QtWidgets.QCheckBox()
539
+ value_editor.setChecked(param.value())
540
+ elif param_type == 'action':
541
+ value_editor = QtWidgets.QCheckBox('Try to wait for Event')
542
+ elif param_type == 'str' or param_type == 'file':
543
+ value_editor = QtWidgets.QLineEdit()
544
+ value_editor.setText(param.value())
545
+ elif param_type == 'list':
546
+ value_editor = QtWidgets.QComboBox()
547
+ for limit in param_limits:
548
+ value_editor.addItem(str(limit), limit)
549
+ value_editor.setCurrentText(str(param.value()))
550
+ else:
551
+ value_editor = None
552
+
553
+ return value_editor
554
+
555
+ def get_paramtree(self):
556
+ target_name = self.target_object_combo.currentText()
557
+ target_object = WeakObjects.getObject(target_name)
558
+
559
+ if target_object is None:
560
+ return None
561
+
562
+ if isinstance(target_object, Tree):
563
+ return target_object
564
+ if isinstance(target_object, Camera_Panel):
565
+ return target_object.camera_options
566
+ for attr_name in dir(target_object):
567
+ attr = getattr(target_object, attr_name)
568
+ if isinstance(attr, Tree):
569
+ return attr
570
+
571
+ def get_params(self):
572
+ target_name = self.target_object_combo.currentText()
573
+ target_object = WeakObjects.getObject(target_name)
574
+
575
+ if target_object is None:
576
+ return None
577
+
578
+ if isinstance(target_object, Tree):
579
+ return target_object.get_param_paths()
580
+ if isinstance(target_object, Camera_Panel):
581
+ return target_object.camera_options.get_param_paths()
582
+
583
+ for attr_name in dir(target_object):
584
+ attr = getattr(target_object, attr_name)
585
+ if hasattr(attr, 'get_param_paths'):
586
+ return attr.get_param_paths()
587
+
588
+ def get_parameter_value(self):
589
+ if self.expression_mode_checkbox.isChecked():
590
+ return self.expression_editor.text(), True
591
+
592
+ editor = self.value_editor_stack.currentWidget()
593
+ if isinstance(editor, (QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox)):
594
+ return editor.value(), False
595
+ elif isinstance(editor, QtWidgets.QCheckBox):
596
+ return editor.isChecked(), False
597
+ elif isinstance(editor, QtWidgets.QLineEdit):
598
+ return editor.text(), False
599
+ elif isinstance(editor, QtWidgets.QComboBox):
600
+ return editor.currentData(), False
601
+ else:
602
+ return None, False
603
+
604
+ def set_parameter_value(self, value, is_expression=False):
605
+ self.expression_mode_checkbox.setChecked(is_expression)
606
+ if is_expression:
607
+ self.expression_editor.setText(value)
608
+ else:
609
+ editor = self.value_editor_stack.currentWidget()
610
+ if isinstance(editor, (QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox)):
611
+ editor.setValue(float(value))
612
+ elif isinstance(editor, QtWidgets.QCheckBox):
613
+ editor.setChecked(bool(value))
614
+ elif isinstance(editor, QtWidgets.QLineEdit):
615
+ editor.setText(str(value))
616
+ elif isinstance(editor, QtWidgets.QComboBox):
617
+ index = editor.findData(value)
618
+ if index >= 0:
619
+ editor.setCurrentIndex(index)
620
+ else:
621
+ return None
622
+
623
+
624
+ class ParameterAdjustmentActionItem(BaseActionItem):
625
+ def __init__(
626
+ self,
627
+ action: ParameterAdjustmentAction,
628
+ parent=None,
629
+ ):
630
+ super().__init__(action, parent)
631
+
632
+ def tree(self):
633
+ return WeakObjects.OBJECTS
634
+
635
+ def mouseDoubleClickEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent):
636
+ config_dialog = ParameterAdjustmentDialog()
637
+
638
+ if self.action.target_object:
639
+ index = config_dialog.target_object_combo.findText(
640
+ self.action.target_object
641
+ )
642
+ if index >= 0:
643
+ config_dialog.target_object_combo.setCurrentIndex(index)
644
+
645
+ if self.action.parameter_name:
646
+ index = config_dialog.parameter_name_combo.findText(
647
+ self.action.parameter_name
648
+ )
649
+ if index >= 0:
650
+ config_dialog.parameter_name_combo.setCurrentIndex(index)
651
+ if self.action.parameter_value is not None:
652
+ config_dialog.set_parameter_value(
653
+ self.action.parameter_value, self.action.is_expression
654
+ )
655
+ if self.action.delay:
656
+ config_dialog.delay_spin.setValue(self.action.delay)
657
+
658
+ if config_dialog.exec() == QtWidgets.QDialog.DialogCode.Accepted:
659
+ target_object_index = config_dialog.target_object_combo.currentIndex()
660
+ if target_object_index > -1:
661
+ self.action.target_object = (
662
+ config_dialog.target_object_combo.currentText()
663
+ )
664
+ self.action.parameter_name = (
665
+ config_dialog.parameter_name_combo.currentText()
666
+ )
667
+ self.action.parameter_value, self.action.is_expression = (
668
+ config_dialog.get_parameter_value()
669
+ )
670
+ self.action.delay = config_dialog.delay_spin.value()
671
+
672
+ self.text_item.setHtml(self.action.toHTML())
673
+
674
+ self.update_layout_recursive()
675
+
676
+
677
+ ACTION_TYPE_TO_ITEM = {
678
+ ActionGroup: ActionGroupItem,
679
+ FunctionCall: BaseActionItem,
680
+ ForLoop: ActionGroupItem,
681
+ ParameterAdjustmentAction: ParameterAdjustmentActionItem,
682
+ }
683
+
684
+
685
+ def get_action_item(action: BaseAction):
686
+ '''
687
+ Get the appropriate action item based on the action type.
688
+
689
+ Parameters
690
+ ----------
691
+ action : BaseAction
692
+ The action for which to create an item.
693
+
694
+ Returns
695
+ -------
696
+ BaseActionItem
697
+ The corresponding action item.
698
+ '''
699
+ action_item_class = ACTION_TYPE_TO_ITEM.get(type(action))
700
+ if action_item_class is None:
701
+ raise ValueError(f'Invalid action type: {type(action)}')
702
+
703
+ return action_item_class(action)