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