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