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,444 @@
1
+ import sys
2
+ from subprocess import PIPE, Popen
3
+
4
+ from microEye.qt import (
5
+ QT_API,
6
+ QApplication,
7
+ QtCore,
8
+ QtGui,
9
+ QtWidgets,
10
+ Slot,
11
+ getOpenFileName,
12
+ getSaveFileName,
13
+ )
14
+
15
+ if 'PyQt' in QT_API:
16
+ from microEye.qt import Qsci
17
+
18
+ class CodeEditorWidget(Qsci.QsciScintilla):
19
+ """
20
+ Customized code editor widget based on QsciScintilla.
21
+
22
+ Parameters
23
+ ----------
24
+ linter : str, optional
25
+ The linter to be used (default is 'pyflakes').
26
+ formatter : str, optional
27
+ The code formatter to be used (default is 'autopep8').
28
+ """
29
+
30
+ def __init__(self, linter='pyflakes', formatter='autopep8'):
31
+ """
32
+ Initialize the CodeEditorWidget.
33
+
34
+ Parameters
35
+ ----------
36
+ linter : str, optional
37
+ The linter to be used (default is 'pyflakes').
38
+ formatter : str, optional
39
+ The code formatter to be used (default is 'autopep8').
40
+ """
41
+ super().__init__()
42
+
43
+ self.setUtf8(True)
44
+ self.setMarginType(0, Qsci.QsciScintilla.MarginType.NumberMargin)
45
+
46
+ # 1. Text wrapping
47
+ # -----------------
48
+ self.setWrapMode(Qsci.QsciScintilla.WrapMode.WrapWord)
49
+ self.setWrapVisualFlags(
50
+ Qsci.QsciScintilla.WrapVisualFlag.WrapFlagByText,
51
+ Qsci.QsciScintilla.WrapVisualFlag.WrapFlagInMargin,
52
+ )
53
+ self.setWrapIndentMode(Qsci.QsciScintilla.WrapIndentMode.WrapIndentSame)
54
+
55
+ # 2. End-of-line mode
56
+ # --------------------
57
+ self.setEolMode(Qsci.QsciScintilla.EolMode.EolUnix)
58
+ self.setEolVisibility(False)
59
+
60
+ # 3. Indentation
61
+ # ---------------
62
+ self.setIndentationsUseTabs(False)
63
+ self.setTabWidth(4)
64
+ self.setIndentationGuides(True)
65
+ self.setTabIndents(True)
66
+ self.setAutoIndent(True)
67
+
68
+ # 4. Caret
69
+ # ---------
70
+ # self.setCaretForegroundColor(QColor('#ff0000ff'))
71
+ self.setCaretLineVisible(True)
72
+ self.setCaretLineBackgroundColor(QtGui.QColor('#1f00007f'))
73
+ self.setCaretForegroundColor(QtGui.QColor('#ffffff'))
74
+ self.setCaretWidth(2)
75
+
76
+ # 5. Margins
77
+ # -----------
78
+ # Margin 0 = Line nr margin
79
+ self.setMarginType(0, Qsci.QsciScintilla.MarginType.NumberMargin)
80
+ self.setMarginWidth(0, '0000000000')
81
+ self.setMarginsForegroundColor(QtGui.QColor('#ffffff'))
82
+ self.setMarginsBackgroundColor(QtGui.QColor('#181818'))
83
+ # Margin 1 = Symbol
84
+ # self.setMarginType(1, QsciScintilla.SymbolMarginColor)
85
+ # self.setMarginWidth(0, '00')
86
+ # self.markerDefine(QsciScintilla.Circle, 0)
87
+ # self.setMarginMarkerMask(1, 0b1)
88
+
89
+ self.linter = linter
90
+ self.formatter = formatter
91
+
92
+ self.__lexer = Qsci.QsciLexerPython(self)
93
+ self.__lexer.setDefaultFont(QtGui.QFont('Consolas'))
94
+ self.__lexer.setDefaultPaper(QtGui.QColor('#1F1F1F'))
95
+ self.__lexer.setDefaultColor(QtGui.QColor('#f9f9f9'))
96
+ self.__lexer.setColor(QtGui.QColor('#B5CEA8'), Qsci.QsciLexerPython.Number)
97
+ self.__lexer.setColor(
98
+ QtGui.QColor('#CE9178'), Qsci.QsciLexerPython.DoubleQuotedFString
99
+ )
100
+ self.__lexer.setColor(
101
+ QtGui.QColor('#CE9178'), Qsci.QsciLexerPython.DoubleQuotedString
102
+ )
103
+ self.__lexer.setColor(
104
+ QtGui.QColor('#CE9178'), Qsci.QsciLexerPython.SingleQuotedFString
105
+ )
106
+ self.__lexer.setColor(
107
+ QtGui.QColor('#CE9178'), Qsci.QsciLexerPython.SingleQuotedString
108
+ )
109
+ self.__lexer.setColor(
110
+ QtGui.QColor('#CE9178'), Qsci.QsciLexerPython.TripleDoubleQuotedFString
111
+ )
112
+ self.__lexer.setColor(
113
+ QtGui.QColor('#CE9178'), Qsci.QsciLexerPython.TripleDoubleQuotedString
114
+ )
115
+ self.__lexer.setColor(
116
+ QtGui.QColor('#CE9178'), Qsci.QsciLexerPython.TripleSingleQuotedFString
117
+ )
118
+ self.__lexer.setColor(
119
+ QtGui.QColor('#CE9178'), Qsci.QsciLexerPython.TripleSingleQuotedString
120
+ )
121
+
122
+ self.__lexer.setColor(QtGui.QColor('#569CD6'), Qsci.QsciLexerPython.Keyword)
123
+ self.__lexer.setColor(
124
+ QtGui.QColor('#DCDCAA'), Qsci.QsciLexerPython.FunctionMethodName
125
+ )
126
+ self.__lexer.setColor(
127
+ QtGui.QColor('#4DC9B0'), Qsci.QsciLexerPython.ClassName
128
+ )
129
+ self.__lexer.setColor(QtGui.QColor('#6A9955'), Qsci.QsciLexerPython.Comment)
130
+ self.__lexer.setFont(QtGui.QFont('Consolas'), Qsci.QsciLexerPython.Comment)
131
+ self.__lexer.setColor(
132
+ QtGui.QColor('#6A9955'), Qsci.QsciLexerPython.CommentBlock
133
+ )
134
+ self.__lexer.setFont(
135
+ QtGui.QFont('Consolas'), Qsci.QsciLexerPython.CommentBlock
136
+ )
137
+ self.__lexer.setColor(
138
+ QtGui.QColor('#FFD70F'), Qsci.QsciLexerPython.Operator
139
+ )
140
+
141
+ self.__api = Qsci.QsciAPIs(self.__lexer)
142
+ self.setLexer(self.__lexer)
143
+ # self.setAutoCompletionSource(QsciScintilla.AcsAll)
144
+ # self.setAutoCompletionThreshold(1)
145
+
146
+ self.currentLineNumber = 0
147
+
148
+ self.timer = QtCore.QTimer(self)
149
+ self.timer.timeout.connect(self.lintCode)
150
+ self.timer.setSingleShot(True)
151
+ self.timer.setInterval(100)
152
+
153
+ self.textChanged.connect(self.debouncedLintCode)
154
+
155
+ def debouncedLintCode(self):
156
+ '''
157
+ Start a timer to debounce code linting on text changes.
158
+ '''
159
+ self.timer.start()
160
+
161
+ def lintCode(self):
162
+ '''
163
+ Lint the code using the specified linter.
164
+
165
+ Returns
166
+ -------
167
+ str
168
+ A message indicating the linting result.
169
+ '''
170
+ code = self.text()
171
+
172
+ if self.linter == 'pyflakes':
173
+ process = Popen(
174
+ ['pyflakes'], stdin=PIPE, stdout=PIPE, stderr=PIPE, text=True
175
+ )
176
+ else:
177
+ return 'Unsupported linter.'
178
+
179
+ output, errors = process.communicate(code)
180
+
181
+ if errors:
182
+ self.markLintingErrors(errors)
183
+ return errors
184
+ else:
185
+ self.clearAnnotations()
186
+ return 'Linting passed successfully.'
187
+
188
+ def markLintingErrors(self, errors: str):
189
+ '''
190
+ Mark linting errors in the editor.
191
+
192
+ Parameters
193
+ ----------
194
+ errors : str
195
+ Error messages from the linter.
196
+ '''
197
+ error_lines_set = []
198
+ error_lines = errors.splitlines()
199
+
200
+ self.clearAnnotations()
201
+
202
+ message = ''
203
+ for line in error_lines:
204
+ parts = line.split(':')
205
+ if len(parts) >= 2 and parts[1].isdigit():
206
+ line_number = int(parts[1])
207
+ col_number = int(parts[2])
208
+ error_lines_set.append(line_number)
209
+ message += f'Col {col_number}: {parts[-1]}\n'
210
+ else:
211
+ if len(line.strip()) > 0:
212
+ new_line = '' if '^' in line else '\n'
213
+ message += f'{line}{new_line}'
214
+
215
+ self.annotate(line_number - 1, message, 3)
216
+
217
+ def formatCode(self):
218
+ '''
219
+ Format the code using the specified code formatter.
220
+
221
+ Returns
222
+ -------
223
+ str
224
+ A message indicating the formatting result.
225
+ '''
226
+ code = self.text()
227
+
228
+ if self.formatter == 'autopep8':
229
+ process = Popen(
230
+ ['autopep8', '-'], stdin=PIPE, stdout=PIPE, stderr=PIPE, text=True
231
+ )
232
+ formatted_code, errors = process.communicate(input=code)
233
+
234
+ if errors:
235
+ return errors
236
+ else:
237
+ self.setText(formatted_code)
238
+ return 'Code formatted successfully.'
239
+ else:
240
+ return 'Unsupported code formatter.'
241
+ else:
242
+ class PythonHighlighter(QtGui.QSyntaxHighlighter):
243
+ def __init__(self, parent=None):
244
+ super().__init__(parent)
245
+ self.highlightingRules = []
246
+
247
+ keywordFormat = QtGui.QTextCharFormat()
248
+ keywordFormat.setForeground(QtGui.QColor('#569CD6'))
249
+ keywords = [
250
+ 'and', 'as', 'assert', 'break', 'class', 'continue','def', 'del',
251
+ 'elif', 'else', 'except', 'False', 'finally', 'for', 'from', 'global',
252
+ 'if', 'import', 'in', 'is', 'lambda', 'None', 'nonlocal', 'not',
253
+ 'or', 'pass', 'raise', 'return', 'True', 'try', 'while', 'with', 'yield'
254
+ ]
255
+
256
+ for keyword in keywords:
257
+ pattern = QtCore.QRegularExpression(r'\b' + keyword + r'\b')
258
+ self.highlightingRules.append((pattern, keywordFormat))
259
+
260
+ # Add more patterns and formats for strings, comments, etc.
261
+
262
+ def highlightBlock(self, text):
263
+ for pattern, format in self.highlightingRules:
264
+ expression = QtCore.QRegularExpression(pattern)
265
+
266
+ # Start at the beginning of the text
267
+ cursor = 0
268
+
269
+ while cursor < len(text):
270
+ match = expression.match(text, cursor)
271
+
272
+ if not match.hasMatch():
273
+ break
274
+
275
+ index = match.capturedStart()
276
+ length = match.capturedLength()
277
+ self.setFormat(index, length, format)
278
+
279
+ # Move cursor to the end of the current match
280
+ cursor = index + length
281
+
282
+ self.setCurrentBlockState(0)
283
+
284
+ class CodeEditorWidget(QtWidgets.QPlainTextEdit):
285
+ def __init__(self, linter='pyflakes', formatter='autopep8', parent=None):
286
+ super().__init__(parent)
287
+ self.setFont(QtGui.QFont('Consolas', 10))
288
+ self.setTabStopDistance(4 * self.fontMetrics().horizontalAdvance(' '))
289
+
290
+ self.highlighter = PythonHighlighter(self.document())
291
+
292
+ self.linter = linter
293
+ self.formatter = formatter
294
+
295
+ self.textChanged.connect(self.debouncedLintCode)
296
+ self.timer = QtCore.QTimer(self)
297
+ self.timer.timeout.connect(self.lintCode)
298
+ self.timer.setSingleShot(True)
299
+ self.timer.setInterval(100)
300
+
301
+ def text(self):
302
+ return self.toPlainText()
303
+
304
+ def setText(self, value):
305
+ self.setPlainText(value)
306
+
307
+ @Slot()
308
+ def debouncedLintCode(self):
309
+ self.timer.start()
310
+
311
+ @Slot()
312
+ def lintCode(self):
313
+ code = self.toPlainText()
314
+ if self.linter == 'pyflakes':
315
+ process = Popen(
316
+ ['pyflakes', '-'],
317
+ stdin=PIPE, stdout=PIPE, stderr=PIPE, text=True)
318
+ output, errors = process.communicate(input=code)
319
+ if errors:
320
+ self.markLintingErrors(errors)
321
+ else:
322
+ self.clearAnnotations()
323
+
324
+ def markLintingErrors(self, errors):
325
+ # Add logic to mark linting errors
326
+ pass
327
+
328
+ def clearAnnotations(self):
329
+ # Add logic to clear annotations
330
+ pass
331
+
332
+ @Slot()
333
+ def formatCode(self):
334
+ code = self.toPlainText()
335
+ if self.formatter == 'autopep8':
336
+ process = Popen(
337
+ ['autopep8', '-'],
338
+ stdin=PIPE, stdout=PIPE, stderr=PIPE, text=True)
339
+ formatted_code, errors = process.communicate(input=code)
340
+ if errors:
341
+ return errors
342
+ else:
343
+ self.setPlainText(formatted_code)
344
+ return 'Code formatted successfully.'
345
+ else:
346
+ return 'Unsupported code formatter.'
347
+
348
+
349
+ class pyEditor(QtWidgets.QWidget):
350
+ '''
351
+ Main window containing the code editor.
352
+
353
+ Parameters
354
+ ----------
355
+ parent : QWidget, optional
356
+ The parent widget (default is None).
357
+ '''
358
+
359
+ def __init__(self, parent=None):
360
+ '''
361
+ Initialize the pyEditor.
362
+
363
+ Parameters
364
+ ----------
365
+ parent : QWidget, optional
366
+ The parent widget (default is None).
367
+ '''
368
+ super().__init__(parent=parent)
369
+
370
+ self.mLayout = QtWidgets.QVBoxLayout()
371
+ self.setLayout(self.mLayout)
372
+
373
+ self.codeEditorWidget = CodeEditorWidget()
374
+ self.codeEditorWidget.setAcceptDrops(True)
375
+ self.codeEditorWidget.dragEnterEvent = self.pydragEnterEvent
376
+ self.codeEditorWidget.dropEvent = (
377
+ lambda e: self.codeEditorWidget.insertPlainText(e.mimeData().text())
378
+ )
379
+
380
+ self.btns_layout = QtWidgets.QHBoxLayout()
381
+ self.open_btn = QtWidgets.QPushButton('Open', clicked=lambda: self.openScript())
382
+ self.save_btn = QtWidgets.QPushButton('Save', clicked=lambda: self.saveScript())
383
+ self.format_btn = QtWidgets.QPushButton(
384
+ 'Format', clicked=self.codeEditorWidget.formatCode
385
+ )
386
+ self.exec_btn = QtWidgets.QPushButton('Execute')
387
+ self.btns_layout.addWidget(self.open_btn)
388
+ self.btns_layout.addWidget(self.save_btn)
389
+ self.btns_layout.addWidget(self.format_btn)
390
+ self.btns_layout.addWidget(self.exec_btn)
391
+
392
+ self.mLayout.addWidget(self.codeEditorWidget)
393
+ self.mLayout.addLayout(self.btns_layout)
394
+
395
+ def openScript(self):
396
+ '''
397
+ Open a Python script file and load its content into the editor.
398
+ '''
399
+ filename, _ = getOpenFileName(
400
+ self, 'Load Script', filter='Python Files (*.py);;'
401
+ )
402
+
403
+ if len(filename) > 0:
404
+ with open(filename, encoding='utf-8') as file:
405
+ self.codeEditorWidget.setText(file.read())
406
+
407
+ def saveScript(self):
408
+ '''
409
+ Save the content of the editor into a Python script file.
410
+ '''
411
+ filename, _ = getSaveFileName(
412
+ self, 'Save Script', filter='Python Files (*.py);;'
413
+ )
414
+
415
+ if len(filename) > 0:
416
+ with open(filename, 'w', encoding='utf-8') as file:
417
+ file.write(self.codeEditorWidget.text())
418
+
419
+ def pydragEnterEvent(self, e):
420
+ '''
421
+ Handle drag enter event for plain text.
422
+ '''
423
+ if e.mimeData().hasFormat('text/plain'):
424
+ e.accept()
425
+ else:
426
+ e.ignore()
427
+
428
+ def toPlainText(self):
429
+ '''
430
+ Get the plain text content of the code editor.
431
+
432
+ Returns
433
+ -------
434
+ str
435
+ The plain text content of the code editor.
436
+ '''
437
+ return self.codeEditorWidget.text()
438
+
439
+
440
+ if __name__ == '__main__':
441
+ app = QApplication(sys.argv)
442
+ window = pyEditor()
443
+ window.show()
444
+ sys.exit(app.exec())
@@ -0,0 +1,35 @@
1
+ import sys
2
+ import traceback
3
+
4
+ from microEye.qt import QApplication, QtWidgets
5
+
6
+
7
+ def retry_exec(func, *args, **kwargs):
8
+ while True:
9
+ try:
10
+ func(*args, **kwargs)
11
+ break
12
+ except Exception as e:
13
+ if not show_error_dialog(str(e)):
14
+ break
15
+
16
+
17
+ def show_error_dialog(error_message):
18
+ app = QApplication.instance()
19
+ if app is None:
20
+ app = QApplication(sys.argv)
21
+
22
+ msg_box = QtWidgets.QMessageBox()
23
+ msg_box.setIcon(QtWidgets.QMessageBox.Icon.Critical)
24
+ msg_box.setWindowTitle('Error')
25
+ msg_box.setText('An error occurred')
26
+ msg_box.setInformativeText(error_message)
27
+ msg_box.setDetailedText(traceback.format_exc())
28
+ retry_button = msg_box.addButton(
29
+ 'Retry', QtWidgets.QMessageBox.ButtonRole.AcceptRole
30
+ )
31
+ ok_button = msg_box.addButton(QtWidgets.QMessageBox.StandardButton.Ok)
32
+
33
+ msg_box.exec()
34
+
35
+ return msg_box.clickedButton() == retry_button
@@ -0,0 +1,112 @@
1
+ import os
2
+ import random
3
+ import sys
4
+
5
+ import pyfiglet
6
+ import pyjokes
7
+ import qdarkstyle
8
+ import qdarktheme
9
+
10
+ from microEye import __version__
11
+ from microEye.qt import QT_API, QApplication, QtCore, QtGui, QtWidgets
12
+
13
+
14
+ def splash(module):
15
+ fonts = [
16
+ # 'ansi_shadow',
17
+ # 'big',
18
+ # 'doom',
19
+ # 'slant',
20
+ 'small',
21
+ 'small_slant',
22
+ 'smslant',
23
+ # 'standard',
24
+ # 'stop',
25
+ ]
26
+ print(
27
+ pyfiglet.Figlet(font=random.choice(fonts), width=250).renderText(
28
+ f'MicroEye v{__version__}\n{module}'
29
+ )
30
+ )
31
+
32
+
33
+ QSS = '''
34
+ ParameterControlledButton {
35
+ font-weight: 600;
36
+ min-width: 100px;
37
+ min-height: 20px;
38
+ }
39
+ QPushButton {
40
+ padding: 4px;
41
+ font-weight: 600;
42
+ }
43
+ QDockWidget {
44
+ font-weight: 600;
45
+ font-size: 11pt;
46
+ }
47
+ microLauncher {
48
+ border: 1px solid white;
49
+ padding: 0;
50
+ }
51
+ QPushButton#closeButton {
52
+ border: none;
53
+ }
54
+ '''
55
+
56
+
57
+ def StartGUI(cls: type, *args, **kwargs):
58
+ '''Initializes a new QApplication and module.
59
+
60
+ Use
61
+ -------
62
+ app, window = StartGUI(module_type)
63
+
64
+
65
+ app.exec()
66
+
67
+ Returns
68
+ -------
69
+ tuple (QApplication, module)
70
+ Returns a tuple with QApp and module window.
71
+ '''
72
+ splash(cls.__name__)
73
+ print(pyjokes.get_joke())
74
+ # create a QApp
75
+ QtWidgets.QApplication.setAttribute(
76
+ QtCore.Qt.ApplicationAttribute.AA_ShareOpenGLContexts
77
+ )
78
+ app = QApplication(sys.argv)
79
+ # set darkmode from *qdarkstyle* (not compatible with pyqt6)
80
+ # Additional stylesheet
81
+
82
+ theme = os.environ.get('MITHEME', default=None)
83
+ if theme is not None:
84
+ if theme == 'qdarkstyle':
85
+ app.setStyleSheet(qdarkstyle.load_stylesheet(qt_api=QT_API))
86
+ elif theme == 'qdarktheme':
87
+ qdarktheme.setup_theme(additional_qss=QSS)
88
+ elif theme in QtWidgets.QStyleFactory.keys(): # noqa: SIM118
89
+ app.setStyle(theme)
90
+
91
+ # sets the app icon
92
+ dirname = os.path.dirname(os.path.abspath(__file__))
93
+ app_icon = QtGui.QIcon()
94
+ app_icon.addFile(os.path.join(dirname, '../icons/16.png'), QtCore.QSize(16, 16))
95
+ app_icon.addFile(os.path.join(dirname, '../icons/24.png'), QtCore.QSize(24, 24))
96
+ app_icon.addFile(os.path.join(dirname, '../icons/32.png'), QtCore.QSize(32, 32))
97
+ app_icon.addFile(os.path.join(dirname, '../icons/48.png'), QtCore.QSize(48, 48))
98
+ app_icon.addFile(os.path.join(dirname, '../icons/64.png'), QtCore.QSize(64, 64))
99
+ app_icon.addFile(os.path.join(dirname, '../icons/128.png'), QtCore.QSize(128, 128))
100
+ app_icon.addFile(os.path.join(dirname, '../icons/256.png'), QtCore.QSize(256, 256))
101
+ app_icon.addFile(os.path.join(dirname, '../icons/512.png'), QtCore.QSize(512, 512))
102
+
103
+ app.setWindowIcon(app_icon)
104
+
105
+ if sys.platform.startswith('win'):
106
+ import ctypes
107
+
108
+ myappid = f'samhitech.mircoEye.{cls.__name__}' # appid
109
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
110
+
111
+ window = cls(*args)
112
+ return app, window
@@ -0,0 +1,115 @@
1
+ import sys
2
+ import threading
3
+ import traceback
4
+
5
+ import numpy as np
6
+
7
+ from microEye.qt import QtCore, Signal, Slot
8
+
9
+
10
+ class WorkerSignals(QtCore.QObject):
11
+ '''
12
+ Defines the signals available from a running worker thread.
13
+
14
+ Supported signals are:
15
+ - finished: No data
16
+ - error: tuple (exctype, value, traceback.format_exc())
17
+ - result: object data returned from processing, anything
18
+ - progress: object for progress updates
19
+ '''
20
+ finished = Signal()
21
+ error = Signal(tuple)
22
+ result = Signal(object)
23
+ progress = Signal(object)
24
+
25
+
26
+ class QThreadWorker(QtCore.QRunnable):
27
+ '''Thread worker
28
+
29
+ Inherits from QRunnable to handler worker thread setup,
30
+ signals and wrap-up.
31
+
32
+ Parameters
33
+ ----------
34
+ callback : function
35
+ The function callback to run on this worker thread. Supplied args and
36
+ kwargs will be passed through to the runner.
37
+ args:
38
+ Arguments to pass to the callback function
39
+ kwargs:
40
+ Keywords to pass to the callback function
41
+ '''
42
+
43
+ def __init__(self, fn, *args, **kwargs):
44
+ super().__init__()
45
+
46
+ # Store constructor arguments (re-used for processing)
47
+ self.fn = fn
48
+ self.args = args
49
+ self.kwargs = kwargs
50
+ self.signals = WorkerSignals()
51
+ self.stop_event = threading.Event()
52
+ self._started = False
53
+ self._done = False
54
+
55
+ self.kwargs['event'] = self.stop_event
56
+
57
+ # Add the callback to our kwargs
58
+ if kwargs.get('progress'):
59
+ self.kwargs['progress_callback'] = self.signals.progress
60
+
61
+ @property
62
+ def done(self) -> bool:
63
+ return self._done
64
+
65
+ @property
66
+ def started(self) -> bool:
67
+ return self._started
68
+
69
+ @Slot()
70
+ def run(self):
71
+ '''Initialise the runner function with passed args, kwargs.'''
72
+ self._started = True
73
+ # Retrieve args/kwargs here; and fire processing using them
74
+ try:
75
+ if self.kwargs.get('nokwargs'):
76
+ result = self.fn(*self.args)
77
+ else:
78
+ result = self.fn(*self.args, **self.kwargs)
79
+ except Exception:
80
+ traceback.print_exc()
81
+ exctype, value = sys.exc_info()[:2]
82
+ self.signals.error.emit((exctype, value, traceback.format_exc()))
83
+ else:
84
+ # Return the result of the processing
85
+ self.signals.result.emit(result)
86
+ finally:
87
+ self.stop_event.set()
88
+ self._done = True
89
+ self.signals.finished.emit() # Done
90
+
91
+ def stop(self):
92
+ '''
93
+ Request the worker to stop.
94
+ '''
95
+ self.stop_event.set()
96
+
97
+ def is_set(self):
98
+ '''
99
+ Check if the stop has been requested.
100
+ '''
101
+ return self.stop_event.is_set()
102
+
103
+ def wait(self, timeout=None):
104
+ '''
105
+ Wait until the worker has finished or the stop has been requested.
106
+
107
+ Parameters
108
+ ----------
109
+ timeout : float or None
110
+ The maximum number of seconds to wait. If None, waits indefinitely.
111
+ '''
112
+ if self._started:
113
+ self.stop_event.wait(timeout)
114
+ else:
115
+ raise RuntimeError('Cannot wait on a worker that has not been started')