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,312 @@
1
+ from microEye.hardware.cams.micam import miCamera
2
+ from microEye.qt import QDateTime, Qt, QtCore, QtGui, QtWidgets, Signal, Slot
3
+
4
+ DEFAULT_EXPOSURE_SHORTCUTS = [
5
+ 1,
6
+ 3,
7
+ 5,
8
+ 10,
9
+ 15,
10
+ 20,
11
+ 25,
12
+ 30,
13
+ 40,
14
+ 50,
15
+ 100,
16
+ 150,
17
+ 200,
18
+ 300,
19
+ 500,
20
+ 1000,
21
+ ]
22
+
23
+
24
+ class DiscreteSlider(QtWidgets.QSlider):
25
+ '''
26
+ A QSlider that snaps to discrete, predefined values.
27
+ '''
28
+
29
+ def __init__(self, values: list, parent=None):
30
+ super().__init__(Qt.Orientation.Horizontal, parent)
31
+ self._values = values
32
+
33
+ # The slider's range is 0..(len(values)-1).
34
+ self.setMinimum(0)
35
+ self.setMaximum(len(values) - 1)
36
+
37
+ # Dynamically calculate the minimum width
38
+ # based on the largest value and number of values
39
+ fm = QtGui.QFontMetrics(self.font())
40
+ max_label_width = max(fm.horizontalAdvance(str(val)) for val in self._values)
41
+ spacing = 10 # Add some spacing between ticks
42
+ self.setMinimumWidth(len(self._values) * (max_label_width + spacing))
43
+
44
+ self.setMinimumHeight(60)
45
+
46
+ # Make ticks visible (optional)
47
+ self.setTickPosition(QtWidgets.QSlider.TickPosition.TicksAbove)
48
+ self.setTickInterval(1)
49
+
50
+ # Optionally, you could make it snap to ticks:
51
+ self.setSingleStep(1)
52
+ self.setPageStep(1)
53
+
54
+ def valueFromIndex(self):
55
+ '''
56
+ Returns the label corresponding to the current slider value.
57
+ '''
58
+ return self._values[self.value()]
59
+
60
+ def setIndexFromValue(self, val):
61
+ '''
62
+ Given a label from the predefined list, move the slider to that position.
63
+ '''
64
+ if val in self._values:
65
+ idx = self._values.index(val)
66
+ self.setValue(idx)
67
+
68
+ def paintEvent(self, event):
69
+ # First, draw the slider normally.
70
+ super().paintEvent(event)
71
+
72
+ # Create a QPainter for custom drawing.
73
+ painter = QtGui.QPainter(self)
74
+ painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing)
75
+
76
+ # Prepare a style option so we can get accurate geometry.
77
+ option = QtWidgets.QStyleOptionSlider()
78
+ self.initStyleOption(option)
79
+
80
+ # We’ll use the handle rect to get the precise x-coord for each step
81
+ fm = QtGui.QFontMetrics(painter.font())
82
+
83
+ # For each discrete value
84
+ for i, label in enumerate(self._values):
85
+ # Tell the style we want geometry for sliderPosition = i
86
+ option.sliderPosition = i
87
+
88
+ if i == 0:
89
+ label = 'min'
90
+
91
+ # Get the handle rect for that position
92
+ handle_rect = self.style().subControlRect(
93
+ QtWidgets.QStyle.ComplexControl.CC_Slider,
94
+ option,
95
+ QtWidgets.QStyle.SubControl.SC_SliderHandle,
96
+ self,
97
+ )
98
+
99
+ # Center x on the handle
100
+ x_center = handle_rect.center().x()
101
+
102
+ # Decide where you want to place the text vertically:
103
+ # - Above the handle? -> handle_rect.top() - some_offset
104
+ # - Below the groove? -> handle_rect.bottom() + some_offset
105
+ # Adjust as needed:
106
+ y_text = handle_rect.top() - 4
107
+
108
+ text_width = fm.horizontalAdvance(str(label))
109
+ text_height = fm.height()
110
+
111
+ # Build a small bounding rect so we can center-align text
112
+ rect = QtCore.QRectF(
113
+ x_center - text_width / 2, # left
114
+ y_text - text_height, # top
115
+ text_width,
116
+ text_height,
117
+ )
118
+
119
+ # Optionally clamp rect within the widget’s boundaries
120
+ if rect.left() < 0:
121
+ rect.moveLeft(0)
122
+ if rect.right() > self.width():
123
+ rect.moveRight(self.width())
124
+
125
+ # Draw the label, centered in our bounding rect
126
+ painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, str(label))
127
+
128
+ painter.end()
129
+
130
+
131
+ class CameraShortcutsWidget(QtWidgets.QWidget):
132
+ '''
133
+ A widget containing camera shortcuts for common operations like exposure adjustment
134
+ and toggling autostretch.
135
+ '''
136
+
137
+ exposureChanged = Signal(float)
138
+ autostretchChanged = Signal()
139
+ previewChanged = Signal()
140
+ saveDataChanged = Signal()
141
+ zoomChanged = Signal(float)
142
+ acquisitionStart = Signal()
143
+ acquisitionStop = Signal()
144
+ adjustName = Signal(str)
145
+
146
+ def __init__(
147
+ self,
148
+ camera: miCamera = None,
149
+ exposure_range=None,
150
+ exposure_shortcuts=DEFAULT_EXPOSURE_SHORTCUTS,
151
+ parent=None,
152
+ ):
153
+ '''
154
+ Initialize the camera shortcuts widget.
155
+
156
+ Parameters
157
+ ----------
158
+ camera_options : CameraOptions
159
+ The camera options object for callbacks
160
+ camera : miCamera, optional
161
+ Camera object for accessing properties like exposure range
162
+ exposure_range : tuple, optional
163
+ Min and max exposure values as (min, max)
164
+ exposure_shortcuts : list, optional
165
+ List of exposure shortcut values
166
+ parent : QWidget, optional
167
+ Parent widget
168
+ '''
169
+ super().__init__(parent)
170
+
171
+ self._exposure_shortcuts = exposure_shortcuts or DEFAULT_EXPOSURE_SHORTCUTS
172
+
173
+ # If camera is provided, use its exposure range
174
+ if camera is not None and hasattr(camera, 'exposure_range'):
175
+ self.min_exposure = camera.exposure_range[0]
176
+ elif exposure_range is not None:
177
+ self.min_exposure = exposure_range[0]
178
+ else:
179
+ self.min_exposure = 0.1 # Default minimum exposure
180
+
181
+ # Set up the UI
182
+ self._setup_ui()
183
+
184
+ def _setup_ui(self):
185
+ """Set up the widget's UI components"""
186
+ self.__layout = QtWidgets.QFormLayout(self)
187
+ self.setLayout(self.__layout)
188
+
189
+ # Exposure Time shortcuts
190
+ self.exposure_slider = DiscreteSlider(
191
+ [self.min_exposure, *self._exposure_shortcuts]
192
+ )
193
+ self.exposure_slider.valueChanged.connect(
194
+ lambda: self.exposureChanged.emit(self.exposure_slider.valueFromIndex())
195
+ )
196
+ self.exposure_slider.setIndexFromValue(50.0)
197
+
198
+ self.__layout.addRow('Exposure [ms]: ', self.exposure_slider)
199
+
200
+ # Zoom slider
201
+ self.display_size_slider = QtWidgets.QSlider(Qt.Orientation.Horizontal)
202
+ self.display_size_slider.setMinimum(10)
203
+ self.display_size_slider.setMaximum(400)
204
+ self.display_size_slider.setValue(50)
205
+ self.display_size_slider.valueChanged.connect(
206
+ lambda: self.zoomChanged.emit(self.display_size_slider.value() / 100)
207
+ )
208
+
209
+ self.__layout.addRow('Zoom [%]: ', self.display_size_slider)
210
+
211
+ # Add a layout for experiment name buttons
212
+ experiment_name_btns = QtWidgets.QHBoxLayout()
213
+ self.__layout.addRow('Experiment Name:', experiment_name_btns)
214
+
215
+ # Increament index
216
+ self.increment_index_button = QtWidgets.QPushButton('+ Index')
217
+ self.increment_index_button.setToolTip('Increment the current index')
218
+ self.increment_index_button.clicked.connect(
219
+ lambda: self.adjustName.emit('index')
220
+ )
221
+
222
+ experiment_name_btns.addWidget(self.increment_index_button)
223
+
224
+ # Add exposure to name
225
+ self.add_exposure_button = QtWidgets.QPushButton('+ Exposure')
226
+ self.add_exposure_button.setToolTip('Add the current exposure to the name')
227
+ self.add_exposure_button.clicked.connect(
228
+ lambda: self.adjustName.emit('exposure')
229
+ )
230
+
231
+ experiment_name_btns.addWidget(self.add_exposure_button)
232
+
233
+ # buttons
234
+ button_layout = QtWidgets.QHBoxLayout()
235
+ self.__layout.addRow(button_layout)
236
+
237
+ # Start/Stop acquisition
238
+ self.acquisition_button = QtWidgets.QPushButton('Start Acquisition')
239
+ self.acquisition_button.clicked.connect(self.acquisitionStart.emit)
240
+
241
+ button_layout.addWidget(self.acquisition_button)
242
+
243
+ self.stop_button = QtWidgets.QPushButton('Stop Acquisition')
244
+ self.stop_button.clicked.connect(self.acquisitionStop.emit)
245
+
246
+ button_layout.addWidget(self.stop_button)
247
+
248
+ # Toggle save data
249
+ self.save_data_button = QtWidgets.QCheckBox('Save Data')
250
+ self.save_data_button.clicked.connect(self.saveDataChanged.emit)
251
+
252
+ button_layout.addWidget(self.save_data_button)
253
+
254
+ # Toggle autostretch
255
+ self.autostretch_button = QtWidgets.QCheckBox('Autostretching')
256
+ self.autostretch_button.clicked.connect(self.autostretchChanged.emit)
257
+
258
+ button_layout.addWidget(self.autostretch_button)
259
+
260
+ # Toggle preview
261
+ self.preview_button = QtWidgets.QCheckBox('Preview')
262
+ self.preview_button.clicked.connect(self.previewChanged.emit)
263
+
264
+ button_layout.addWidget(self.preview_button)
265
+
266
+ def set_exposure(self, value):
267
+ '''Set the exposure slider to the given value'''
268
+ self.exposure_slider.setIndexFromValue(value)
269
+
270
+ def get_exposure(self):
271
+ '''Get the current exposure value from the slider'''
272
+ return self.exposure_slider.valueFromIndex()
273
+
274
+ def set_zoom(self, value):
275
+ '''Set the zoom slider to the given value'''
276
+ self.display_size_slider.setValue(int(value * 100))
277
+
278
+ def get_zoom(self):
279
+ '''Get the current zoom value from the slider'''
280
+ return self.display_size_slider.value() / 100
281
+
282
+ def set_autostretch(self, value):
283
+ '''Set the autostretch checkbox to the given value'''
284
+ self.autostretch_button.setChecked(value)
285
+
286
+ def set_preview(self, value):
287
+ '''Set the preview checkbox to the given value'''
288
+ self.preview_button.setChecked(value)
289
+
290
+ def set_save_data(self, value):
291
+ '''Set the save data checkbox to the given value'''
292
+ self.save_data_button.setChecked(value)
293
+
294
+ def create_context_menu(self, parent):
295
+ '''
296
+ Create a context menu containing this widget
297
+
298
+ Parameters
299
+ ----------
300
+ parent : QWidget
301
+ The parent widget for the menu
302
+
303
+ Returns
304
+ -------
305
+ QMenu
306
+ The created context menu
307
+ '''
308
+ menu = QtWidgets.QMenu(parent)
309
+ widget_action = QtWidgets.QWidgetAction(menu)
310
+ widget_action.setDefaultWidget(self)
311
+ menu.addAction(widget_action)
312
+ return menu