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,524 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from tabulate import tabulate
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class miCamera:
|
|
9
|
+
def __init__(self, Cam_ID=0) -> None:
|
|
10
|
+
self.Cam_ID = Cam_ID
|
|
11
|
+
self.acquisition = False
|
|
12
|
+
self.bytes_per_pixel = 1
|
|
13
|
+
self.exposure_current = 1
|
|
14
|
+
self.temperature = -127
|
|
15
|
+
self.name = ''
|
|
16
|
+
self.exposure_range = [0.05, 5000]
|
|
17
|
+
self._width = 512
|
|
18
|
+
self._height = 512
|
|
19
|
+
|
|
20
|
+
self.status = {}
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def height(self):
|
|
24
|
+
return self._height
|
|
25
|
+
|
|
26
|
+
@height.setter
|
|
27
|
+
def height(self, value):
|
|
28
|
+
self._height = value
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def width(self):
|
|
32
|
+
return self._width
|
|
33
|
+
|
|
34
|
+
@width.setter
|
|
35
|
+
def width(self, value):
|
|
36
|
+
self._width = value
|
|
37
|
+
|
|
38
|
+
def get_temperature(self):
|
|
39
|
+
return self.temperature
|
|
40
|
+
|
|
41
|
+
def getWidth(self):
|
|
42
|
+
if isinstance(self.width, ctypes.c_int):
|
|
43
|
+
return self.width.value
|
|
44
|
+
elif isinstance(self.width, int):
|
|
45
|
+
return self.width
|
|
46
|
+
|
|
47
|
+
def getHeight(self):
|
|
48
|
+
if isinstance(self.height, ctypes.c_int):
|
|
49
|
+
return self.height.value
|
|
50
|
+
elif isinstance(self.height, int):
|
|
51
|
+
return self.height
|
|
52
|
+
|
|
53
|
+
def getExposure(self):
|
|
54
|
+
if isinstance(self.exposure_current, ctypes.c_double):
|
|
55
|
+
return self.exposure_current.value
|
|
56
|
+
elif isinstance(self.exposure_current, (int, float)):
|
|
57
|
+
return self.exposure_current
|
|
58
|
+
|
|
59
|
+
def populate_status(self):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
def print_status(self):
|
|
63
|
+
self.populate_status()
|
|
64
|
+
for key in self.status:
|
|
65
|
+
data = [[k, i] for k, i in self.status[key].items()]
|
|
66
|
+
print(tabulate(data, headers=[key], tablefmt='rounded_grid'))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class miDummy(miCamera):
|
|
70
|
+
instances = []
|
|
71
|
+
|
|
72
|
+
def __init__(self, Cam_ID=0, **kwargs):
|
|
73
|
+
super().__init__(Cam_ID)
|
|
74
|
+
|
|
75
|
+
miDummy.instances.append(self)
|
|
76
|
+
|
|
77
|
+
self.name = 'MicroEye Dummy Camera'
|
|
78
|
+
self.exposure_current = kwargs.get('exposure', 10.0)
|
|
79
|
+
self.exposure_unit = kwargs.get('exposure_unit', 'ms')
|
|
80
|
+
self.exposure_increment = 1.0
|
|
81
|
+
self.exposure_range = kwargs.get('exposure_range', [0.05, 5000])
|
|
82
|
+
self.bytes_per_pixel = 2
|
|
83
|
+
|
|
84
|
+
self._height = kwargs.get('height', 512)
|
|
85
|
+
self._width = kwargs.get('width', 512)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
self.binning_horizontal = kwargs.get('binning_horizontal', 1)
|
|
89
|
+
self.binning_vertical = kwargs.get('binning_vertical', 1)
|
|
90
|
+
self.bit_depth = kwargs.get('bit_depth', 12)
|
|
91
|
+
self.gain = kwargs.get('gain', 2.23)
|
|
92
|
+
self.full_well_capacity = kwargs.get('full_well_capacity', 9200)
|
|
93
|
+
self.quantum_efficiency = kwargs.get('quantum_efficiency', 0.8)
|
|
94
|
+
self.dark_current = kwargs.get('dark_current', 0.0001)
|
|
95
|
+
self.readout_noise = kwargs.get('readout_noise', 2.1)
|
|
96
|
+
self.noise_baseline = kwargs.get('noise_baseline', 5)
|
|
97
|
+
self.flux = kwargs.get('flux', 0)
|
|
98
|
+
self.pattern_type = kwargs.get('pattern_type', 'Sinusoidal')
|
|
99
|
+
self.pattern_offset = kwargs.get('pattern_offset', 0)
|
|
100
|
+
|
|
101
|
+
# Sinusoidal pattern parameters
|
|
102
|
+
self.sinusoidal_frequency = kwargs.get('sinusoidal_frequency', 0.01)
|
|
103
|
+
self.sinusoidal_phase = kwargs.get('sinusoidal_phase', 0.0)
|
|
104
|
+
self.sinusoidal_amplitude = kwargs.get('sinusoidal_amplitude', 0.1e5)
|
|
105
|
+
self.sinusoidal_direction = kwargs.get('sinusoidal_direction', 'd')
|
|
106
|
+
|
|
107
|
+
# Single molecule parameters
|
|
108
|
+
self.sm_intensity = kwargs.get('sm_intensity', 5000)
|
|
109
|
+
self.sm_density = kwargs.get('sm_density', 0.5)
|
|
110
|
+
self.sm_pixel_size = kwargs.get('sm_pixel_size', 114.17)
|
|
111
|
+
self.sm_wavelength = kwargs.get('sm_wavelength', 650)
|
|
112
|
+
self.sm_na = kwargs.get('sm_na', 1.49)
|
|
113
|
+
|
|
114
|
+
# Gaussian pattern Drift parameters
|
|
115
|
+
self.drift_current_velocity = 0.0 # Current drift velocity
|
|
116
|
+
self.drift_center = self.width // 2
|
|
117
|
+
self.drift_speed = 0.10
|
|
118
|
+
self.drift_period = 80
|
|
119
|
+
self.drift_momentum = 0.85
|
|
120
|
+
self.drift_random_walk_factor = 0.80
|
|
121
|
+
self.drift_vibration_amplitude = 0.1
|
|
122
|
+
self.drift_sigma_x = 5
|
|
123
|
+
self.drift_sigma_y = 20
|
|
124
|
+
self.drift_amplitude = 5000
|
|
125
|
+
|
|
126
|
+
self.__roi = None
|
|
127
|
+
self._meshes = None
|
|
128
|
+
|
|
129
|
+
self.update_meshes()
|
|
130
|
+
|
|
131
|
+
def __del__(self):
|
|
132
|
+
if self in miDummy.instances:
|
|
133
|
+
miDummy.instances.remove(self)
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def exposure(self):
|
|
137
|
+
return self.exposure_current
|
|
138
|
+
|
|
139
|
+
@exposure.setter
|
|
140
|
+
def exposure(self, value):
|
|
141
|
+
if value < self.exposure_range[0]:
|
|
142
|
+
value = self.exposure_range[0]
|
|
143
|
+
elif value > self.exposure_range[1]:
|
|
144
|
+
value = self.exposure_range[1]
|
|
145
|
+
self.exposure_current = value
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def height(self):
|
|
149
|
+
return self._height
|
|
150
|
+
|
|
151
|
+
@height.setter
|
|
152
|
+
def height(self, value):
|
|
153
|
+
if not isinstance(value, int) or value < 32 or value > 4096:
|
|
154
|
+
raise ValueError('Height must be an integer between 32 and 4096.')
|
|
155
|
+
self._height = value
|
|
156
|
+
if self._width is not None:
|
|
157
|
+
self._meshes = None
|
|
158
|
+
self.update_meshes()
|
|
159
|
+
if self.__roi:
|
|
160
|
+
self.set_roi(*self.__roi)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def width(self):
|
|
164
|
+
return self._width
|
|
165
|
+
|
|
166
|
+
@width.setter
|
|
167
|
+
def width(self, value):
|
|
168
|
+
if not isinstance(value, int) or value < 32 or value > 4096:
|
|
169
|
+
raise ValueError('Width must be an integer between 32 and 4096.')
|
|
170
|
+
self._width = value
|
|
171
|
+
if self._height is not None:
|
|
172
|
+
self._meshes = None
|
|
173
|
+
self.update_meshes()
|
|
174
|
+
if self.__roi:
|
|
175
|
+
self.set_roi(*self.__roi)
|
|
176
|
+
|
|
177
|
+
def update_meshes(self):
|
|
178
|
+
x = np.arange(self.width)
|
|
179
|
+
y = np.arange(self.height)
|
|
180
|
+
X, Y = np.meshgrid(x, y)
|
|
181
|
+
|
|
182
|
+
self._meshes = {
|
|
183
|
+
'd': (X + Y),
|
|
184
|
+
'h': X,
|
|
185
|
+
'v': Y,
|
|
186
|
+
'r': np.sqrt((X - self.width / 2) ** 2 + (Y - self.height / 2) ** 2),
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
def set_roi(self, x: int, y: int, width: int, height: int):
|
|
190
|
+
'''
|
|
191
|
+
Set the region of interest.
|
|
192
|
+
|
|
193
|
+
This function sets the region of interest (ROI) for the camera.
|
|
194
|
+
The ROI is defined by the top-left corner coordinates (x, y)
|
|
195
|
+
and the width and height of the region.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
x : int
|
|
200
|
+
The x-coordinate of the top-left corner of the ROI.
|
|
201
|
+
y : int
|
|
202
|
+
The y-coordinate of the top-left corner of the ROI.
|
|
203
|
+
width : int
|
|
204
|
+
The width of the ROI.
|
|
205
|
+
height : int
|
|
206
|
+
The height of the ROI.
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
None
|
|
211
|
+
'''
|
|
212
|
+
detector_width = self.width
|
|
213
|
+
detector_height = self.height
|
|
214
|
+
|
|
215
|
+
if x == 0 and y == 0 and width == detector_width and height == detector_height:
|
|
216
|
+
self.__roi = None
|
|
217
|
+
else:
|
|
218
|
+
x = max(0, min(x, detector_width - 1))
|
|
219
|
+
y = max(0, min(y, detector_height - 1))
|
|
220
|
+
width = max(1, min(width, detector_width - x))
|
|
221
|
+
height = max(1, min(height, detector_height - y))
|
|
222
|
+
self.__roi = (x, y, width, height)
|
|
223
|
+
|
|
224
|
+
def get_roi(self) -> tuple[int, int, int, int]:
|
|
225
|
+
'''
|
|
226
|
+
Return the current region of interest (ROI).
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
Tuple[int, int, int, int]
|
|
231
|
+
The top-left corner coordinates (x, y) and the width and height of the ROI.
|
|
232
|
+
'''
|
|
233
|
+
if self.__roi is None:
|
|
234
|
+
return 0, 0, self.width, self.height
|
|
235
|
+
else:
|
|
236
|
+
x, y, width, height = self.__roi
|
|
237
|
+
return x, y, width, height
|
|
238
|
+
|
|
239
|
+
def reset_roi(self):
|
|
240
|
+
'''
|
|
241
|
+
Reset the region of interest.
|
|
242
|
+
'''
|
|
243
|
+
self.__roi = None
|
|
244
|
+
|
|
245
|
+
def get_dummy_image(self, flux: Union[float, int, np.ndarray] = 10):
|
|
246
|
+
'''
|
|
247
|
+
Generate a dummy image with noise and CMOS conversion based on
|
|
248
|
+
the camera properties.
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
flux : float | int | ndarray, optional
|
|
253
|
+
The photon flux value used to generate a
|
|
254
|
+
dummy image when no input image is provided [photons/s].
|
|
255
|
+
Default is 10.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
ndarray
|
|
260
|
+
A 2D numpy array representing the dummy
|
|
261
|
+
image with added noise and CMOS conversion.
|
|
262
|
+
|
|
263
|
+
Notes
|
|
264
|
+
-----
|
|
265
|
+
The dummy image is generated based on the camera properties, including
|
|
266
|
+
exposure time, dark current, quantum efficiency, full well capacity,
|
|
267
|
+
readout noise, and gain.
|
|
268
|
+
The exposure time, dark current, quantum efficiency, full well capacity,
|
|
269
|
+
and gain are used to calculate the baseline, signal, and noise,
|
|
270
|
+
which are then added to the generated dummy image. After that,
|
|
271
|
+
the image is scaled based on the full well capacity and gain,
|
|
272
|
+
clipped based on the bit depth, and cast to the desired data type.
|
|
273
|
+
|
|
274
|
+
The exposure time is converted from milliseconds to seconds.
|
|
275
|
+
A random noise image is generated with the desired width, height,
|
|
276
|
+
and bit depth. The noise parameter now affects the scale, instead of
|
|
277
|
+
the loc. The image is then scaled, clipped, and cast to the desired
|
|
278
|
+
data type.
|
|
279
|
+
|
|
280
|
+
'''
|
|
281
|
+
if isinstance(flux, np.ndarray):
|
|
282
|
+
if flux.shape != (self.height, self.width):
|
|
283
|
+
raise ValueError(
|
|
284
|
+
f'Flux array must have shape ({self.height}, {self.width})!'
|
|
285
|
+
)
|
|
286
|
+
elif not isinstance(flux, float) and not isinstance(flux, int):
|
|
287
|
+
raise TypeError('Flux must be a float, int or ndarray!')
|
|
288
|
+
|
|
289
|
+
# Convert exposure time from milliseconds to seconds
|
|
290
|
+
exposure_s = self.exposure_current / 1000
|
|
291
|
+
|
|
292
|
+
baseline = self.dark_current * exposure_s
|
|
293
|
+
|
|
294
|
+
signal = self.quantum_efficiency * flux * exposure_s
|
|
295
|
+
|
|
296
|
+
# Generate a random noise image with the desired width, height, and bit depth
|
|
297
|
+
# The noise parameter now affects the scale, instead of the loc
|
|
298
|
+
image = np.random.poisson(
|
|
299
|
+
lam=baseline + signal, # mean
|
|
300
|
+
size=(self.height, self.width),
|
|
301
|
+
) + np.random.normal(
|
|
302
|
+
loc=0, scale=self.readout_noise, size=(self.height, self.width)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Scale the image using quantum efficiency, full well capacity, and gain
|
|
306
|
+
image = np.clip(image, 0, self.full_well_capacity) / self.gain
|
|
307
|
+
|
|
308
|
+
# Add the baseline
|
|
309
|
+
image += self.noise_baseline
|
|
310
|
+
|
|
311
|
+
# Clip the image based on the bit depth
|
|
312
|
+
image = np.clip(image, 0, 2**self.bit_depth - 1).astype(np.uint16)
|
|
313
|
+
|
|
314
|
+
if self.__roi:
|
|
315
|
+
x, y, width, height = self.__roi
|
|
316
|
+
return image[y : y + height, x : x + width]
|
|
317
|
+
else:
|
|
318
|
+
return image
|
|
319
|
+
|
|
320
|
+
def get_sinus_diagonal_pattern(
|
|
321
|
+
self, time, amplitude=2000, frequency=0.001, phase=0, offset=0, type='d'
|
|
322
|
+
):
|
|
323
|
+
'''
|
|
324
|
+
Generate a sinusoidal diagonal pattern for the flux array.
|
|
325
|
+
|
|
326
|
+
Parameters
|
|
327
|
+
----------
|
|
328
|
+
time : float
|
|
329
|
+
The current time.
|
|
330
|
+
|
|
331
|
+
amplitude : float, optional
|
|
332
|
+
The amplitude of the sinusoidal pattern (default is 1000).
|
|
333
|
+
|
|
334
|
+
frequency : float, optional
|
|
335
|
+
The frequency of the sinusoidal pattern (default is 0.01).
|
|
336
|
+
|
|
337
|
+
phase : float, optional
|
|
338
|
+
The phase of the sinusoidal pattern (default is 0).
|
|
339
|
+
|
|
340
|
+
offset : float, optional
|
|
341
|
+
The offset value to add to the pattern (default is 0).
|
|
342
|
+
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
ndarray
|
|
346
|
+
A 2D numpy array representing the sinusoidal diagonal pattern.
|
|
347
|
+
'''
|
|
348
|
+
if type not in ['d', 'h', 'v', 'r']:
|
|
349
|
+
type = 'd'
|
|
350
|
+
|
|
351
|
+
pattern = (
|
|
352
|
+
amplitude
|
|
353
|
+
* np.sin(
|
|
354
|
+
2 * np.pi * frequency * self._meshes[type] + phase + 2 * np.pi * time
|
|
355
|
+
)
|
|
356
|
+
+ offset
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if pattern.min() < 0:
|
|
360
|
+
pattern += abs(pattern.min())
|
|
361
|
+
|
|
362
|
+
return pattern
|
|
363
|
+
|
|
364
|
+
def get_gaussian_beam_pattern(
|
|
365
|
+
self,
|
|
366
|
+
time: float,
|
|
367
|
+
center_x: Optional[float] = None,
|
|
368
|
+
center_y: Optional[float] = None,
|
|
369
|
+
):
|
|
370
|
+
'''
|
|
371
|
+
Generate a Gaussian beam pattern for the flux array.
|
|
372
|
+
The pattern is a 2D Gaussian distribution with a specified center and
|
|
373
|
+
standard deviation.
|
|
374
|
+
The pattern is also shifted in the x and y directions according to the
|
|
375
|
+
drift speed.
|
|
376
|
+
The pattern is also scaled by the amplitude.
|
|
377
|
+
The pattern is also shifted in time according to the drift speed.
|
|
378
|
+
|
|
379
|
+
Parameters
|
|
380
|
+
----------
|
|
381
|
+
time : float
|
|
382
|
+
The current time.
|
|
383
|
+
amplitude : float, optional
|
|
384
|
+
The amplitude of the Gaussian pattern (default is 5000).
|
|
385
|
+
center_x : float, optional
|
|
386
|
+
The x-coordinate of the center of the Gaussian pattern (default is None).
|
|
387
|
+
center_y : float, optional
|
|
388
|
+
The y-coordinate of the center of the Gaussian pattern (default is None).
|
|
389
|
+
|
|
390
|
+
Returns
|
|
391
|
+
-------
|
|
392
|
+
ndarray
|
|
393
|
+
A 2D numpy array representing the Gaussian beam pattern.
|
|
394
|
+
'''
|
|
395
|
+
if center_x is None:
|
|
396
|
+
# 1. Slow sinusoidal drift (thermal/mechanical creep)
|
|
397
|
+
slow_drift = np.sin(2 * np.pi * time / self.drift_period)
|
|
398
|
+
# 2. Brownian motion / random walk component
|
|
399
|
+
# (use momentum to avoid jerky movement)
|
|
400
|
+
random_component = np.random.normal(0, self.drift_random_walk_factor)
|
|
401
|
+
self.drift_current_velocity = (
|
|
402
|
+
self.drift_momentum * self.drift_current_velocity
|
|
403
|
+
+ (1 - self.drift_momentum) * random_component
|
|
404
|
+
)
|
|
405
|
+
# 3. Small high-frequency vibrations (building vibrations, etc)
|
|
406
|
+
vibration = (
|
|
407
|
+
self.drift_vibration_amplitude * np.sin(0.1 * time) * np.random.random()
|
|
408
|
+
)
|
|
409
|
+
# Combine all drift components
|
|
410
|
+
drift_amount = (
|
|
411
|
+
slow_drift * self.drift_speed
|
|
412
|
+
+ self.drift_current_velocity
|
|
413
|
+
+ vibration
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# Update drift center with bounds checking
|
|
417
|
+
self.drift_center += drift_amount
|
|
418
|
+
edge_margin = max(10, self.width // 10)
|
|
419
|
+
if self.drift_center < edge_margin:
|
|
420
|
+
self.drift_center = edge_margin
|
|
421
|
+
self.drift_current_velocity *= -0.5 # Bounce back with damping
|
|
422
|
+
elif self.drift_center > self.width - edge_margin:
|
|
423
|
+
self.drift_center = self.width - edge_margin
|
|
424
|
+
self.drift_current_velocity *= -0.5 # Bounce back with damping
|
|
425
|
+
|
|
426
|
+
center_x = self.drift_center
|
|
427
|
+
if center_y is None:
|
|
428
|
+
center_y = self.height // 2
|
|
429
|
+
|
|
430
|
+
x = np.arange(self.width)
|
|
431
|
+
y = np.arange(self.height)
|
|
432
|
+
X, Y = np.meshgrid(x, y)
|
|
433
|
+
|
|
434
|
+
pattern = self.drift_amplitude * np.exp(
|
|
435
|
+
-((X - center_x) ** 2) / (2 * self.drift_sigma_x**2)
|
|
436
|
+
- ((Y - center_y) ** 2) / (2 * self.drift_sigma_y**2)
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
if pattern.min() < 0:
|
|
440
|
+
pattern += abs(pattern.min())
|
|
441
|
+
|
|
442
|
+
return pattern
|
|
443
|
+
|
|
444
|
+
def get_single_molecule_events(self):
|
|
445
|
+
flux = np.zeros((self.height, self.width))
|
|
446
|
+
|
|
447
|
+
pass
|
|
448
|
+
|
|
449
|
+
def get_dummy_image_from_pattern(self, time):
|
|
450
|
+
'''
|
|
451
|
+
Generate a dummy image with noise and CMOS conversion based on
|
|
452
|
+
the camera properties.
|
|
453
|
+
|
|
454
|
+
Parameters
|
|
455
|
+
----------
|
|
456
|
+
time : float
|
|
457
|
+
The current time.
|
|
458
|
+
|
|
459
|
+
Returns
|
|
460
|
+
-------
|
|
461
|
+
ndarray
|
|
462
|
+
A 2D numpy array representing the dummy image
|
|
463
|
+
with added noise and CMOS conversion.
|
|
464
|
+
'''
|
|
465
|
+
if self.pattern_type.lower() == 'sinusoidal':
|
|
466
|
+
flux = self.get_sinus_diagonal_pattern(
|
|
467
|
+
time,
|
|
468
|
+
self.sinusoidal_amplitude,
|
|
469
|
+
self.sinusoidal_frequency,
|
|
470
|
+
self.sinusoidal_phase,
|
|
471
|
+
self.pattern_offset,
|
|
472
|
+
self.sinusoidal_direction,
|
|
473
|
+
)
|
|
474
|
+
elif self.pattern_type.lower() == 'single molecule sim':
|
|
475
|
+
flux = 0
|
|
476
|
+
elif self.pattern_type.lower() == 'gaussian':
|
|
477
|
+
flux = self.get_gaussian_beam_pattern(time, center_x=None, center_y=None)
|
|
478
|
+
else:
|
|
479
|
+
flux = self.flux
|
|
480
|
+
|
|
481
|
+
return self.get_dummy_image(flux)
|
|
482
|
+
|
|
483
|
+
@staticmethod
|
|
484
|
+
def get_camera_list():
|
|
485
|
+
return [
|
|
486
|
+
{
|
|
487
|
+
'Camera ID': 'Cam 0',
|
|
488
|
+
'Device ID': 0,
|
|
489
|
+
'Model': 'Dummy',
|
|
490
|
+
'Serial': '42023060',
|
|
491
|
+
'InUse': bool(miDummy.instances),
|
|
492
|
+
'Status': 'Ready',
|
|
493
|
+
'Sensor ID': '06032024',
|
|
494
|
+
'Driver': 'miDummy',
|
|
495
|
+
}
|
|
496
|
+
]
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
if __name__ == '__main__':
|
|
500
|
+
import timeit
|
|
501
|
+
|
|
502
|
+
import matplotlib.animation as animation
|
|
503
|
+
import matplotlib.pyplot as plt
|
|
504
|
+
|
|
505
|
+
# create a dummy camera object
|
|
506
|
+
cam = miDummy()
|
|
507
|
+
cam.pattern_type = 'gaussian'
|
|
508
|
+
|
|
509
|
+
# set up animation
|
|
510
|
+
fig, ax = plt.subplots()
|
|
511
|
+
im = ax.imshow(cam.get_dummy_image_from_pattern(0), cmap='gray', vmin=0)
|
|
512
|
+
|
|
513
|
+
plt.colorbar(im)
|
|
514
|
+
|
|
515
|
+
def update(frame):
|
|
516
|
+
img = cam.get_dummy_image_from_pattern(frame)
|
|
517
|
+
im.set_data(img)
|
|
518
|
+
return (im,)
|
|
519
|
+
|
|
520
|
+
ani = animation.FuncAnimation(
|
|
521
|
+
fig, update, frames=np.arange(0, 5, 0.05), interval=1, blit=True
|
|
522
|
+
)
|
|
523
|
+
# ani.save('animation_1.gif', writer='pillow', fps=30)
|
|
524
|
+
plt.show()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import pco
|
|
5
|
+
from pco.sdk import Sdk, shared_library_loader
|
|
6
|
+
|
|
7
|
+
interfaces = [
|
|
8
|
+
'FireWire',
|
|
9
|
+
'Camera Link MTX',
|
|
10
|
+
'GenICam',
|
|
11
|
+
'Camera Link NAT',
|
|
12
|
+
'GigE',
|
|
13
|
+
'USB 2.0',
|
|
14
|
+
'Camera Link ME4',
|
|
15
|
+
'USB 3.0',
|
|
16
|
+
'CLHS',
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _scanner(interfaces: list[str]):
|
|
21
|
+
shared_library_loader._load_all()
|
|
22
|
+
sdk = Sdk()
|
|
23
|
+
if sys.platform.startswith('linux'):
|
|
24
|
+
ret = sdk.scan_cameras()
|
|
25
|
+
devices_info = ret['devices']
|
|
26
|
+
else:
|
|
27
|
+
devices_info = []
|
|
28
|
+
for interface in interfaces:
|
|
29
|
+
for camera_number in range(64):
|
|
30
|
+
ret = sdk.open_camera_ex(
|
|
31
|
+
interface=interface, camera_number=camera_number
|
|
32
|
+
)
|
|
33
|
+
if sdk.camera_handle.value is not None:
|
|
34
|
+
ret['camera name'] = sdk.get_camera_name()['camera name'].replace(
|
|
35
|
+
' ', '_'
|
|
36
|
+
)
|
|
37
|
+
ret['serial number'] = sdk.get_camera_type()['serial number']
|
|
38
|
+
ret['interface'] = interface
|
|
39
|
+
sdk.close_camera()
|
|
40
|
+
|
|
41
|
+
if ret['error'] == 0:
|
|
42
|
+
devices_info.append(ret)
|
|
43
|
+
elif ret['error'] & 0x80002001 == 0x80002001:
|
|
44
|
+
continue
|
|
45
|
+
else:
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
shared_library_loader.decrement()
|
|
49
|
+
|
|
50
|
+
return devices_info
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
logger = logging.getLogger('pco')
|
|
54
|
+
logger.setLevel(logging.DEBUG)
|
|
55
|
+
logger.addHandler(pco.stream_handler)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
print(_scanner(interfaces))
|