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