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,556 @@
1
+
2
+ from queue import Queue
3
+
4
+ import cv2
5
+ import numpy as np
6
+
7
+
8
+ class MultiRectangularROISelector:
9
+ '''
10
+ Class for interactively selecting and manipulating
11
+ multiple rectangular ROIs in an image.
12
+
13
+ Parameters
14
+ ----------
15
+ image_data : np.ndarray
16
+ The original image data.
17
+ resize_factor : float, optional
18
+ The resize factor for the image. Defaults to 1.0.
19
+ max_rois : int, optional
20
+ The maximum number of rois the user can select. Defaults to 4.
21
+ one_size : bool, optional
22
+ If True after the first ROI insert is forced. Defaults to False.
23
+ '''
24
+
25
+ def __init__(
26
+ self,
27
+ image_data: np.ndarray, resize_factor=1.0, max_rois=1,
28
+ one_size=False):
29
+ '''
30
+ Initialize the MultiRectangularROISelector instance.
31
+
32
+ Parameters
33
+ ----------
34
+ image_data : np.ndarray
35
+ The original image data.
36
+ resize_factor : float, optional
37
+ The resize factor for the image. Defaults to 1.0.
38
+ max_rois : int, optional
39
+ The maximum number of rois the user can select. Defaults to 4.
40
+ one_size : bool, optional
41
+ If True after the first ROI insert is forced. Defaults to False.
42
+ '''
43
+ self.original = image_data.copy()
44
+ self.shape = self.original.shape
45
+ self.resize_factor = resize_factor
46
+ self.resized_empty: np.ndarray = cv2.resize(
47
+ self.original, (0, 0),
48
+ fx=self.resize_factor, fy=self.resize_factor,
49
+ interpolation=cv2.INTER_NEAREST)
50
+ self.resized_image = self.resized_empty.copy()
51
+
52
+ self.one_size = one_size
53
+ self.max_rois = max(1, max_rois)
54
+ self.rois = [] # stores multiple ROIs, represented as a list [x1, y1, x2, y2]
55
+ self.active_roi_idx = -1 # Index of the currently active ROI
56
+ self.dragging = False
57
+ self.drag_idx = -1
58
+ self.modes = Queue()
59
+ for mode in ['Add', 'Edit', 'Insert', 'Move', 'Remove']:
60
+ self.modes.put(mode)
61
+ self.current_mode = self.modes.get()
62
+
63
+ def update_rectangles(self, image, rectangles, color):
64
+ '''
65
+ Update the image with the specified rectangles.
66
+
67
+ Parameters
68
+ ----------
69
+ image : np.ndarray
70
+ The image to be updated.
71
+ rectangles : List[List[float]]
72
+ List of rectangles represented as [x1, y1, x2, y2].
73
+ color : Tuple[int, int, int]
74
+ Color for drawing rectangles.
75
+
76
+ Returns
77
+ -------
78
+ None
79
+ '''
80
+ for rect in rectangles:
81
+ x1, y1, x2, y2 = rect
82
+ cv2.rectangle(
83
+ image,
84
+ (int(x1 * self.resize_factor), int(y1 * self.resize_factor)),
85
+ (int(x2 * self.resize_factor), int(y2 * self.resize_factor)),
86
+ color,
87
+ int(1 * self.resize_factor),
88
+ )
89
+
90
+ # Add text annotation for ROI number/index
91
+ text_position = (
92
+ int(x1 * self.resize_factor),
93
+ int(y1 * self.resize_factor) - 10,
94
+ )
95
+ cv2.putText(
96
+ image,
97
+ f'ROI {self.rois.index(rect)}',
98
+ text_position,
99
+ cv2.FONT_HERSHEY_SIMPLEX,
100
+ 0.5,
101
+ color,
102
+ 1,
103
+ cv2.LINE_AA,
104
+ )
105
+
106
+ def draw_edit_circles(self, image, rect, color):
107
+ '''
108
+ Draw edit circles at the corners of the rectangle.
109
+
110
+ Parameters
111
+ ----------
112
+ image : np.ndarray
113
+ The image to draw on.
114
+ rect : List[float]
115
+ The rectangle coordinates.
116
+ color : Tuple[int, int, int]
117
+ Color for drawing circles.
118
+
119
+ Returns
120
+ -------
121
+ None
122
+ '''
123
+ x1, y1, x2, y2 = rect
124
+ circle_radius = int(3 * self.resize_factor)
125
+
126
+ # Draw circles at the corners of the rectangle
127
+ for corner in [(x1, y1), (x2, y1), (x2, y2), (x1, y2)]:
128
+ cv2.circle(
129
+ image,
130
+ (
131
+ int(corner[0] * self.resize_factor),
132
+ int(corner[1] * self.resize_factor)),
133
+ circle_radius,
134
+ color,
135
+ -1,
136
+ )
137
+
138
+ def redraw(self):
139
+ '''
140
+ Redraw the image with rectangles and edit circles.
141
+
142
+ Returns
143
+ -------
144
+ None
145
+ '''
146
+ if self.resized_image.shape == self.resized_empty.shape:
147
+ self.resized_image[:] = self.resized_empty
148
+ else:
149
+ self.resized_image = self.resized_empty.copy()
150
+
151
+ for roi_idx, roi in enumerate(self.rois):
152
+ color = (255, 0, 0) if roi_idx != self.active_roi_idx else (0, 255, 255)
153
+
154
+ self.draw_edit_circles(self.resized_image, roi, color)
155
+ self.update_rectangles(self.resized_image, [roi], color)
156
+
157
+ cv2.imshow('Select Rectangular ROIs', self.resized_image)
158
+ cv2.setWindowTitle(
159
+ 'Select Rectangular ROIs',
160
+ f'Select Rectangular ROIs "Mode: {self.current_mode}"',
161
+ )
162
+
163
+ def mouse_callback(self, event, x, y, flags, param):
164
+ '''
165
+ Mouse callback function for handling user interactions.
166
+
167
+ Returns
168
+ -------
169
+ None
170
+ '''
171
+ shift_pressed = flags & cv2.EVENT_FLAG_SHIFTKEY != 0
172
+ ctrl_pressed = flags & cv2.EVENT_FLAG_CTRLKEY != 0
173
+ if event == cv2.EVENT_LBUTTONDOWN:
174
+ if self.current_mode == 'Edit':
175
+ if self.one_size and len(self.rois) > 1:
176
+ return
177
+
178
+ if self.active_roi_idx >= 0 and self.active_roi_idx < len(self.rois):
179
+ self.drag_idx = self.is_hit(x, y)
180
+ self.dragging = self.drag_idx != -1
181
+ elif self.current_mode == 'Add':
182
+ if self.one_size and len(self.rois) > 0:
183
+ return
184
+
185
+ if not self.dragging and len(self.rois) < self.max_rois:
186
+ self.rois.append([
187
+ x / self.resize_factor, y / self.resize_factor,
188
+ x / self.resize_factor, y / self.resize_factor])
189
+ self.dragging = True
190
+ self.drag_idx = 2 # Assign drag index to bottom-right corner
191
+ self.active_roi_idx = len(self.rois) - 1
192
+ elif self.current_mode == 'Insert':
193
+ if not self.dragging and len(self.rois) < self.max_rois:
194
+ if self.active_roi_idx == -1:
195
+ # Add the first ROI
196
+ self.rois.append([
197
+ x / self.resize_factor, y / self.resize_factor,
198
+ x / self.resize_factor, y / self.resize_factor])
199
+ self.dragging = True
200
+ self.drag_idx = 2 # Assign drag index to bottom-right corner
201
+ else:
202
+ # For subsequent ROIs, set initial size to match the first ROI
203
+ x1, y1, x2, y2 = self.rois[-1] # Get the first ROI
204
+ self.rois.append([
205
+ x / self.resize_factor, y / self.resize_factor,
206
+ (x / self.resize_factor + (x2 - x1)),
207
+ (y / self.resize_factor + (y2 - y1))])
208
+ self.active_roi_idx = len(self.rois) - 1
209
+ elif self.current_mode == 'Move':
210
+ if self.active_roi_idx >= 0 and self.active_roi_idx < len(self.rois):
211
+ self.drag_idx = self.is_hit(x, y)
212
+ self.dragging = self.drag_idx != -1
213
+ elif self.current_mode == 'Remove':
214
+ if self.active_roi_idx >= 0 and self.active_roi_idx < len(self.rois):
215
+ if self.is_hit(x, y) > -1:
216
+ self.delete_active()
217
+ self.redraw()
218
+ elif event == cv2.EVENT_LBUTTONUP:
219
+ self.dragging = False
220
+ if self.active_roi_idx >= 0:
221
+ x1, y1, x2, y2 = self.rois[self.active_roi_idx]
222
+ if (x1 == x2 and y1 == y2) or \
223
+ x2 > self.shape[1] or y2 > self.shape[0]:
224
+ self.delete_active()
225
+ elif event == cv2.EVENT_MOUSEMOVE:
226
+ if self.current_mode in ['Add', 'Edit']:
227
+ if self.dragging and 0 <= self.drag_idx < 4:
228
+ offset = 2 / self.resize_factor
229
+ x1, y1, x2, y2 = self.rois[self.active_roi_idx]
230
+ x_ratio, y_ratio = x / self.resize_factor, y / self.resize_factor
231
+
232
+ if self.drag_idx == 0:
233
+ if shift_pressed:
234
+ delta = [x_ratio - x1, y_ratio - y1]
235
+ self.rois[self.active_roi_idx][0] += np.mean(delta)
236
+ self.rois[self.active_roi_idx][1] += np.mean(delta)
237
+ else:
238
+ self.rois[self.active_roi_idx][0] = min(
239
+ x_ratio, x2 - offset)
240
+ self.rois[self.active_roi_idx][1] = min(
241
+ y_ratio, y2 - offset)
242
+ elif self.drag_idx == 1:
243
+ if shift_pressed:
244
+ delta = [x_ratio - x2, y_ratio - y1]
245
+ self.rois[self.active_roi_idx][2] += np.mean(delta)
246
+ self.rois[self.active_roi_idx][1] += np.mean(delta)
247
+ else:
248
+ self.rois[self.active_roi_idx][2] = max(
249
+ x_ratio, x1 + offset)
250
+ self.rois[self.active_roi_idx][1] = min(
251
+ y_ratio, y2 - offset)
252
+ elif self.drag_idx == 2:
253
+ if shift_pressed:
254
+ delta = [x_ratio - x2, y_ratio - y2]
255
+ self.rois[self.active_roi_idx][2] += np.mean(delta)
256
+ self.rois[self.active_roi_idx][3] += np.mean(delta)
257
+ else:
258
+ self.rois[self.active_roi_idx][2] = max(
259
+ x_ratio, x1 + offset)
260
+ self.rois[self.active_roi_idx][3] = max(
261
+ y_ratio, y1 + offset)
262
+ elif self.drag_idx == 3:
263
+ if shift_pressed:
264
+ delta = [x_ratio - x1, y_ratio - y2]
265
+ self.rois[self.active_roi_idx][0] += np.mean(delta)
266
+ self.rois[self.active_roi_idx][3] += np.mean(delta)
267
+ else:
268
+ self.rois[self.active_roi_idx][0] = min(
269
+ x_ratio, x2 - offset)
270
+ self.rois[self.active_roi_idx][3] = max(
271
+ y_ratio, y1 + offset)
272
+ self.redraw()
273
+ elif self.current_mode == 'Move':
274
+ if self.dragging and 0 <= self.drag_idx < 4:
275
+ x1, y1, x2, y2 = self.rois[self.active_roi_idx]
276
+ x_ratio, y_ratio = x / self.resize_factor, y / self.resize_factor
277
+
278
+ if self.drag_idx == 0:
279
+ delta_x = x_ratio - x1
280
+ delta_y = y_ratio - y1
281
+ elif self.drag_idx == 1:
282
+ delta_x = x_ratio - x2
283
+ delta_y = y_ratio - y1
284
+ elif self.drag_idx == 2:
285
+ delta_x = x_ratio - x2
286
+ delta_y = y_ratio - y2
287
+ elif self.drag_idx == 3:
288
+ delta_x = x_ratio - x1
289
+ delta_y = y_ratio - y2
290
+
291
+ self.rois[self.active_roi_idx][0] += delta_x
292
+ self.rois[self.active_roi_idx][2] += delta_x
293
+
294
+ if shift_pressed and not ctrl_pressed and len(self.rois) > 1:
295
+ delta_y = self.rois[self.active_roi_idx - 1][1] - self.rois[
296
+ self.active_roi_idx][1]
297
+ elif shift_pressed and ctrl_pressed and len(self.rois) > 1:
298
+ height = self.rois[self.active_roi_idx][3] - self.rois[
299
+ self.active_roi_idx][1]
300
+ delta_y = (self.shape[0] - height) // 2 - self.rois[
301
+ self.active_roi_idx][1]
302
+
303
+ self.rois[self.active_roi_idx][1] += delta_y
304
+ self.rois[self.active_roi_idx][3] += delta_y
305
+ self.redraw()
306
+ elif event == cv2.EVENT_MOUSEWHEEL:
307
+ if flags > 0:
308
+ self.resize_factor += 0.05
309
+ else:
310
+ self.resize_factor = max(0.1, self.resize_factor - 0.05)
311
+ self.resized_empty = cv2.resize(
312
+ self.original,
313
+ (0, 0),
314
+ fx=self.resize_factor,
315
+ fy=self.resize_factor,
316
+ interpolation=cv2.INTER_NEAREST,
317
+ )
318
+ self.redraw()
319
+
320
+ def is_hit(self, x: int, y: int):
321
+ '''
322
+ Check if the point (x, y) is within the active rectangle.
323
+
324
+ Parameters
325
+ ----------
326
+ x : int
327
+ X-coordinate of the point.
328
+ y : int
329
+ Y-coordinate of the point.
330
+
331
+ Returns
332
+ -------
333
+ int
334
+ Index of the hit corner, or -1 if not hit.
335
+ '''
336
+ x1, y1, x2, y2 = self.rois[self.active_roi_idx]
337
+ delta = np.sqrt(
338
+ (np.array([x1, x2, x2, x1]) - x / self.resize_factor)**2 + \
339
+ (np.array([y1, y1, y2, y2]) - y / self.resize_factor)**2)
340
+ return np.argmin(delta) if any(delta < 10) else -1
341
+
342
+ def delete_active(self):
343
+ '''
344
+ Delete the currently active rectangle.
345
+
346
+ Returns
347
+ -------
348
+ None
349
+ '''
350
+ if 0 <= self.active_roi_idx < len(self.rois):
351
+ del self.rois[self.active_roi_idx]
352
+
353
+ # Adjust active ROI index only if there are remaining ROIs
354
+ if self.rois:
355
+ self.active_roi_idx = max(0, self.active_roi_idx - 1)
356
+ else:
357
+ self.active_roi_idx = -1
358
+
359
+ def cycle_mode(self):
360
+ '''
361
+ Cycle through different modes of operation.
362
+
363
+ Returns
364
+ -------
365
+ None
366
+ '''
367
+ self.modes.put(self.current_mode)
368
+ self.current_mode = self.modes.get()
369
+
370
+ def select_rectangular_rois(self, offset: tuple[int, int] = None):
371
+ '''
372
+ Select rectangular ROIs interactively.
373
+
374
+ Parameters
375
+ ----------
376
+ offset : Tuple[int, int], optional
377
+ Offset to apply to selected ROIs.
378
+
379
+ Returns
380
+ -------
381
+ List[np.ndarray]
382
+ List of selected ROIs as NumPy arrays.
383
+ '''
384
+ cv2.namedWindow('Select Rectangular ROIs')
385
+ cv2.setMouseCallback('Select Rectangular ROIs', self.mouse_callback)
386
+
387
+ while True:
388
+ self.redraw()
389
+ key = cv2.waitKeyEx(200)
390
+
391
+ if key == ord('q') or key == ord('Q'):
392
+ break
393
+ elif key == 32: # Space
394
+ self.cycle_mode()
395
+ elif key == 2490368: # Up arrow
396
+ if len(self.rois) > 0:
397
+ self.active_roi_idx = (self.active_roi_idx - 1) % len(self.rois)
398
+ self.redraw()
399
+ elif key == 2621440: # Down arrow
400
+ if len(self.rois) > 0:
401
+ self.active_roi_idx = (self.active_roi_idx + 1) % len(self.rois)
402
+ self.redraw()
403
+ elif key == 3014656: # Delete
404
+ if self.active_roi_idx != -1:
405
+ self.delete_active()
406
+ elif key == ord('a'):
407
+ while self.current_mode != 'Add':
408
+ self.cycle_mode()
409
+ elif key == ord('e'):
410
+ while self.current_mode != 'Edit':
411
+ self.cycle_mode()
412
+ elif key == ord('i'):
413
+ while self.current_mode != 'Insert':
414
+ self.cycle_mode()
415
+ elif key == ord('m'):
416
+ while self.current_mode != 'Move':
417
+ self.cycle_mode()
418
+ elif key == ord('r'):
419
+ while self.current_mode != 'Remove':
420
+ self.cycle_mode()
421
+
422
+ cv2.destroyAllWindows()
423
+
424
+ if offset is not None:
425
+ return [
426
+ np.array([
427
+ rect[0] + offset[0], rect[1] + offset[1],
428
+ rect[2] + offset[0], rect[3] + offset[1]])
429
+ for rect in self.rois
430
+ ]
431
+ else:
432
+ return [np.array(rect) for rect in self.rois]
433
+
434
+ @staticmethod
435
+ def get_selector(
436
+ image: np.ndarray, resize_factor=1.0,
437
+ max_rois=4, one_size=False):
438
+ '''
439
+ Get a MultiRectangularROISelector instance for the specified image.
440
+
441
+ Parameters
442
+ ----------
443
+ image : np.ndarray
444
+ The image for ROI selection.
445
+ resize_factor : float, optional
446
+ The resize factor for the image. Defaults to 1.0.
447
+ max_rois : int, optional
448
+ The maximum number of rois the user can select. Defaults to 4.
449
+ one_size : bool, optional
450
+ If True after the first ROI insert is forced. Defaults to False.
451
+
452
+ Returns
453
+ -------
454
+ MultiRectangularROISelector
455
+ An instance of the selector.
456
+ '''
457
+ if image is None:
458
+ raise ValueError('Parameter image is None.')
459
+
460
+ if image.ndim != 2:
461
+ raise ValueError(
462
+ f'Parameter image should be an array with ndim = 2, not {image.ndim}.')
463
+
464
+ # Convert monochromatic image to RGB
465
+ _image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
466
+
467
+ # Select a multi-rectangle ROI with the specified resize factor
468
+ return MultiRectangularROISelector(_image, resize_factor, max_rois, one_size)
469
+
470
+ def convert_rois_to_pos_size(rois: list[np.ndarray]):
471
+ '''
472
+ Convert a list of rectangular ROIs represented as NumPy arrays to a list
473
+ of positions and sizes.
474
+
475
+ Parameters
476
+ ----------
477
+ rois : list[np.ndarray]
478
+ List of rectangular ROIs, each represented as a NumPy array with shape (4,).
479
+
480
+ Returns
481
+ -------
482
+ list[list[int]]
483
+ List of rectangular ROIs converted to positions and sizes.
484
+ Each entry is a list [x, y, width, height].
485
+ '''
486
+ res = []
487
+ for roi in rois:
488
+ if roi.shape[0] == 4:
489
+ res.append(
490
+ [
491
+ # x, y
492
+ round(roi[0]), round(roi[1]),
493
+ # w, h
494
+ round(roi[2]- roi[0]), round(roi[3] - roi[1])
495
+ ]
496
+ )
497
+ return res
498
+
499
+ def convert_pos_size_to_rois(rectangles: list[list[int]]) -> list[list]:
500
+ '''
501
+ Convert a list of rectangular positions and sizes to a list of rectangular ROIs.
502
+
503
+ Parameters
504
+ ----------
505
+ rectangles : list[list[int]]
506
+ List of rectangular positions and sizes,
507
+ each represented as [x, y, width, height].
508
+
509
+ Returns
510
+ -------
511
+ list[list]
512
+ List of rectangular ROIs converted to positions and sizes.
513
+ Each entry is a list [x1, y1, x2, y2].
514
+ '''
515
+ res = []
516
+ for rect in rectangles:
517
+ x, y, w, h = rect
518
+ res.append([x, y, x + w, y + h])
519
+ return res
520
+
521
+
522
+ if __name__ == '__main__':
523
+ # Example usage
524
+ image = np.random.choice(range(256), (512, 512)).astype(np.uint8)
525
+
526
+ resize_factor = 1
527
+
528
+ selector = MultiRectangularROISelector.get_selector(image, resize_factor, 4, True)
529
+
530
+ # Select a multi-point line ROI with the specified resize factor
531
+ roi_points = selector.select_rectangular_rois()
532
+
533
+ # Draw the selected line on the resized image
534
+ if len(roi_points) > 1:
535
+ pass
536
+ # for roi in roi_points:
537
+ # if len(roi) > 1:
538
+ # #-- Plot...
539
+ # fig, axes = plt.subplots(nrows=2)
540
+ # axes[0].imshow(image)
541
+ # axes[0].plot(roi[:, 0], roi[:, 1], 'ro-')
542
+ # axes[0].axis('image')
543
+
544
+ # # linewidth
545
+ # linewidth = 1
546
+ # axes[1].plot(
547
+ # get_kymogram_row(
548
+ # image,
549
+ # roi[:, 0].flatten(), # X points
550
+ # roi[:, 1].flatten(), # Y points
551
+ # linewidth),
552
+ # label=f'linewidth={linewidth}')
553
+ # plt.legend()
554
+ # plt.show()
555
+ # else:
556
+ # print('Error: Please select at least two points.')
@@ -0,0 +1,3 @@
1
+ from microEye.analysis.utils.coordinates import radial_coordinate
2
+ from microEye.analysis.utils.images import checker_pairs, expand_image, match_shape
3
+ from microEye.analysis.utils.windows import hamming_2Dwindow
@@ -0,0 +1,18 @@
1
+
2
+ import numpy as np
3
+
4
+
5
+ def radial_coordinate(shape):
6
+ '''Generates a 2D array with radial coordinates
7
+ with according to the first two axis of the
8
+ supplied shape tuple
9
+
10
+ Returns
11
+ -------
12
+ R, Rsq : Radius 2d matrix (R) and radius squared matrix (Rsq)
13
+ '''
14
+ y_len = np.arange(-shape[0]//2, shape[0]//2)
15
+ x_len = np.arange(-shape[1]//2, shape[1]//2)
16
+ X, Y = np.meshgrid(x_len, y_len)
17
+ Rsq = (X**2 + Y**2)
18
+ return np.sqrt(Rsq), Rsq
@@ -0,0 +1,68 @@
1
+ import numpy as np
2
+
3
+
4
+ def expand_image(image: np.ndarray, shape):
5
+ '''
6
+ Expand an image to a specified shape by padding with zeros.
7
+
8
+ Parameters
9
+ ----------
10
+ image : np.ndarray
11
+ Image to be expanded
12
+ shape : tuple
13
+ Desired shape
14
+
15
+ Returns
16
+ -------
17
+ np.ndarray
18
+ Expanded image
19
+ '''
20
+ if image.shape == shape:
21
+ return image
22
+ else:
23
+ ret = np.zeros(shape)
24
+ ret[:image.shape[0], :image.shape[1]] = image
25
+ return ret
26
+
27
+ def match_shape(image1: np.ndarray, image2: np.ndarray):
28
+ '''
29
+ Match the shape of two images by expanding the smaller one.
30
+
31
+ Parameters
32
+ ----------
33
+ image1 : np.ndarray
34
+ First image
35
+ image2 : np.ndarray
36
+ Second image
37
+ '''
38
+ shape = tuple(max(x, y) for x, y in zip(image1.shape, image2.shape))
39
+
40
+ if any(map(lambda x, y: x != y, image1.shape, shape)):
41
+ image1 = expand_image(image1, shape)
42
+ if any(map(lambda x, y: x != y, image2.shape, shape)):
43
+ image2 = expand_image(image2, shape)
44
+
45
+ return image1, image2
46
+
47
+ def checker_pairs(image: np.ndarray):
48
+ '''
49
+ Extracts checkerboard pairs from an image.
50
+
51
+ Parameters
52
+ ----------
53
+ image : np.ndarray
54
+ Image to extract checkerboard pairs from
55
+ '''
56
+ shape = image.shape
57
+ odd_index = [np.arange(1, shape[i], 2) for i in range(len(shape))]
58
+ even_index = [np.arange(0, shape[i], 2) for i in range(len(shape))]
59
+
60
+ odd = image[odd_index[0], :][:, odd_index[1]]
61
+ even = image[even_index[0], :][:, even_index[1]]
62
+ odd, even = match_shape(odd, even)
63
+
64
+ oddeven = image[odd_index[0], :][:, even_index[1]]
65
+ evenodd = image[even_index[0], :][:, odd_index[1]]
66
+ oddeven, evenodd = match_shape(oddeven, evenodd)
67
+
68
+ return odd, even, oddeven, evenodd
@@ -0,0 +1,19 @@
1
+ import numpy as np
2
+
3
+
4
+ def hamming_2Dwindow(size: int):
5
+ '''
6
+ Generate a 2D Hamming window.
7
+
8
+ Parameters
9
+ ----------
10
+ size : int
11
+ Size of the window
12
+
13
+ Returns
14
+ -------
15
+ np.ndarray
16
+ 2D Hamming window
17
+ '''
18
+ window1d = np.hamming(size)
19
+ return np.sqrt(np.outer(window1d, window1d))
@@ -0,0 +1,3 @@
1
+ from microEye.analysis.viewer.images import StackView
2
+ from microEye.analysis.viewer.localizations import LocalizationsView
3
+ from microEye.analysis.viewer.psf import PSFView