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,680 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, Optional, Union
|
|
5
|
+
|
|
6
|
+
import cv2
|
|
7
|
+
import numpy as np
|
|
8
|
+
from tqdm import tqdm
|
|
9
|
+
|
|
10
|
+
from microEye.analysis.filters import *
|
|
11
|
+
from microEye.analysis.fitting.phasor_fit import *
|
|
12
|
+
from microEye.analysis.fitting.pyfit3Dcspline import (
|
|
13
|
+
CPUmleFit_LM,
|
|
14
|
+
get_roi_list,
|
|
15
|
+
get_roi_list_CMOS,
|
|
16
|
+
)
|
|
17
|
+
from microEye.analysis.fitting.results import *
|
|
18
|
+
from microEye.qt import Qt, QtWidgets, Signal
|
|
19
|
+
from microEye.utils.uImage import TiffSeqHandler, ZarrImageSequence, uImage
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_blob_detector(
|
|
23
|
+
params: cv2.SimpleBlobDetector_Params = None,
|
|
24
|
+
) -> cv2.SimpleBlobDetector:
|
|
25
|
+
if params is None:
|
|
26
|
+
# Setup SimpleBlobDetector parameters.
|
|
27
|
+
params = cv2.SimpleBlobDetector_Params()
|
|
28
|
+
|
|
29
|
+
params.filterByColor = True
|
|
30
|
+
params.blobColor = 255
|
|
31
|
+
|
|
32
|
+
params.minDistBetweenBlobs = 1
|
|
33
|
+
|
|
34
|
+
# Change thresholds
|
|
35
|
+
# params.minThreshold = 0
|
|
36
|
+
# params.maxThreshold = 255
|
|
37
|
+
|
|
38
|
+
# Filter by Area.
|
|
39
|
+
params.filterByArea = True
|
|
40
|
+
params.minArea = 2.0
|
|
41
|
+
params.maxArea = 80.0
|
|
42
|
+
|
|
43
|
+
# Filter by Circularity
|
|
44
|
+
params.filterByCircularity = False
|
|
45
|
+
# params.minCircularity = 1
|
|
46
|
+
|
|
47
|
+
# Filter by Convexity
|
|
48
|
+
params.filterByConvexity = False
|
|
49
|
+
# params.minConvexity = 1
|
|
50
|
+
|
|
51
|
+
# Filter by Inertia
|
|
52
|
+
params.filterByInertia = False
|
|
53
|
+
# params.minInertiaRatio = 1
|
|
54
|
+
|
|
55
|
+
# Create a detector with the parameters
|
|
56
|
+
ver = (cv2.__version__).split('.')
|
|
57
|
+
if int(ver[0]) < 3:
|
|
58
|
+
return cv2.SimpleBlobDetector(params)
|
|
59
|
+
else:
|
|
60
|
+
return cv2.SimpleBlobDetector_create(params)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AbstractDetector:
|
|
64
|
+
def __init__(self) -> None:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
def find_peaks(self, img: np.ndarray):
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def find_peaks_preview(self, th_img: np.ndarray, img: np.ndarray):
|
|
71
|
+
return None, img
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class CV_BlobDetector(AbstractDetector):
|
|
75
|
+
def __init__(self, **kwargs) -> None:
|
|
76
|
+
'''
|
|
77
|
+
Initialize the CV_BlobDetector instance.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
**kwargs : Any
|
|
82
|
+
Additional keyword arguments to customize blob detector parameters.
|
|
83
|
+
Check CV_BlobDetector.set_blob_detector_params for more info.
|
|
84
|
+
'''
|
|
85
|
+
self.set_blob_detector_params(**kwargs)
|
|
86
|
+
|
|
87
|
+
def set_blob_detector_params(
|
|
88
|
+
self,
|
|
89
|
+
min_threshold=0,
|
|
90
|
+
max_threshold=255,
|
|
91
|
+
minArea=1.5,
|
|
92
|
+
maxArea=80,
|
|
93
|
+
minCircularity=None,
|
|
94
|
+
minConvexity=None,
|
|
95
|
+
minInertiaRatio=None,
|
|
96
|
+
blobColor=255,
|
|
97
|
+
minDistBetweenBlobs=1,
|
|
98
|
+
) -> cv2.SimpleBlobDetector_Params:
|
|
99
|
+
'''
|
|
100
|
+
Set parameters for the blob detector.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
min_threshold : float, optional
|
|
105
|
+
Minimum threshold for blob detection (default: 0).
|
|
106
|
+
max_threshold : float, optional
|
|
107
|
+
Maximum threshold for blob detection (default: 255).
|
|
108
|
+
minArea : float, optional
|
|
109
|
+
Minimum area of blobs (default: 1.5).
|
|
110
|
+
maxArea : float, optional
|
|
111
|
+
Maximum area of blobs (default: 80).
|
|
112
|
+
minCircularity : float or None, optional
|
|
113
|
+
Minimum circularity of blobs. If None,
|
|
114
|
+
circularity filtering is disabled (default: None).
|
|
115
|
+
minConvexity : float or None, optional
|
|
116
|
+
Minimum convexity of blobs. If None,
|
|
117
|
+
convexity filtering is disabled (default: None).
|
|
118
|
+
minInertiaRatio : float or None, optional
|
|
119
|
+
Minimum inertia ratio of blobs. If None,
|
|
120
|
+
inertia ratio filtering is disabled (default: None).
|
|
121
|
+
blobColor : int, optional
|
|
122
|
+
Blob color (default: 255).
|
|
123
|
+
minDistBetweenBlobs : float, optional
|
|
124
|
+
Minimum distance between blobs (default: 0).
|
|
125
|
+
'''
|
|
126
|
+
# Setup SimpleBlobDetector parameters.
|
|
127
|
+
self.params = cv2.SimpleBlobDetector_Params()
|
|
128
|
+
|
|
129
|
+
self.params.filterByColor = True
|
|
130
|
+
self.params.blobColor = blobColor
|
|
131
|
+
|
|
132
|
+
self.params.minDistBetweenBlobs = minDistBetweenBlobs
|
|
133
|
+
|
|
134
|
+
# Change thresholds
|
|
135
|
+
self.params.minThreshold = float(min_threshold)
|
|
136
|
+
self.params.maxThreshold = max_threshold
|
|
137
|
+
|
|
138
|
+
# Filter by Area.
|
|
139
|
+
self.params.filterByArea = True
|
|
140
|
+
self.params.minArea = minArea
|
|
141
|
+
self.params.maxArea = maxArea
|
|
142
|
+
|
|
143
|
+
# Filter by Circularity
|
|
144
|
+
if minCircularity is None:
|
|
145
|
+
self.params.filterByCircularity = False
|
|
146
|
+
# self.params.minCircularity = 0
|
|
147
|
+
else:
|
|
148
|
+
self.params.filterByCircularity = True
|
|
149
|
+
self.params.minCircularity = minCircularity
|
|
150
|
+
|
|
151
|
+
# Filter by Convexity
|
|
152
|
+
if minConvexity is None:
|
|
153
|
+
self.params.filterByConvexity = False
|
|
154
|
+
# self.params.minConvexity = 0
|
|
155
|
+
else:
|
|
156
|
+
self.params.filterByConvexity = True
|
|
157
|
+
self.params.minConvexity = minConvexity
|
|
158
|
+
|
|
159
|
+
# Filter by Inertia
|
|
160
|
+
if minInertiaRatio is None:
|
|
161
|
+
self.params.filterByInertia = False
|
|
162
|
+
# self.params.minInertiaRatio = 0
|
|
163
|
+
else:
|
|
164
|
+
self.params.filterByInertia = True
|
|
165
|
+
self.params.minInertiaRatio = minInertiaRatio
|
|
166
|
+
|
|
167
|
+
self.detector = get_blob_detector(self.params)
|
|
168
|
+
|
|
169
|
+
def find_peaks(self, img: np.ndarray):
|
|
170
|
+
'''
|
|
171
|
+
Find blob peaks in the given image.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
img : numpy.ndarray
|
|
176
|
+
Input image.
|
|
177
|
+
|
|
178
|
+
Returns
|
|
179
|
+
-------
|
|
180
|
+
keypoints : numpy.ndarray
|
|
181
|
+
Array of blob keypoints.
|
|
182
|
+
'''
|
|
183
|
+
keypoints = self.detector.detect(img)
|
|
184
|
+
|
|
185
|
+
return cv2.KeyPoint_convert(keypoints)
|
|
186
|
+
|
|
187
|
+
def find_peaks_preview(self, th_img: np.ndarray, img: np.ndarray):
|
|
188
|
+
'''
|
|
189
|
+
Find blob peaks in the thresholded image and return keypoints and preview image.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
th_img : numpy.ndarray
|
|
194
|
+
Thresholded input image.
|
|
195
|
+
img : numpy.ndarray
|
|
196
|
+
Original input image.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
keypoints : numpy.ndarray
|
|
201
|
+
Array of blob keypoints.
|
|
202
|
+
im_with_keypoints : numpy.ndarray
|
|
203
|
+
Image with keypoints drawn.
|
|
204
|
+
'''
|
|
205
|
+
keypoints = self.detector.detect(th_img)
|
|
206
|
+
|
|
207
|
+
im_with_keypoints = cv2.drawKeypoints(
|
|
208
|
+
img,
|
|
209
|
+
keypoints,
|
|
210
|
+
np.array([]),
|
|
211
|
+
(0, 0, 255),
|
|
212
|
+
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return cv2.KeyPoint_convert(keypoints), im_with_keypoints
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class BlobDetectionWidget(QtWidgets.QGroupBox):
|
|
219
|
+
update = Signal()
|
|
220
|
+
|
|
221
|
+
def __init__(self) -> None:
|
|
222
|
+
super().__init__()
|
|
223
|
+
|
|
224
|
+
self.detector = CV_BlobDetector()
|
|
225
|
+
|
|
226
|
+
self._layout = QtWidgets.QHBoxLayout()
|
|
227
|
+
self.setTitle('OpenCV Blob Approx. Localization')
|
|
228
|
+
self.setLayout(self._layout)
|
|
229
|
+
|
|
230
|
+
self.minArea = QtWidgets.QDoubleSpinBox()
|
|
231
|
+
self.minArea.setMinimum(0)
|
|
232
|
+
self.minArea.setMaximum(1024)
|
|
233
|
+
self.minArea.setSingleStep(0.05)
|
|
234
|
+
self.minArea.setValue(1.5)
|
|
235
|
+
self.minArea.valueChanged.connect(self.value_changed)
|
|
236
|
+
|
|
237
|
+
self.maxArea = QtWidgets.QDoubleSpinBox()
|
|
238
|
+
self.maxArea.setMinimum(0)
|
|
239
|
+
self.maxArea.setMaximum(1024)
|
|
240
|
+
self.maxArea.setSingleStep(0.05)
|
|
241
|
+
self.maxArea.setValue(80.0)
|
|
242
|
+
self.maxArea.valueChanged.connect(self.value_changed)
|
|
243
|
+
|
|
244
|
+
self.minCircularity = QtWidgets.QDoubleSpinBox()
|
|
245
|
+
self.minCircularity.setMinimum(0)
|
|
246
|
+
self.minCircularity.setMaximum(1)
|
|
247
|
+
self.minCircularity.setSingleStep(0.05)
|
|
248
|
+
self.minCircularity.setValue(0)
|
|
249
|
+
self.minCircularity.valueChanged.connect(self.value_changed)
|
|
250
|
+
|
|
251
|
+
self.minConvexity = QtWidgets.QDoubleSpinBox()
|
|
252
|
+
self.minConvexity.setMinimum(0)
|
|
253
|
+
self.minConvexity.setMaximum(1)
|
|
254
|
+
self.minConvexity.setSingleStep(0.05)
|
|
255
|
+
self.minConvexity.setValue(0)
|
|
256
|
+
self.minConvexity.valueChanged.connect(self.value_changed)
|
|
257
|
+
|
|
258
|
+
self.minInertiaRatio = QtWidgets.QDoubleSpinBox()
|
|
259
|
+
self.minInertiaRatio.setMinimum(0)
|
|
260
|
+
self.minInertiaRatio.setMaximum(1)
|
|
261
|
+
self.minInertiaRatio.setSingleStep(0.05)
|
|
262
|
+
self.minInertiaRatio.setValue(0)
|
|
263
|
+
self.minInertiaRatio.valueChanged.connect(self.value_changed)
|
|
264
|
+
|
|
265
|
+
min_label = QtWidgets.QLabel('Min area:')
|
|
266
|
+
max_label = QtWidgets.QLabel('Max area:')
|
|
267
|
+
min_label.setAlignment(Qt.AlignmentFlag.AlignRight)
|
|
268
|
+
max_label.setAlignment(Qt.AlignmentFlag.AlignRight)
|
|
269
|
+
|
|
270
|
+
self._layout.addWidget(min_label)
|
|
271
|
+
self._layout.addWidget(self.minArea)
|
|
272
|
+
self._layout.addWidget(max_label)
|
|
273
|
+
self._layout.addWidget(self.maxArea)
|
|
274
|
+
# self.controls_layout.addWidget(self.minCircularity)
|
|
275
|
+
# self.controls_layout.addWidget(self.minConvexity)
|
|
276
|
+
# self.controls_layout.addWidget(self.minInertiaRatio)
|
|
277
|
+
|
|
278
|
+
def value_changed(self, value):
|
|
279
|
+
self.detector.set_blob_detector_params(
|
|
280
|
+
minArea=self.minArea.value(), maxArea=self.maxArea.value()
|
|
281
|
+
)
|
|
282
|
+
self.update.emit()
|
|
283
|
+
|
|
284
|
+
def get_metadata(self):
|
|
285
|
+
return {
|
|
286
|
+
'name': 'OpenCV Blob Detection',
|
|
287
|
+
'min area': self.minArea.value(),
|
|
288
|
+
'max area': self.maxArea.value(),
|
|
289
|
+
'filter by area': True,
|
|
290
|
+
'other': 'Check default parameters in CV_BlobDetector',
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def pre_process_frame(
|
|
295
|
+
index: int,
|
|
296
|
+
stack_handler: Union['TiffSeqHandler', 'ZarrImageSequence'],
|
|
297
|
+
options: dict[str, Any],
|
|
298
|
+
) -> tuple[np.ndarray, np.ndarray, Optional[tuple[tuple[int, int], tuple[int, int]]]]:
|
|
299
|
+
'''
|
|
300
|
+
Preprocess a frame before localization.
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
index : int
|
|
305
|
+
Index of the frame.
|
|
306
|
+
stack_handler : Union[TiffSeqHandler, ZarrImageSequence]
|
|
307
|
+
Image stack handler.
|
|
308
|
+
options : Dict[str, Any]
|
|
309
|
+
Dictionary of preprocessing options.
|
|
310
|
+
|
|
311
|
+
Returns
|
|
312
|
+
-------
|
|
313
|
+
tuple[np.ndarray, np.ndarray, Optional[tuple[tuple[int, int], tuple[int, int]]]]
|
|
314
|
+
Preprocessed image, filtered image, and ROI information (if applicable).
|
|
315
|
+
'''
|
|
316
|
+
gain = options.get('gain', 1)
|
|
317
|
+
offset = options.get('offset', 0)
|
|
318
|
+
tm_window = options.get('tm_window', 0)
|
|
319
|
+
roi_info = options.get('roi_info')
|
|
320
|
+
c = options.get('channel', 0)
|
|
321
|
+
z = options.get('zSlice', 0)
|
|
322
|
+
|
|
323
|
+
image = stack_handler.getSlice(index, c, z)
|
|
324
|
+
image = image * gain - offset
|
|
325
|
+
|
|
326
|
+
if tm_window > 1:
|
|
327
|
+
temp = TemporalMedianFilter(tm_window)
|
|
328
|
+
frames = temp.getFrames(index, stack_handler)
|
|
329
|
+
filtered = temp.run(image, frames, roi_info)
|
|
330
|
+
else:
|
|
331
|
+
filtered = image.copy()
|
|
332
|
+
|
|
333
|
+
if roi_info is not None:
|
|
334
|
+
origin, dim = roi_info
|
|
335
|
+
slice_y = slice(int(origin[1]), int(origin[1] + dim[1]))
|
|
336
|
+
slice_x = slice(int(origin[0]), int(origin[0] + dim[0]))
|
|
337
|
+
image = image[slice_y, slice_x]
|
|
338
|
+
filtered = filtered[slice_y, slice_x]
|
|
339
|
+
|
|
340
|
+
return image, filtered, roi_info
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def detect_points(
|
|
344
|
+
filtered_image: np.ndarray,
|
|
345
|
+
filter_obj: 'AbstractFilter',
|
|
346
|
+
detector: 'AbstractDetector',
|
|
347
|
+
options: dict[str, Any],
|
|
348
|
+
) -> np.ndarray:
|
|
349
|
+
'''
|
|
350
|
+
Detect points in the filtered image.
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
filtered_image : np.ndarray
|
|
355
|
+
Filtered image.
|
|
356
|
+
filter_obj : AbstractFilter
|
|
357
|
+
Image filter object.
|
|
358
|
+
detector : AbstractDetector
|
|
359
|
+
Point detection algorithm.
|
|
360
|
+
options : Dict[str, Any]
|
|
361
|
+
Detection options.
|
|
362
|
+
|
|
363
|
+
Returns
|
|
364
|
+
-------
|
|
365
|
+
np.ndarray
|
|
366
|
+
Detected points.
|
|
367
|
+
'''
|
|
368
|
+
irange = options.get('irange')
|
|
369
|
+
rel_threshold = options.get('rel_threshold', 0.4)
|
|
370
|
+
max_threshold = options.get('max_threshold', 1.0)
|
|
371
|
+
|
|
372
|
+
uImg = uImage(filtered_image)
|
|
373
|
+
uImg.equalizeLUT(irange, True)
|
|
374
|
+
|
|
375
|
+
if isinstance(filter_obj, BandpassFilter):
|
|
376
|
+
filter_obj._show_filter = False
|
|
377
|
+
filter_obj._refresh = False
|
|
378
|
+
|
|
379
|
+
img = filter_obj.run(uImg._view)
|
|
380
|
+
|
|
381
|
+
_, th_img = cv2.threshold(
|
|
382
|
+
img, np.quantile(img, 1 - 1e-4) * rel_threshold, 255, cv2.THRESH_BINARY
|
|
383
|
+
)
|
|
384
|
+
if max_threshold < 1.0:
|
|
385
|
+
_, th2 = cv2.threshold(
|
|
386
|
+
img, np.max(img) * max_threshold, 1, cv2.THRESH_BINARY_INV
|
|
387
|
+
)
|
|
388
|
+
th_img *= th2
|
|
389
|
+
|
|
390
|
+
return detector.find_peaks(th_img)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def fit_points(
|
|
394
|
+
image: np.ndarray,
|
|
395
|
+
points: np.ndarray,
|
|
396
|
+
varim: Optional[np.ndarray],
|
|
397
|
+
options: dict[str, Any],
|
|
398
|
+
) -> tuple[np.ndarray, Optional[np.ndarray], Optional[np.ndarray]]:
|
|
399
|
+
'''
|
|
400
|
+
Fit detected points using specified method.
|
|
401
|
+
|
|
402
|
+
Parameters
|
|
403
|
+
----------
|
|
404
|
+
image : np.ndarray
|
|
405
|
+
Original image.
|
|
406
|
+
points : np.ndarray
|
|
407
|
+
Detected points.
|
|
408
|
+
varim : Optional[np.ndarray]
|
|
409
|
+
Variance image (if applicable).
|
|
410
|
+
options : Dict[str, Any]
|
|
411
|
+
Fitting options.
|
|
412
|
+
|
|
413
|
+
Returns
|
|
414
|
+
-------
|
|
415
|
+
tuple[np.ndarray, Optional[np.ndarray], Optional[np.ndarray]]
|
|
416
|
+
Fitted parameters, CRLBs, and log-likelihood values.
|
|
417
|
+
'''
|
|
418
|
+
method : FittingMethod = options.get('method', FittingMethod._2D_Phasor_CPU)
|
|
419
|
+
roi_size = options.get('roi_size', 13)
|
|
420
|
+
PSFparam = options.get('PSFparam', np.array([1]))
|
|
421
|
+
|
|
422
|
+
if method == FittingMethod._2D_Phasor_CPU:
|
|
423
|
+
params = phasor_fit(image, points, roi_size=roi_size)
|
|
424
|
+
return params, None, None
|
|
425
|
+
|
|
426
|
+
if varim is None:
|
|
427
|
+
rois, coords = get_roi_list(image, points, roi_size)
|
|
428
|
+
varims = None
|
|
429
|
+
else:
|
|
430
|
+
rois, varims, coords = get_roi_list_CMOS(image, varim, points, roi_size)
|
|
431
|
+
|
|
432
|
+
fit_type = method.value
|
|
433
|
+
|
|
434
|
+
params, crlbs, loglike = CPUmleFit_LM(rois, fit_type, PSFparam, varims, 0)
|
|
435
|
+
params[:, :2] += coords
|
|
436
|
+
|
|
437
|
+
return params, crlbs, loglike
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def localize_frame(
|
|
441
|
+
index: int,
|
|
442
|
+
stack_handler: Union['TiffSeqHandler', 'ZarrImageSequence'],
|
|
443
|
+
filter_obj: 'AbstractFilter',
|
|
444
|
+
detector: 'AbstractDetector',
|
|
445
|
+
options: dict[str, Any],
|
|
446
|
+
) -> tuple[list[int], np.ndarray, Optional[np.ndarray], Optional[np.ndarray]]:
|
|
447
|
+
'''
|
|
448
|
+
Localize features in a frame.
|
|
449
|
+
|
|
450
|
+
Parameters
|
|
451
|
+
----------
|
|
452
|
+
index : int
|
|
453
|
+
Frame index.
|
|
454
|
+
stack_handler : Union[TiffSeqHandler, ZarrImageSequence]
|
|
455
|
+
Image stack handler.
|
|
456
|
+
filter_obj : AbstractFilter
|
|
457
|
+
Image filter object.
|
|
458
|
+
detector : AbstractDetector
|
|
459
|
+
Point detection algorithm.
|
|
460
|
+
options : Dict[str, Any]
|
|
461
|
+
Localization options.
|
|
462
|
+
|
|
463
|
+
Options
|
|
464
|
+
-------
|
|
465
|
+
gain : Union[float, np.ndarray]
|
|
466
|
+
Gain value.
|
|
467
|
+
offset : Union[float, np.ndarray]
|
|
468
|
+
Offset value.
|
|
469
|
+
varim : np.ndarray
|
|
470
|
+
Variance image.
|
|
471
|
+
roi_info : tuple, optional
|
|
472
|
+
Region of interest information ((x,y), (w,h)) and (default is None).
|
|
473
|
+
tm_window : int, optional
|
|
474
|
+
Temporal median filter window size (default is 0).
|
|
475
|
+
irange : tuple[int, int], optional
|
|
476
|
+
Intensity range (default is None for autoscaling).
|
|
477
|
+
rel_threshold : float, optional
|
|
478
|
+
Relative intensity threshold (default is 0.4).
|
|
479
|
+
max_threshold : float, optional
|
|
480
|
+
Maximum intensity threshold (default is 1.0).
|
|
481
|
+
roi_size : int, optional
|
|
482
|
+
Size of the region of interest (default is 13).
|
|
483
|
+
method : FittingMethod, optional
|
|
484
|
+
Fitting method (default is FittingMethod._2D_Phasor_CPU).
|
|
485
|
+
|
|
486
|
+
Returns
|
|
487
|
+
-------
|
|
488
|
+
frames : np.ndarray
|
|
489
|
+
Localized frames.
|
|
490
|
+
params : np.ndarray
|
|
491
|
+
Localization parameters.
|
|
492
|
+
crlbs : np.ndarray
|
|
493
|
+
Cramer-Rao lower bounds.
|
|
494
|
+
loglike : np.ndarray
|
|
495
|
+
Log-likelihood values.
|
|
496
|
+
'''
|
|
497
|
+
image, filtered, roi_info = pre_process_frame(index, stack_handler, options)
|
|
498
|
+
points = detect_points(filtered, filter_obj, detector, options)
|
|
499
|
+
|
|
500
|
+
if len(points) == 0:
|
|
501
|
+
return None, None, None, None
|
|
502
|
+
|
|
503
|
+
params, crlbs, loglike = fit_points(image, points, options.get('varim'), options)
|
|
504
|
+
|
|
505
|
+
if params is not None and len(params) > 0 and roi_info is not None:
|
|
506
|
+
origin = roi_info[0]
|
|
507
|
+
params[:, 0] += origin[0]
|
|
508
|
+
params[:, 1] += origin[1]
|
|
509
|
+
|
|
510
|
+
frames = [index + 1] * params.shape[0] if params is not None else None
|
|
511
|
+
|
|
512
|
+
return frames, params, crlbs, loglike
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def time_string_ms(ms):
|
|
516
|
+
s = int(ms // 1000)
|
|
517
|
+
ms = int(ms % 1000)
|
|
518
|
+
m = int(s // 60)
|
|
519
|
+
s = int(s % 60)
|
|
520
|
+
h = int(m // 60)
|
|
521
|
+
m = int(m % 60)
|
|
522
|
+
return f'{h:02d}:{m:02d}:{s:02d}.{ms:03d}'
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def localizeStackCPU(
|
|
526
|
+
stack_handler: Union[TiffSeqHandler, ZarrImageSequence],
|
|
527
|
+
filter_obj: AbstractFilter,
|
|
528
|
+
detector: AbstractDetector,
|
|
529
|
+
options: dict[str, Any],
|
|
530
|
+
) -> tuple[list, list, list, list]:
|
|
531
|
+
'''
|
|
532
|
+
Localize features in an image stack using CPU parallelization.
|
|
533
|
+
|
|
534
|
+
Parameters
|
|
535
|
+
----------
|
|
536
|
+
stack_handler : Union[TiffSeqHandler, ZarrImageSequence]
|
|
537
|
+
Handler for the image stack.
|
|
538
|
+
filter_obj : AbstractFilter
|
|
539
|
+
Image filter object.
|
|
540
|
+
detector : AbstractDetector
|
|
541
|
+
Point detection algorithm.
|
|
542
|
+
options : Dict[str, Any]
|
|
543
|
+
Dictionary of localization options.
|
|
544
|
+
n_threads : int, optional
|
|
545
|
+
Number of threads to use for parallel processing.
|
|
546
|
+
If None, uses all available threads minus 2.
|
|
547
|
+
|
|
548
|
+
Returns
|
|
549
|
+
-------
|
|
550
|
+
tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
|
|
551
|
+
lists of frame indices, localization parameters,
|
|
552
|
+
CRLBs, and log-likelihood values.
|
|
553
|
+
'''
|
|
554
|
+
if options.get('n_threads') is None:
|
|
555
|
+
n_threads = max(1, ThreadPoolExecutor()._max_workers - 2)
|
|
556
|
+
|
|
557
|
+
start = datetime.now()
|
|
558
|
+
|
|
559
|
+
all_frames, all_params, all_crlbs, all_loglike = [], [], [], []
|
|
560
|
+
|
|
561
|
+
def process_frame(
|
|
562
|
+
frame_index: int,
|
|
563
|
+
) -> tuple[
|
|
564
|
+
Optional[np.ndarray],
|
|
565
|
+
Optional[np.ndarray],
|
|
566
|
+
Optional[np.ndarray],
|
|
567
|
+
Optional[np.ndarray],
|
|
568
|
+
]:
|
|
569
|
+
return localize_frame(frame_index, stack_handler, filter_obj, detector, options)
|
|
570
|
+
|
|
571
|
+
with ThreadPoolExecutor(max_workers=n_threads) as executor:
|
|
572
|
+
futures = [
|
|
573
|
+
executor.submit(process_frame, i) for i in range(stack_handler.shape[0])
|
|
574
|
+
]
|
|
575
|
+
|
|
576
|
+
for future in tqdm(
|
|
577
|
+
as_completed(futures), total=len(futures), desc='Processing frames'
|
|
578
|
+
):
|
|
579
|
+
frames, params, crlbs, loglike = future.result()
|
|
580
|
+
if frames is not None:
|
|
581
|
+
all_frames.extend(frames)
|
|
582
|
+
all_params.extend(params)
|
|
583
|
+
if crlbs is not None:
|
|
584
|
+
all_crlbs.extend(crlbs)
|
|
585
|
+
if loglike is not None:
|
|
586
|
+
all_loglike.extend(loglike)
|
|
587
|
+
|
|
588
|
+
duration = (datetime.now() - start).total_seconds() * 1000
|
|
589
|
+
print(f'\nDone... {time_string_ms(duration)}')
|
|
590
|
+
|
|
591
|
+
return (
|
|
592
|
+
np.array(all_frames, dtype=np.int64),
|
|
593
|
+
np.array(all_params, np.float64),
|
|
594
|
+
np.array(all_crlbs, np.float64) if all_crlbs else None,
|
|
595
|
+
np.array(all_loglike, np.float64) if all_loglike else None,
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def get_rois_lists_GPU(
|
|
600
|
+
stack_handler: Union[TiffSeqHandler, ZarrImageSequence],
|
|
601
|
+
filter: AbstractFilter,
|
|
602
|
+
detector: AbstractDetector,
|
|
603
|
+
**kwargs,
|
|
604
|
+
):
|
|
605
|
+
gain: Union[float, np.ndarray] = kwargs.get('gain', 1)
|
|
606
|
+
offset: Union[float, np.ndarray] = kwargs.get('offset', 0)
|
|
607
|
+
varim: np.ndarray = kwargs.get('varim')
|
|
608
|
+
roi_info = kwargs.get('roi_info')
|
|
609
|
+
irange = kwargs.get('irange')
|
|
610
|
+
rel_threshold: float = kwargs.get('rel_threshold', 0.4)
|
|
611
|
+
max_threshold: float = kwargs.get('max_threshold', 1.0)
|
|
612
|
+
tm_window: int = kwargs.get('tm_window', 0)
|
|
613
|
+
roi_size: int = kwargs.get('roi_size', 13)
|
|
614
|
+
c: int = kwargs.get('channel', 0)
|
|
615
|
+
z: int = kwargs.get('zSlice', 0)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def process_image(k: int) -> tuple[list, list, list, list]:
|
|
619
|
+
image = stack_handler.getSlice(k, c, z)
|
|
620
|
+
image = (image * gain) - offset
|
|
621
|
+
|
|
622
|
+
if tm_window > 1:
|
|
623
|
+
temp = TemporalMedianFilter()
|
|
624
|
+
temp._temporal_window = tm_window
|
|
625
|
+
frames = temp.getFrames(k, stack_handler)
|
|
626
|
+
filtered = temp.run(image, frames, roi_info)
|
|
627
|
+
else:
|
|
628
|
+
filtered = image.copy()
|
|
629
|
+
|
|
630
|
+
if roi_info is not None:
|
|
631
|
+
origin, dim = roi_info
|
|
632
|
+
slice_y = slice(int(origin[1]), int(origin[1] + dim[1]))
|
|
633
|
+
slice_x = slice(int(origin[0]), int(origin[0] + dim[0]))
|
|
634
|
+
image = image[slice_y, slice_x]
|
|
635
|
+
filtered = filtered[slice_y, slice_x]
|
|
636
|
+
|
|
637
|
+
uImg = uImage(filtered)
|
|
638
|
+
uImg.equalizeLUT(irange, True)
|
|
639
|
+
|
|
640
|
+
if isinstance(filter, BandpassFilter):
|
|
641
|
+
filter._show_filter = False
|
|
642
|
+
filter._refresh = False
|
|
643
|
+
|
|
644
|
+
img = filter.run(uImg._view)
|
|
645
|
+
|
|
646
|
+
_, th_img = cv2.threshold(
|
|
647
|
+
img, np.quantile(img, 1 - 1e-4) * rel_threshold, 255, cv2.THRESH_BINARY
|
|
648
|
+
)
|
|
649
|
+
if max_threshold < 1.0:
|
|
650
|
+
_, th2 = cv2.threshold(
|
|
651
|
+
img, np.max(img) * max_threshold, 1, cv2.THRESH_BINARY_INV
|
|
652
|
+
)
|
|
653
|
+
th_img *= th2
|
|
654
|
+
|
|
655
|
+
points: np.ndarray = detector.find_peaks(th_img)
|
|
656
|
+
|
|
657
|
+
if len(points) > 0:
|
|
658
|
+
if varim is None:
|
|
659
|
+
rois, coords = get_roi_list(image, points, roi_size)
|
|
660
|
+
return rois, [], coords, [k + 1] * rois.shape[0]
|
|
661
|
+
else:
|
|
662
|
+
rois, varims, coords = get_roi_list_CMOS(image, varim, points, roi_size)
|
|
663
|
+
return rois, varims, coords, [k + 1] * rois.shape[0]
|
|
664
|
+
return [], [], [], []
|
|
665
|
+
|
|
666
|
+
with ThreadPoolExecutor() as executor:
|
|
667
|
+
futures = [executor.submit(process_image, k) for k in range(len(stack_handler))]
|
|
668
|
+
results = []
|
|
669
|
+
for future in tqdm(
|
|
670
|
+
as_completed(futures), total=len(stack_handler), desc='Processing images'
|
|
671
|
+
):
|
|
672
|
+
results.append(future.result())
|
|
673
|
+
|
|
674
|
+
roi_list, varim_list, coord_list, frames_list = zip(*results)
|
|
675
|
+
return (
|
|
676
|
+
[item for sublist in roi_list for item in sublist],
|
|
677
|
+
[item for sublist in varim_list for item in sublist],
|
|
678
|
+
[item for sublist in coord_list for item in sublist],
|
|
679
|
+
[item for sublist in frames_list for item in sublist],
|
|
680
|
+
)
|