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,568 @@
1
+ import json
2
+ import os
3
+ import webbrowser
4
+ from enum import Enum
5
+ from typing import Optional, Union
6
+
7
+ import cv2
8
+
9
+ from microEye.analysis.fitting.results import FittingResults
10
+ from microEye.analysis.viewer import LocalizationsView, PSFView, StackView
11
+ from microEye.qt import (
12
+ QT_API,
13
+ QAction,
14
+ QApplication,
15
+ QDateTime,
16
+ QFileSystemModel,
17
+ QIcon,
18
+ QMainWindow,
19
+ Qt,
20
+ QtCore,
21
+ QtWidgets,
22
+ Slot,
23
+ )
24
+ from microEye.utils import StartGUI
25
+
26
+
27
+ class DockKeys(Enum):
28
+ FILE_SYSTEM = 'File System'
29
+ SMLM_ANALYSIS = 'SMLM Analysis'
30
+ DATA_FILTERS = 'Data Filters'
31
+
32
+
33
+ class CustomFileSystemModel(QFileSystemModel):
34
+ def __init__(self, parent=None):
35
+ super().__init__(parent)
36
+
37
+ def filterAcceptsRow(self, source_row, source_parent):
38
+ index = self.index(source_row, 0, source_parent)
39
+ if not index.isValid():
40
+ return False
41
+
42
+ # Get the file/folder name and path
43
+ file_info = self.fileInfo(index)
44
+ file_name = file_info.fileName()
45
+ parent_path = file_info.absolutePath()
46
+
47
+ # Check if the parent directory is a .zarr folder
48
+ if parent_path.endswith('.zarr'):
49
+ return False # Do not show any files within a .zarr folder
50
+
51
+ # Check if the current directory is a .zarr folder
52
+ if file_info.isDir() and file_name.endswith('.zarr'):
53
+ return True # Always accept .zarr folders themselves
54
+
55
+ # Use the default filtering behavior for other files/folders
56
+ return super().filterAcceptsRow(source_row, source_parent)
57
+
58
+ def isDir(self, index):
59
+ # Override the isDir method to treat .zarr folders as files
60
+ file_info = self.fileInfo(index)
61
+ if file_info.isDir() and file_info.fileName().endswith('.zarr'):
62
+ return False # Treat .zarr folders as files
63
+ return super().isDir(index)
64
+
65
+ def data(self, index, role):
66
+ # Optionally, customize the display role to show the .zarr folders as files
67
+ file_info = self.fileInfo(index)
68
+ if (
69
+ role == QFileSystemModel.Roles.FileIconRole
70
+ and file_info.fileName().endswith('.zarr')
71
+ ):
72
+ # Get the icon for a ZIP file (or any compressed file)
73
+ zip_icon = QIcon.fromTheme('application-zip') # On Linux
74
+ if zip_icon.isNull():
75
+ zip_icon = self.iconProvider().icon(
76
+ QtCore.QFileInfo('dummy.zip')
77
+ ) # Fallback for Windows/Mac
78
+ return zip_icon # Return ZIP icon for .zarr folders
79
+
80
+ return super().data(index, role)
81
+
82
+ def hasChildren(self, index):
83
+ # Ensure that .zarr folders don't expand by showing no children
84
+ file_info = self.fileInfo(index)
85
+ if file_info.isDir() and file_info.fileName().endswith('.zarr'):
86
+ return False # Treat .zarr folders as files with no children
87
+ return super().hasChildren(index)
88
+
89
+
90
+ class multi_viewer(QMainWindow):
91
+ def __init__(self, path=None):
92
+ super().__init__()
93
+ # Set window properties
94
+ self.title = 'Multi Viewer Module'
95
+ self.left = 0
96
+ self.top = 0
97
+ self._width = 1600
98
+ self._height = 950
99
+ self._zoom = 1
100
+ self._n_levels = 4096
101
+
102
+ # Threading
103
+ self._threadpool = QtCore.QThreadPool.globalInstance()
104
+ print(
105
+ f'Multithreading with maximum {self._threadpool.maxThreadCount()} threads'
106
+ )
107
+
108
+ # Set the path
109
+ if path is None:
110
+ path = os.path.dirname(os.path.abspath(__package__))
111
+ self.initialize(path)
112
+
113
+ # Set up the status bar
114
+ self.status()
115
+
116
+ # Status Bar Timer
117
+ self.timer = QtCore.QTimer(self)
118
+ self.timer.timeout.connect(self.status)
119
+ self.timer.start(200)
120
+
121
+ # Set main window properties
122
+ self.setStatusBar(self.statusBar())
123
+
124
+ def initialize(self, path):
125
+ # Set Title / Dimensions / Center Window
126
+ self.setWindowTitle(self.title)
127
+ self.setGeometry(self.left, self.top, self._width, self._height)
128
+
129
+ # Define main window layout
130
+ self.setupMainWindowLayout()
131
+
132
+ # Initialize the file system model / tree
133
+ self.setupFileSystemTab(path)
134
+
135
+ # Tabify docks
136
+ self.tabifyDocks()
137
+
138
+ # Set tab positions
139
+ self.setTabPositions()
140
+
141
+ # Raise docks
142
+ self.raiseDocks()
143
+
144
+ # Create menu bar
145
+ self.createMenuBar()
146
+
147
+ self.show()
148
+ self.center()
149
+
150
+ def setupMainWindowLayout(self):
151
+ # # Create the MDI area
152
+ self.mdi_area = QtWidgets.QMdiArea()
153
+ self.mdi_area.setViewMode(QtWidgets.QMdiArea.ViewMode.TabbedView)
154
+ self.mdi_area.setTabsClosable(True)
155
+ self.mdi_area.setTabsMovable(True)
156
+ self.mdi_area.setBackground(Qt.GlobalColor.transparent)
157
+ tabs = self.mdi_area.findChild(QtWidgets.QTabBar)
158
+ tabs.setExpanding(False)
159
+
160
+ self.setCentralWidget(self.mdi_area)
161
+
162
+ self.docks: dict[
163
+ str, QtWidgets.QDockWidget
164
+ ] = {} # Dictionary to store created docks
165
+ self.layouts = {}
166
+
167
+ def setupFileSystemTab(self, path):
168
+ # Tiff File system tree viewer tab layout
169
+ self.file_tree_layout = self.create_tab(
170
+ DockKeys.FILE_SYSTEM,
171
+ QtWidgets.QVBoxLayout,
172
+ 'LeftDockWidgetArea',
173
+ widget=None,
174
+ )
175
+
176
+ self.path = path
177
+
178
+ # Create QFileSystemModel
179
+ self.model = CustomFileSystemModel()
180
+ self.model.setRootPath(path)
181
+ self.model.setFilter(
182
+ QtCore.QDir.Filter.AllDirs
183
+ | QtCore.QDir.Filter.Files
184
+ | QtCore.QDir.Filter.NoDotAndDotDot
185
+ )
186
+ self.model.setNameFilters(['*.tif', '*.tiff', '*.tsv', '*.h5'])
187
+ self.model.setNameFilterDisables(False)
188
+
189
+ # Create QTreeView
190
+ self.tree = QtWidgets.QTreeView()
191
+ self.tree.setModel(self.model)
192
+ self.tree.setRootIndex(self.model.index(path))
193
+ self.tree.setAnimated(False)
194
+ self.tree.setIndentation(20)
195
+ self.tree.setSortingEnabled(False)
196
+ self.tree.hideColumn(1)
197
+ self.tree.hideColumn(2)
198
+ self.tree.hideColumn(3)
199
+ self.tree.setWindowTitle('Dir View')
200
+ self.tree.setMinimumWidth(400)
201
+ self.tree.resize(512, 256)
202
+
203
+ # Connect double-click signal to a method that uses QTimer
204
+ self.tree.doubleClicked.connect(self._open_file)
205
+
206
+ # Add the File system tab contents
207
+ self.imsq_pattern = QtWidgets.QLineEdit('/image_0*.ome.tif')
208
+
209
+ self.file_tree_layout.addWidget(QtWidgets.QLabel('Image Sequence pattern:'))
210
+ self.file_tree_layout.addWidget(self.imsq_pattern)
211
+ self.file_tree_layout.addWidget(self.tree)
212
+
213
+ def tabifyDocks(self):
214
+ # Tabify docks
215
+ pass
216
+
217
+ def setTabPositions(self):
218
+ self.setTabPosition(
219
+ Qt.DockWidgetArea.LeftDockWidgetArea, QtWidgets.QTabWidget.TabPosition.East
220
+ )
221
+ self.setTabPosition(
222
+ Qt.DockWidgetArea.RightDockWidgetArea, QtWidgets.QTabWidget.TabPosition.West
223
+ )
224
+
225
+ def raiseDocks(self):
226
+ # Raise docks
227
+ self.docks[DockKeys.FILE_SYSTEM].raise_()
228
+
229
+ def createMenuBar(self):
230
+ menu_bar = self.menuBar()
231
+
232
+ # Create file menu
233
+ file_menu = menu_bar.addMenu('File')
234
+ view_menu = menu_bar.addMenu('View')
235
+ help_menu = menu_bar.addMenu('Help')
236
+
237
+ # Create exit action
238
+ save_config = QAction('Save Config.', self)
239
+ save_config.triggered.connect(lambda: saveConfig(self))
240
+ load_config = QAction('Load Config.', self)
241
+ load_config.triggered.connect(lambda: loadConfig(self))
242
+
243
+ github = QAction('microEye Github', self)
244
+ github.triggered.connect(
245
+ lambda: webbrowser.open('https://github.com/samhitech/microEye')
246
+ )
247
+ pypi = QAction('microEye PYPI', self)
248
+ pypi.triggered.connect(
249
+ lambda: webbrowser.open('https://pypi.org/project/microEye/')
250
+ )
251
+
252
+ # Add exit action to file menu
253
+ file_menu.addAction(save_config)
254
+ file_menu.addAction(load_config)
255
+
256
+ def connect(action: QAction, dock: QtWidgets.QDockWidget):
257
+ action.triggered.connect(lambda: dock.setVisible(action.isChecked()))
258
+
259
+ # Create toggle view actions for each dock
260
+ dock_toggle_actions = {}
261
+ for key, dock in self.docks.items():
262
+ toggle_action = dock.toggleViewAction()
263
+ toggle_action.setEnabled(True)
264
+ dock_toggle_actions[key] = toggle_action
265
+ view_menu.addAction(toggle_action)
266
+ if '6' in QT_API:
267
+ connect(toggle_action, dock)
268
+
269
+ help_menu.addAction(github)
270
+ help_menu.addAction(pypi)
271
+
272
+ def create_tab(
273
+ self,
274
+ key: DockKeys,
275
+ layout_type: Optional[
276
+ type[Union[QtWidgets.QVBoxLayout, QtWidgets.QFormLayout]]
277
+ ] = None,
278
+ dock_area: str = 'LeftDockWidgetArea',
279
+ widget: Optional[QtWidgets.QWidget] = None,
280
+ visible: bool = True,
281
+ ) -> Optional[type[Union[QtWidgets.QVBoxLayout, QtWidgets.QFormLayout]]]:
282
+ '''
283
+ Create a tab with a dock widget.
284
+
285
+ Parameters
286
+ ----------
287
+ key : DockKeys
288
+ The unique identifier for the tab.
289
+ layout_type : Optional[Type[Union[QVBoxLayout, QFormLayout]]], optional
290
+ The layout type for the group in the dock. If provided,
291
+ widget should be None.
292
+ dock_area : str, optional
293
+ The dock area where the tab will be added.
294
+ widget : Optional[QWidget], optional
295
+ The widget to be placed in the dock. If provided,
296
+ layout_type should be None.
297
+ visible : bool, optional
298
+ Whether the dock widget should be visible.
299
+
300
+ Returns
301
+ -------
302
+ Optional[Type[Union[QVBoxLayout, QFormLayout]]]
303
+ The layout if layout_type is provided, otherwise None.
304
+ '''
305
+ if widget:
306
+ group = widget
307
+ else:
308
+ group = QtWidgets.QWidget()
309
+ group_layout = layout_type() if layout_type else QtWidgets.QVBoxLayout()
310
+ group.setLayout(group_layout)
311
+ self.layouts[key] = group_layout
312
+
313
+ dock = QtWidgets.QDockWidget(str(key.value), self)
314
+ dock.setFeatures(
315
+ QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable
316
+ | QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable
317
+ )
318
+ dock.setWidget(group)
319
+ dock.setVisible(visible)
320
+ self.addDockWidget(getattr(Qt.DockWidgetArea, dock_area), dock)
321
+
322
+ # Store the dock in the dictionary
323
+ self.docks[key] = dock
324
+
325
+ # Return the layout if layout_type is provided, otherwise None
326
+ return None if widget else group_layout
327
+
328
+ def center(self):
329
+ '''Centers the window within the screen using setGeometry.'''
330
+ # Get the screen geometry
331
+ screen_geometry = QApplication.primaryScreen().availableGeometry()
332
+
333
+ # Calculate the center point
334
+ center_point = screen_geometry.center()
335
+
336
+ # Set the window geometry
337
+ self.setGeometry(
338
+ center_point.x() - self.width() // 2,
339
+ center_point.y() - self.height() // 2,
340
+ self.width(),
341
+ self.height(),
342
+ )
343
+
344
+ def status(self):
345
+ # Statusbar time
346
+ self.statusBar().showMessage(
347
+ f'{QT_API} | '
348
+ + 'Time: '
349
+ + QDateTime.currentDateTime().toString('hh:mm:ss,zzz')
350
+ )
351
+
352
+ @Slot(QtCore.QModelIndex)
353
+ def _open_file(self, index: QtCore.QModelIndex):
354
+ # Set the Qt.WindowFlags for making the subwindow resizable
355
+ view = None
356
+
357
+ path = self.model.filePath(index)
358
+
359
+ if not os.path.isdir(path):
360
+ if path.endswith('.tif') or path.endswith('.tiff'):
361
+ view = StackView(path, None)
362
+ view.localizedData.connect(self.localizedData)
363
+ elif (
364
+ path.endswith('.h5') and not path.endswith('.psf.h5')
365
+ ) or path.endswith('.tsv'):
366
+ results = FittingResults.fromFile(path, 1)
367
+ if results is not None:
368
+ view = LocalizationsView(results)
369
+ print('Done importing results.')
370
+ else:
371
+ print('Error importing results.')
372
+ elif path.endswith('.psf.h5'):
373
+ view = PSFView(path)
374
+ else:
375
+ if path.endswith('.zarr'):
376
+ view = StackView(path)
377
+ else:
378
+ try:
379
+ view = StackView(path, self.imsq_pattern.text())
380
+ except Exception as e:
381
+ print(f'Error opening image sequence: {e}')
382
+ return
383
+
384
+ view.localizedData.connect(self.localizedData)
385
+
386
+ if view:
387
+ window = self.mdi_area.addSubWindow(view)
388
+ window.show()
389
+
390
+ def localizedData(self, path):
391
+ index = self.model.index(path)
392
+ self._open_file(index)
393
+
394
+ def StartGUI(path=None):
395
+ '''
396
+ Initializes a new QApplication and multi_viewer.
397
+
398
+ Parameters
399
+ ----------
400
+ path : str, optional
401
+ The path to a file to be loaded initially.
402
+
403
+ Returns
404
+ -------
405
+ tuple of QApplication and multi_viewer
406
+ Returns a tuple with QApplication and multi_viewer main window.
407
+ '''
408
+ return StartGUI(multi_viewer, path)
409
+
410
+
411
+ def get_dock_config(dock: QtWidgets.QDockWidget):
412
+ '''
413
+ Get the configuration dictionary for a QDockWidget.
414
+
415
+ Parameters
416
+ ----------
417
+ dock : QDockWidget
418
+ The QDockWidget to get the configuration for.
419
+
420
+ Returns
421
+ -------
422
+ dict
423
+ The configuration dictionary containing isFloating,
424
+ position, size, and isVisible.
425
+ '''
426
+ if dock:
427
+ return {
428
+ 'isFloating': dock.isFloating(),
429
+ 'position': (
430
+ dock.mapToGlobal(QtCore.QPoint(0, 0)).x(),
431
+ dock.mapToGlobal(QtCore.QPoint(0, 0)).y(),
432
+ ),
433
+ 'size': (dock.geometry().width(), dock.geometry().height()),
434
+ 'isVisible': dock.isVisible(),
435
+ }
436
+
437
+
438
+ def get_widget_config(widget: QtWidgets.QWidget):
439
+ '''
440
+ Get the configuration dictionary for a QWidget.
441
+
442
+ Parameters
443
+ ----------
444
+ widget : QWidget
445
+ The QWidget to get the configuration for.
446
+
447
+ Returns
448
+ -------
449
+ dict
450
+ The configuration dictionary containing position, size, and isMaximized.
451
+ '''
452
+ if widget:
453
+ return {
454
+ 'position': (
455
+ widget.mapToGlobal(QtCore.QPoint(0, 0)).x(),
456
+ widget.mapToGlobal(QtCore.QPoint(0, 0)).y(),
457
+ ),
458
+ 'size': (widget.geometry().width(), widget.geometry().height()),
459
+ 'isMaximized': widget.isMaximized(),
460
+ }
461
+
462
+
463
+ def saveConfig(window: multi_viewer, filename: str = 'config_tiff.json'):
464
+ """
465
+ Save the configuration for the multi_viewer application.
466
+
467
+ Parameters
468
+ ----------
469
+ window : multi_viewer
470
+ The main application window.
471
+ filename : str, optional
472
+ The filename of the configuration file, by default 'config_tiff.json'.
473
+ """
474
+ config = dict()
475
+
476
+ # Save multi_viewer widget config
477
+ config['multi_viewer'] = get_widget_config(window)
478
+
479
+ # Save docks config
480
+ for key in DockKeys:
481
+ dock = window.docks.get(key)
482
+ if dock:
483
+ config[key.value] = get_dock_config(dock)
484
+
485
+ with open(filename, 'w') as file:
486
+ json.dump(config, file, indent=2)
487
+
488
+ print(f'{filename} file generated!')
489
+
490
+
491
+ def load_widget_config(widget: QtWidgets.QWidget, widget_config):
492
+ '''
493
+ Load configuration for a QWidget.
494
+
495
+ Parameters
496
+ ----------
497
+ widget : QWidget
498
+ The QWidget to apply the configuration to.
499
+ widget_config : dict
500
+ The configuration dictionary containing position, size, and maximized status.
501
+
502
+ Returns
503
+ -------
504
+ None
505
+ '''
506
+ widget.setGeometry(
507
+ widget_config['position'][0],
508
+ widget_config['position'][1],
509
+ widget_config['size'][0],
510
+ widget_config['size'][1],
511
+ )
512
+ if bool(widget_config['isMaximized']):
513
+ widget.showMaximized()
514
+
515
+
516
+ def loadConfig(window: multi_viewer, filename: str = 'config_tiff.json'):
517
+ """
518
+ Load the configuration for the multi_viewer application.
519
+
520
+ Parameters
521
+ ----------
522
+ window : multi_viewer
523
+ The main application window.
524
+ filename : str, optional
525
+ The filename of the configuration file, by default 'config_tiff.json'.
526
+ """
527
+ if not os.path.exists(filename):
528
+ print(f'{filename} not found!')
529
+ return
530
+
531
+ config: dict = None
532
+
533
+ with open(filename) as file:
534
+ config = json.load(file)
535
+
536
+ # Loading multi_viewer widget config
537
+ if 'multi_viewer' in config:
538
+ load_widget_config(window, config['multi_viewer'])
539
+
540
+ # Loading docks
541
+ for dock_key, dock_config in config.items():
542
+ dock_enum_key = None
543
+ try:
544
+ dock_enum_key = DockKeys(dock_key)
545
+ except ValueError:
546
+ # Skip processing if dock_key is not a valid DockKeys enum
547
+ continue
548
+
549
+ if dock_enum_key in window.docks:
550
+ dock = window.docks[dock_enum_key]
551
+ dock.setVisible(bool(dock_config.get('isVisible', False)))
552
+ if bool(dock_config.get('isFloating', False)):
553
+ dock.setFloating(True)
554
+ dock.setGeometry(
555
+ dock_config.get('position', (0, 0))[0],
556
+ dock_config.get('position', (0, 0))[1],
557
+ dock_config.get('size', (0, 0))[0],
558
+ dock_config.get('size', (0, 0))[1],
559
+ )
560
+ else:
561
+ dock.setFloating(False)
562
+
563
+ print(f'{filename} file loaded!')
564
+
565
+
566
+ if __name__ == '__main__':
567
+ app, window = multi_viewer.StartGUI('')
568
+ app.exec()
@@ -0,0 +1 @@
1
+ from microEye.analysis.processing.frc import FRC_resolution_binomial, plot_frc
@@ -0,0 +1,120 @@
1
+ import cv2
2
+ import numba
3
+ import numpy as np
4
+ import pyqtgraph as pg
5
+ from scipy.interpolate import interp1d
6
+ from scipy.signal import savgol_filter
7
+
8
+ from microEye.analysis.rendering import BaseRenderer
9
+ from microEye.analysis.utils import *
10
+
11
+
12
+ def FRC_compute(
13
+ fft_12: np.ndarray, fft_11: np.ndarray, fft_22: np.ndarray, FRC_res, R, R_max
14
+ ):
15
+ R = R.reshape(-1)
16
+ argsort = np.argsort(R)
17
+ R = R[argsort]
18
+ fft_12 = fft_12.reshape(-1)[argsort]
19
+ fft_11 = fft_11.reshape(-1)[argsort]
20
+ fft_22 = fft_22.reshape(-1)[argsort]
21
+ ids = np.cumsum(np.bincount(R.astype(np.int64)))
22
+ for idx in range(1, R_max + 1):
23
+ a = np.sum(fft_12[ids[idx - 1] : ids[idx]])
24
+ b = np.sum(fft_11[ids[idx - 1] : ids[idx]])
25
+ c = np.sum(fft_22[ids[idx - 1] : ids[idx]])
26
+ FRC_res[idx - 1] = a / np.sqrt(b * c)
27
+
28
+
29
+ def FRC_resolution_binomial(data: np.ndarray, pixel_size=10, method='Binomial'):
30
+ '''Fourier Ring Correlation based on
31
+ https://www.frontiersin.org/articles/10.3389/fbinf.2021.817254/
32
+
33
+ Parameters
34
+ ----------
35
+ data : np.ndarray
36
+ data to be resolution estimated, columns (X, Y, intensity)
37
+ pixelSize : int, optional
38
+ super resolution image pixel size in nanometers, by default 10
39
+ '''
40
+ n_points = data.shape[0]
41
+
42
+ # Split data based on method
43
+ if 'Binomial' in method:
44
+ coin = np.random.binomial(1, 0.5, (n_points))
45
+ data1, data2 = data[coin == 0], data[coin == 1]
46
+ elif 'Odd/Even' in method:
47
+ data1, data2 = data[::2], data[1::2]
48
+ elif 'Halves' in method:
49
+ data1, data2 = data[: data.shape[0] // 2], data[data.shape[0] // 2 :]
50
+
51
+ # Generate and process images
52
+ hist = BaseRenderer(pixel_size)
53
+ image_1 = hist.from_array(data1)
54
+ image_2 = hist.from_array(data2)
55
+
56
+ image_1, image_2 = match_shape(image_1, image_2)
57
+ image_1 /= np.sum(image_1)
58
+ image_2 /= np.sum(image_2)
59
+
60
+ window = hamming_2Dwindow(image_1.shape[0])
61
+ image_1 *= window
62
+ image_2 *= window
63
+
64
+ # Compute FFT
65
+ fft_1 = cv2.dft(np.float64(image_1), flags=cv2.DFT_COMPLEX_OUTPUT)
66
+ fft_1 = fft_1[..., 0] + 1j * fft_1[..., 1]
67
+ fft_2 = cv2.dft(np.float64(image_2), flags=cv2.DFT_COMPLEX_OUTPUT)
68
+ fft_2 = fft_2[..., 0] + 1j * fft_2[..., 1]
69
+
70
+ fft_12 = np.fft.fftshift(np.real(fft_1 * np.conj(fft_2)))
71
+ fft_11 = np.fft.fftshift(np.abs(fft_1) ** 2)
72
+ fft_22 = np.fft.fftshift(np.abs(fft_2) ** 2)
73
+
74
+ # Process results
75
+ R, _ = radial_coordinate(image_1.shape)
76
+ R = np.round(R)
77
+ frequencies = np.fft.rfftfreq(R.shape[0], d=pixel_size)
78
+ R_max = frequencies.shape[0]
79
+ FRC_res = np.zeros(R_max)
80
+
81
+ FRC_compute(fft_12, fft_11, fft_22, FRC_res, R, R_max)
82
+
83
+ # Interpolate and smooth results
84
+ interpy = interp1d(frequencies, FRC_res, kind='cubic', fill_value='extrapolate')
85
+ FRC = interpy(frequencies)
86
+
87
+ window_length = min(len(FRC_res) // 20, 101)
88
+ window_length = window_length if window_length % 2 == 1 else window_length + 1
89
+ smoothed = savgol_filter(FRC_res, window_length, 3)
90
+
91
+ idx = np.where(smoothed <= (1 / 7))[0]
92
+ if idx is not None and len(idx) > 0:
93
+ idx = idx.min()
94
+ FRC_res = 1 / frequencies[idx]
95
+ else:
96
+ FRC_res = np.nan
97
+
98
+ return frequencies, FRC, smoothed, FRC_res
99
+
100
+ def plot_frc(frequencies, FRC, smoothed, FRC_res):
101
+ plt = pg.plot()
102
+ plt.showGrid(x=True, y=True)
103
+ legend = plt.addLegend()
104
+ legend.anchor(itemPos=(1, 0), parentPos=(1, 0))
105
+
106
+ plt.setLabel('left', 'FRC', units='')
107
+ plt.setLabel('bottom', 'Spatial Frequency [1/nm]', units='')
108
+ plt.setWindowTitle(f'FRC resolution: {np.round(FRC_res, 1)} nm')
109
+ plt.setXRange(0, frequencies[-1])
110
+ plt.setYRange(0, 1)
111
+
112
+ plt.plot(
113
+ frequencies, FRC,
114
+ pen=pg.mkPen('r', width=2), symbol=None, symbolPen='r',
115
+ symbolBrush=0, name='FRC')
116
+ plt.plot(
117
+ frequencies, smoothed,
118
+ pen=pg.mkPen('b', width=2), symbol=None, symbolPen='b',
119
+ symbolBrush=0, name='FRC smoothed')
120
+ plt.plotItem.addLine(y=1/7, pen='y')
@@ -0,0 +1,3 @@
1
+ from microEye.analysis.rendering.base import BaseRenderer, Projection, RenderModes
2
+ from microEye.analysis.rendering.cloud import PointCloudRenderer
3
+ from microEye.analysis.rendering.volumetric import VolumeRenderer