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