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