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,244 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from microEye import __version__
|
|
5
|
+
from microEye.hardware.protocols.actions import (
|
|
6
|
+
ForLoop,
|
|
7
|
+
FunctionCall,
|
|
8
|
+
ParameterAdjustmentAction,
|
|
9
|
+
)
|
|
10
|
+
from microEye.hardware.protocols.scene_manager import SceneManager
|
|
11
|
+
from microEye.qt import QApplication, Qt, QtCore, QtGui, QtWidgets
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ExperimentDesignerView(QtWidgets.QGraphicsView):
|
|
15
|
+
'''
|
|
16
|
+
Custom QGraphicsView with zooming and key event handling.
|
|
17
|
+
|
|
18
|
+
Subclass of QGraphicsView.
|
|
19
|
+
|
|
20
|
+
Attributes
|
|
21
|
+
----------
|
|
22
|
+
root : ActionGroupItem
|
|
23
|
+
The root item in the scene.
|
|
24
|
+
'''
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
'''
|
|
28
|
+
Initialize a new ExperimentDesignerView instance.
|
|
29
|
+
'''
|
|
30
|
+
super().__init__()
|
|
31
|
+
self.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True)
|
|
32
|
+
self.setRenderHint(QtGui.QPainter.RenderHint.TextAntialiasing, True)
|
|
33
|
+
self.setRenderHint(QtGui.QPainter.RenderHint.SmoothPixmapTransform, True)
|
|
34
|
+
|
|
35
|
+
# Create an instance of the SceneManager
|
|
36
|
+
self.scene_manager = SceneManager()
|
|
37
|
+
self.setScene(self.scene_manager.scene)
|
|
38
|
+
|
|
39
|
+
# Set dark background color for the view
|
|
40
|
+
background_color = QtGui.QColor('#333333') # Adjust the color as needed
|
|
41
|
+
self.setBackgroundBrush(background_color)
|
|
42
|
+
|
|
43
|
+
def wheelEvent(self, event: QtWidgets.QGraphicsSceneWheelEvent):
|
|
44
|
+
'''
|
|
45
|
+
Handle wheel events for zooming.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
event : QGraphicsSceneWheelEvent
|
|
50
|
+
The wheel event.
|
|
51
|
+
'''
|
|
52
|
+
zoom_factor = 1.2
|
|
53
|
+
|
|
54
|
+
if event.angleDelta().y() > 0:
|
|
55
|
+
self.scale(zoom_factor, zoom_factor)
|
|
56
|
+
else:
|
|
57
|
+
self.scale(1 / zoom_factor, 1 / zoom_factor)
|
|
58
|
+
|
|
59
|
+
super().wheelEvent(event)
|
|
60
|
+
|
|
61
|
+
def keyPressEvent(self, event):
|
|
62
|
+
'''
|
|
63
|
+
Handle key press events for swapping and deletion.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
event : QKeyEvent
|
|
68
|
+
The key event.
|
|
69
|
+
'''
|
|
70
|
+
self.scene_manager.handleKeyPress(event)
|
|
71
|
+
|
|
72
|
+
super().keyPressEvent(event)
|
|
73
|
+
|
|
74
|
+
def contextMenuEvent(self, event):
|
|
75
|
+
'''
|
|
76
|
+
Handle context menu events.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
event : QContextMenuEvent
|
|
81
|
+
The context menu event.
|
|
82
|
+
'''
|
|
83
|
+
context_menu = QtWidgets.QMenu()
|
|
84
|
+
|
|
85
|
+
# Add option to add different types
|
|
86
|
+
add_menu = context_menu.addMenu('Add')
|
|
87
|
+
# add_menu.addAction(
|
|
88
|
+
# 'Function Call', lambda: self.scene_manager.add_action(FunctionCall())
|
|
89
|
+
# )
|
|
90
|
+
add_menu.addAction('For Loop', lambda: self.scene_manager.add_action(ForLoop()))
|
|
91
|
+
add_menu.addAction(
|
|
92
|
+
'Parameter Adjustment',
|
|
93
|
+
lambda: self.scene_manager.add_action(ParameterAdjustmentAction()),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Add Move submenu
|
|
97
|
+
move_submenu = context_menu.addMenu('Move')
|
|
98
|
+
move_submenu.addAction(
|
|
99
|
+
'Up',
|
|
100
|
+
lambda: self.keyPressEvent(
|
|
101
|
+
QtGui.QKeyEvent(
|
|
102
|
+
QtCore.QEvent.Type.KeyPress,
|
|
103
|
+
Qt.Key.Key_PageUp,
|
|
104
|
+
Qt.KeyboardModifier.NoModifier,
|
|
105
|
+
0,
|
|
106
|
+
0,
|
|
107
|
+
0,
|
|
108
|
+
)
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
move_submenu.addAction(
|
|
112
|
+
'Down',
|
|
113
|
+
lambda: self.keyPressEvent(
|
|
114
|
+
QtGui.QKeyEvent(
|
|
115
|
+
QtCore.QEvent.Type.KeyPress,
|
|
116
|
+
Qt.Key.Key_PageDown,
|
|
117
|
+
Qt.KeyboardModifier.NoModifier,
|
|
118
|
+
0,
|
|
119
|
+
0,
|
|
120
|
+
0,
|
|
121
|
+
)
|
|
122
|
+
),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
context_menu.addAction(
|
|
126
|
+
'Delete',
|
|
127
|
+
lambda: self.keyPressEvent(
|
|
128
|
+
QtGui.QKeyEvent(
|
|
129
|
+
QtCore.QEvent.Type.KeyPress,
|
|
130
|
+
Qt.Key.Key_Delete,
|
|
131
|
+
Qt.KeyboardModifier.NoModifier,
|
|
132
|
+
0,
|
|
133
|
+
0,
|
|
134
|
+
0,
|
|
135
|
+
)
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Add Execute action
|
|
140
|
+
context_menu.addAction('Execute', self.scene_manager.execute_actions)
|
|
141
|
+
context_menu.addAction('Stop Execution', self.scene_manager.stop_execution)
|
|
142
|
+
|
|
143
|
+
# Add Export and Import actions
|
|
144
|
+
context_menu.addAction('Export (protocol)', self.export_protocol)
|
|
145
|
+
context_menu.addAction('Import (protocol)', self.import_protocol)
|
|
146
|
+
context_menu.addAction('Import & Execute', self.import_execute_protocol)
|
|
147
|
+
|
|
148
|
+
# Show the context menu
|
|
149
|
+
context_menu.exec(event.globalPos())
|
|
150
|
+
|
|
151
|
+
def export_protocol(self):
|
|
152
|
+
'''
|
|
153
|
+
Handle exporting the protocol to a JSON file.
|
|
154
|
+
'''
|
|
155
|
+
file_dialog = QtWidgets.QFileDialog()
|
|
156
|
+
file_name, _ = file_dialog.getSaveFileName(
|
|
157
|
+
self, 'Export Protocol', '', 'JSON Files (*.json)'
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if file_name:
|
|
161
|
+
with open(file_name, 'w') as file:
|
|
162
|
+
protocol_data = self.scene_manager.serialize_protocol()
|
|
163
|
+
json.dump(protocol_data, file, indent=4)
|
|
164
|
+
|
|
165
|
+
def import_protocol(self):
|
|
166
|
+
'''
|
|
167
|
+
Handle importing the protocol from a JSON file.
|
|
168
|
+
'''
|
|
169
|
+
file_dialog = QtWidgets.QFileDialog()
|
|
170
|
+
file_name, _ = file_dialog.getOpenFileName(
|
|
171
|
+
self, 'Import Protocol', '', 'JSON Files (*.json)'
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if not file_name:
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
with open(file_name) as file:
|
|
178
|
+
protocol_data = json.load(file)
|
|
179
|
+
self.scene_manager.load_actions(protocol_data)
|
|
180
|
+
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
def import_execute_protocol(self):
|
|
184
|
+
'''
|
|
185
|
+
Handle importing and executing the protocol from a JSON file.
|
|
186
|
+
'''
|
|
187
|
+
if self.import_protocol():
|
|
188
|
+
self.scene_manager.execute_actions()
|
|
189
|
+
|
|
190
|
+
class ExperimentDesigner(QtWidgets.QWidget):
|
|
191
|
+
HEADER = '> <span style="color:#0f0;">Experiment Designer ('
|
|
192
|
+
HEADER += f'<span style="color:#aaf;">microEye v{__version__}</span>)</span>'
|
|
193
|
+
|
|
194
|
+
def __init__(self):
|
|
195
|
+
super().__init__()
|
|
196
|
+
self.initUI()
|
|
197
|
+
|
|
198
|
+
def initUI(self):
|
|
199
|
+
# Create the ExperimentDesignerView
|
|
200
|
+
self.experiment_view = ExperimentDesignerView()
|
|
201
|
+
|
|
202
|
+
# Create the text terminal
|
|
203
|
+
self.text_terminal = QtWidgets.QPlainTextEdit()
|
|
204
|
+
self.text_terminal.setReadOnly(True)
|
|
205
|
+
self.text_terminal.appendHtml(self.HEADER)
|
|
206
|
+
self.text_terminal.setStyleSheet(
|
|
207
|
+
'QPlainTextEdit { background-color: #111;}'
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
self.experiment_view.scene_manager.updateTerminal.connect(self.updateTerminal)
|
|
211
|
+
self.experiment_view.scene_manager.clearTerminal.connect(self.clearTerminal)
|
|
212
|
+
|
|
213
|
+
# Set up the layout
|
|
214
|
+
layout = QtWidgets.QHBoxLayout()
|
|
215
|
+
layout.addWidget(self.experiment_view, 2)
|
|
216
|
+
layout.addWidget(self.text_terminal, 1)
|
|
217
|
+
self.setLayout(layout)
|
|
218
|
+
|
|
219
|
+
# Set window properties
|
|
220
|
+
self.setWindowTitle('Experiment Designer')
|
|
221
|
+
|
|
222
|
+
def updateTerminal(self, text: str, level: int):
|
|
223
|
+
indent = '>' * (level + 1) + ' '
|
|
224
|
+
if ':' in text and len(text.split(':')) == 2:
|
|
225
|
+
text = text.split(':')
|
|
226
|
+
self.text_terminal.appendHtml(
|
|
227
|
+
f'{indent}<span style="color: #0f0;">{text[0]}: </span>'
|
|
228
|
+
f'<span style="color: #aaa;">{text[1]}</span>'
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
self.text_terminal.appendHtml(
|
|
232
|
+
f'{indent}<span style="color: #0f0;">{text}</span>'
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def clearTerminal(self):
|
|
236
|
+
self.text_terminal.clear()
|
|
237
|
+
self.text_terminal.appendHtml(self.HEADER)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
if __name__ == '__main__':
|
|
241
|
+
app = QApplication(sys.argv)
|
|
242
|
+
editor = ExperimentDesigner()
|
|
243
|
+
editor.show()
|
|
244
|
+
sys.exit(app.exec())
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import traceback
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from microEye.hardware.protocols.actions import ActionGroup, BaseAction
|
|
6
|
+
from microEye.hardware.protocols.actions_items import (
|
|
7
|
+
ActionGroupItem,
|
|
8
|
+
BaseActionItem,
|
|
9
|
+
get_action_item,
|
|
10
|
+
)
|
|
11
|
+
from microEye.hardware.protocols.serialization import (
|
|
12
|
+
deserialize_action,
|
|
13
|
+
serialize_action,
|
|
14
|
+
)
|
|
15
|
+
from microEye.qt import Qt, QtCore, QtGui, QtWidgets, Signal
|
|
16
|
+
from microEye.utils.thread_worker import QThreadWorker
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SceneManager(QtCore.QObject):
|
|
20
|
+
'''
|
|
21
|
+
Class responsible for managing the scene and its layout.
|
|
22
|
+
'''
|
|
23
|
+
|
|
24
|
+
updateTerminal = Signal(str, int)
|
|
25
|
+
clearTerminal = Signal()
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
'''
|
|
29
|
+
Initialize a new SceneManager instance.
|
|
30
|
+
'''
|
|
31
|
+
super().__init__()
|
|
32
|
+
self.scene = QtWidgets.QGraphicsScene()
|
|
33
|
+
self.action = ActionGroup()
|
|
34
|
+
self.action.name = 'Main'
|
|
35
|
+
self.root = ActionGroupItem(self.action)
|
|
36
|
+
self.scene.addItem(self.root)
|
|
37
|
+
|
|
38
|
+
self.worker = None
|
|
39
|
+
|
|
40
|
+
def add_action(self, action: BaseAction):
|
|
41
|
+
'''
|
|
42
|
+
Add an action to the scene.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
action : BaseAction
|
|
47
|
+
The action to be added.
|
|
48
|
+
'''
|
|
49
|
+
selected_items = self.scene.selectedItems()
|
|
50
|
+
|
|
51
|
+
if len(selected_items) == 1:
|
|
52
|
+
action_item = get_action_item(action)
|
|
53
|
+
|
|
54
|
+
if isinstance(selected_items[0], ActionGroupItem):
|
|
55
|
+
selected_item = selected_items[0]
|
|
56
|
+
selected_item.add_child_item(action_item)
|
|
57
|
+
else:
|
|
58
|
+
selected_item = selected_items[0]
|
|
59
|
+
parent_item = selected_item.parentItem()
|
|
60
|
+
if isinstance(parent_item, ActionGroupItem):
|
|
61
|
+
parent_item.add_child_item(action_item)
|
|
62
|
+
|
|
63
|
+
def handleKeyPress(self, event: QtGui.QKeyEvent):
|
|
64
|
+
selected_items = self.scene.selectedItems()
|
|
65
|
+
|
|
66
|
+
if len(selected_items) == 1 and isinstance(selected_items[0], BaseActionItem):
|
|
67
|
+
selected_item = selected_items[0]
|
|
68
|
+
parent_item = selected_item.parentItem()
|
|
69
|
+
if parent_item is not None and isinstance(parent_item, ActionGroupItem):
|
|
70
|
+
index_self = parent_item.child_items.index(selected_item)
|
|
71
|
+
|
|
72
|
+
if event.key() == Qt.Key.Key_PageUp:
|
|
73
|
+
# Move item up
|
|
74
|
+
if index_self > 0:
|
|
75
|
+
parent_item.swap_children(index_self, index_self - 1)
|
|
76
|
+
elif event.key() == Qt.Key.Key_PageDown:
|
|
77
|
+
# Move item down
|
|
78
|
+
if index_self < len(parent_item.child_items) - 1:
|
|
79
|
+
parent_item.swap_children(index_self, index_self + 1)
|
|
80
|
+
elif event.key() == Qt.Key.Key_Delete:
|
|
81
|
+
# Delete item and its children
|
|
82
|
+
parent_item.remove_child_item(selected_item)
|
|
83
|
+
self.scene.removeItem(selected_item)
|
|
84
|
+
|
|
85
|
+
def remove_action(self, action: BaseAction):
|
|
86
|
+
'''
|
|
87
|
+
Remove an action from the scene.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
action : BaseAction
|
|
92
|
+
The action to be removed.
|
|
93
|
+
'''
|
|
94
|
+
action_item = self.find_action_item(action)
|
|
95
|
+
if action_item:
|
|
96
|
+
parent_item = action_item.parentItem()
|
|
97
|
+
if parent_item:
|
|
98
|
+
parent_item.remove_child_item(action_item)
|
|
99
|
+
self.scene.removeItem(action_item)
|
|
100
|
+
|
|
101
|
+
def find_action_item(self, action: BaseAction) -> ActionGroupItem:
|
|
102
|
+
'''
|
|
103
|
+
Find the action item corresponding to the given action.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
action : BaseAction
|
|
108
|
+
The action to find.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
ActionGroupItem
|
|
113
|
+
The action item corresponding to the given action, or None if not found.
|
|
114
|
+
'''
|
|
115
|
+
for item in self.scene.items():
|
|
116
|
+
if isinstance(item, ActionGroupItem) and item.action == action:
|
|
117
|
+
return item
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
def execute_actions(self):
|
|
121
|
+
'''
|
|
122
|
+
Execute the actions in the scene.
|
|
123
|
+
'''
|
|
124
|
+
if isinstance(self.root, ActionGroupItem) and (
|
|
125
|
+
self.worker is None or self.worker.is_set()
|
|
126
|
+
):
|
|
127
|
+
self.clearTerminal.emit()
|
|
128
|
+
|
|
129
|
+
def actions(root: ActionGroupItem, updateTerminal, **kwargs):
|
|
130
|
+
try:
|
|
131
|
+
time = QtCore.QDateTime.currentDateTime().date()
|
|
132
|
+
root.action.execute(
|
|
133
|
+
output=updateTerminal, level=0, event=kwargs.get('event'),
|
|
134
|
+
year=time.year(), month=time.month(), day=time.day()
|
|
135
|
+
)
|
|
136
|
+
except Exception:
|
|
137
|
+
updateTerminal.emit(traceback.format_exc(), 0)
|
|
138
|
+
finally:
|
|
139
|
+
updateTerminal.emit('Done!', 0)
|
|
140
|
+
|
|
141
|
+
self.worker = QThreadWorker(
|
|
142
|
+
actions, self.root, self.updateTerminal
|
|
143
|
+
)
|
|
144
|
+
# Execute
|
|
145
|
+
QtCore.QThreadPool.globalInstance().start(self.worker)
|
|
146
|
+
|
|
147
|
+
def stop_execution(self):
|
|
148
|
+
if self.worker:
|
|
149
|
+
self.worker.stop() # This will signal the thread to stop
|
|
150
|
+
|
|
151
|
+
# self.updateTerminal.emit('Execution stopped.', 0)
|
|
152
|
+
|
|
153
|
+
def load_actions(self, actions: list[dict[str, Any]]):
|
|
154
|
+
'''
|
|
155
|
+
Load actions from a list of serialized action data.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
actions : list[dict[str, Any]]
|
|
160
|
+
A list of serialized action data.
|
|
161
|
+
'''
|
|
162
|
+
self.clear_scene()
|
|
163
|
+
for action_data in actions:
|
|
164
|
+
action, action_item = deserialize_action(action_data)
|
|
165
|
+
self.root.add_child_item(action_item)
|
|
166
|
+
|
|
167
|
+
def clear_scene(self):
|
|
168
|
+
'''
|
|
169
|
+
Clear the scene by removing all items.
|
|
170
|
+
'''
|
|
171
|
+
self.scene.clear()
|
|
172
|
+
BaseAction.id_counter = 1
|
|
173
|
+
self.action = ActionGroup()
|
|
174
|
+
self.action.name = 'Main'
|
|
175
|
+
self.root = ActionGroupItem(self.action)
|
|
176
|
+
self.scene.addItem(self.root)
|
|
177
|
+
|
|
178
|
+
def serialize_protocol(self) -> list[dict[str, Any]]:
|
|
179
|
+
'''
|
|
180
|
+
Serialize the protocol to a JSON-serializable data structure.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
list[dict[str, Any]]
|
|
185
|
+
A list of serialized action data.
|
|
186
|
+
'''
|
|
187
|
+
protocol_data = []
|
|
188
|
+
for action in self.root.action.child_actions:
|
|
189
|
+
action_data = serialize_action(action)
|
|
190
|
+
protocol_data.append(action_data)
|
|
191
|
+
return protocol_data
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from microEye.hardware.protocols.actions import (
|
|
4
|
+
BaseAction,
|
|
5
|
+
ForLoop,
|
|
6
|
+
FunctionCall,
|
|
7
|
+
ParameterAdjustmentAction,
|
|
8
|
+
)
|
|
9
|
+
from microEye.hardware.protocols.actions_items import (
|
|
10
|
+
ActionGroupItem,
|
|
11
|
+
get_action_item,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def serialize_action(action: BaseAction) -> dict[str, Any]:
|
|
16
|
+
'''
|
|
17
|
+
Serialize an action to a JSON-serializable data structure.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
action : BaseAction
|
|
22
|
+
The action to be serialized.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
dict[str, Any]
|
|
27
|
+
A dictionary representing the serialized action data.
|
|
28
|
+
'''
|
|
29
|
+
action_data = {
|
|
30
|
+
'type': type(action).__name__,
|
|
31
|
+
'id': action.id,
|
|
32
|
+
'name': action.name,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if isinstance(action, FunctionCall):
|
|
36
|
+
action_data['function'] = action.function.__name__
|
|
37
|
+
action_data['function_args'] = list(action.function_args)
|
|
38
|
+
elif isinstance(action, ForLoop):
|
|
39
|
+
action_data['repeat_count'] = action.repeat_count
|
|
40
|
+
action_data['child_actions'] = [
|
|
41
|
+
serialize_action(child) for child in action.child_actions
|
|
42
|
+
]
|
|
43
|
+
elif isinstance(action, ParameterAdjustmentAction):
|
|
44
|
+
action_data['target_object'] = action.target_object
|
|
45
|
+
action_data['parameter_name'] = action.parameter_name
|
|
46
|
+
action_data['parameter_value'] = action.parameter_value
|
|
47
|
+
action_data['delay'] = action.delay
|
|
48
|
+
action_data['is_expression'] = action.is_expression
|
|
49
|
+
|
|
50
|
+
return action_data
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def deserialize_action(action_data: dict[str, Any]) -> tuple[BaseAction, Any]:
|
|
54
|
+
'''
|
|
55
|
+
Deserialize an action from the JSON data structure.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
action_data : dict[str, Any]
|
|
60
|
+
The dictionary containing the serialized action data.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
tuple[BaseAction, Any]
|
|
65
|
+
A tuple containing the deserialized action instance and its corresponding
|
|
66
|
+
action item.
|
|
67
|
+
'''
|
|
68
|
+
action_type = globals()[action_data['type']]
|
|
69
|
+
|
|
70
|
+
if action_type == FunctionCall:
|
|
71
|
+
function = globals()[action_data['function']]
|
|
72
|
+
args = tuple(action_data['function_args'])
|
|
73
|
+
action = FunctionCall(function, args)
|
|
74
|
+
action_item = get_action_item(action)
|
|
75
|
+
elif action_type == ForLoop:
|
|
76
|
+
action = ForLoop(action_data['repeat_count'])
|
|
77
|
+
action_item = ActionGroupItem(action)
|
|
78
|
+
for child_data in action_data['child_actions']:
|
|
79
|
+
child_action, child_item = deserialize_action(child_data)
|
|
80
|
+
action_item.add_child_item(child_item)
|
|
81
|
+
elif action_type == ParameterAdjustmentAction:
|
|
82
|
+
action = ParameterAdjustmentAction(
|
|
83
|
+
target_object=action_data.get('target_object'),
|
|
84
|
+
parameter_name=action_data.get('parameter_name'),
|
|
85
|
+
parameter_value=action_data.get('parameter_value'),
|
|
86
|
+
delay=action_data.get('delay', 0.0),
|
|
87
|
+
is_expression=action_data.get('is_expression', False)
|
|
88
|
+
)
|
|
89
|
+
action_item = get_action_item(action)
|
|
90
|
+
else:
|
|
91
|
+
action = action_type()
|
|
92
|
+
action_item = get_action_item(action)
|
|
93
|
+
|
|
94
|
+
# action.id = action_data['id']
|
|
95
|
+
action.name = action_data['name']
|
|
96
|
+
|
|
97
|
+
return action, action_item
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# get pycromanager version
|
|
2
|
+
from mmpycorex import __version__ as mmpycorex_version
|
|
3
|
+
from pycromanager import __version__ as pycromanager_version
|
|
4
|
+
|
|
5
|
+
required_pycromanager_version = '1.0.2'
|
|
6
|
+
required_mmpycorex_version = '0.3.16'
|
|
7
|
+
|
|
8
|
+
if pycromanager_version != required_pycromanager_version:
|
|
9
|
+
raise ImportError(
|
|
10
|
+
f'This module requires pycromanager version {required_pycromanager_version}'
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
if mmpycorex_version < required_mmpycorex_version:
|
|
14
|
+
raise ImportError(
|
|
15
|
+
f'This module requires mmpycorex version {required_mmpycorex_version}'
|
|
16
|
+
)
|