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