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,456 @@
1
+ import contextlib
2
+
3
+ import numpy as np
4
+ import pyqtgraph as pg
5
+ import pyqtgraph.opengl as gl
6
+
7
+ from microEye.analysis.rendering.cloud import normalize_intensities
8
+ from microEye.analysis.rendering.volumetric import (
9
+ create_rgba_volume,
10
+ normalize_volume,
11
+ )
12
+ from microEye.qt import Qt, QtGui, QtWidgets
13
+
14
+
15
+ class VolumeViewerWindow(QtWidgets.QWidget):
16
+ def __init__(self, volume_data: np.ndarray = None, metadata=None):
17
+ super().__init__()
18
+ self.setWindowTitle('3D Volume Viewer')
19
+
20
+ # Store data and metadata
21
+ self.volume_data = volume_data
22
+ self.normalized_data = None
23
+ self.metadata = (
24
+ metadata
25
+ if metadata is not None
26
+ else {
27
+ 'voxel_size': {'x': 1, 'y': 1, 'z': 1},
28
+ 'coordinates': {'z_min': 0, 'z_max': 1},
29
+ }
30
+ )
31
+
32
+ # Create the main widget and layout
33
+ self.setLayout(QtWidgets.QHBoxLayout())
34
+
35
+ # Create 3D viewport
36
+ self.init_3d_viewport()
37
+
38
+ # Create control panel
39
+ self.init_control_panel()
40
+
41
+ # Set window size
42
+ self.resize(1200, 800)
43
+
44
+ # Initialize volume visualization if data is provided
45
+ if volume_data is not None:
46
+ self.set_volume_data(volume_data, metadata)
47
+
48
+ def init_3d_viewport(self):
49
+ '''Initialize the 3D viewport with PyQtGraph GLViewWidget.'''
50
+ self.view = gl.GLViewWidget()
51
+ self.layout().addWidget(self.view, stretch=4)
52
+
53
+ self.axis = gl.GLAxisItem()
54
+ self.axis.setSize(x=200, y=200, z=200)
55
+ self.view.addItem(self.axis)
56
+
57
+ self.grid = gl.GLGridItem()
58
+ self.grid.setSize(x=200, y=200)
59
+ self.grid.translate(dx=0, dy=0, dz=0)
60
+ self.view.addItem(self.grid)
61
+
62
+ self.volume_item = None
63
+
64
+ def init_control_panel(self):
65
+ '''Initialize the control panel with sliders and options.'''
66
+ control_widget = QtWidgets.QWidget()
67
+ control_layout = QtWidgets.QVBoxLayout(control_widget)
68
+ self.layout().addWidget(control_widget, stretch=1)
69
+
70
+ # Add color map gradient widget
71
+ colormap_label = QtWidgets.QLabel('Color Map')
72
+ control_layout.addWidget(colormap_label)
73
+
74
+ self.colormap_preview = pg.GradientWidget(orientation='bottom')
75
+ self.colormap_preview.setMinimumHeight(40)
76
+ self.colormap_preview.item.setColorMap(pg.colormap.get('jet', 'matplotlib'))
77
+ self.colormap_preview.sigGradientChanged.connect(self.update_volume)
78
+ control_layout.addWidget(self.colormap_preview)
79
+
80
+ # Add opacity slider
81
+ opacity_label = QtWidgets.QLabel('Opacity')
82
+ control_layout.addWidget(opacity_label)
83
+
84
+ self.opacity_slider = QtWidgets.QSlider(Qt.Orientation.Horizontal)
85
+ self.opacity_slider.setMinimum(0)
86
+ self.opacity_slider.setMaximum(100)
87
+ self.opacity_slider.setValue(100)
88
+ self.opacity_slider.valueChanged.connect(self.update_volume)
89
+ control_layout.addWidget(self.opacity_slider)
90
+
91
+ # Add spacer
92
+ control_layout.addStretch()
93
+
94
+ def update_volume(self):
95
+ '''
96
+ Update the volume visualization with current color map and opacity settings.
97
+ Using accelerated processing functions.
98
+ '''
99
+ if self.volume_data is None:
100
+ return
101
+
102
+ # Remove existing volume if any
103
+ if self.volume_item is not None:
104
+ with contextlib.suppress(Exception):
105
+ self.view.removeItem(self.volume_item)
106
+
107
+ # Normalize data using accelerated function
108
+ if self.normalized_data is None:
109
+ self.normalized_data = normalize_volume(self.volume_data)
110
+
111
+ # Get color lookup table from gradient widget
112
+ colors = self.colormap_preview.item.colorMap().getLookupTable(0.0, 1.0, 256)
113
+ colors = colors[:, :3].astype(np.uint8) # Extract RGB components
114
+
115
+ # Create RGBA volume with accelerated function
116
+ opacity = self.opacity_slider.value() / 100.0
117
+
118
+ rgba_volume = create_rgba_volume(self.normalized_data, colors, opacity)
119
+
120
+ # Create volume item with proper scaling
121
+ scale = (
122
+ self.metadata['voxel_size']['x'],
123
+ self.metadata['voxel_size']['y'],
124
+ self.metadata['voxel_size']['z'],
125
+ )
126
+
127
+ self.volume_item = gl.GLVolumeItem(rgba_volume)
128
+
129
+ # Position the volume with respect to Z zero point
130
+ z_offset = self.metadata['coordinates']['z_min']
131
+ self.volume_item.translate(0, 0, z_offset)
132
+
133
+ # Add to view
134
+ self.view.addItem(self.volume_item)
135
+
136
+ def set_volume_data(self, volume_data, metadata=None):
137
+ '''Set or update the volume data to visualize.'''
138
+ self.volume_data = volume_data
139
+ if metadata is not None:
140
+ self.metadata = metadata
141
+
142
+ # Update the visualization
143
+ self.update_volume()
144
+
145
+
146
+ class PointCloudViewer(QtWidgets.QWidget):
147
+ def __init__(
148
+ self, points: np.ndarray = None, intensities: np.ndarray = None, metadata=None
149
+ ):
150
+ '''
151
+ Initialize the point cloud viewer.
152
+
153
+ Args:
154
+ points: Nx3 numpy array of point coordinates (x, y, z)
155
+ intensities: N numpy array of intensity values
156
+ metadata: Dictionary containing additional information
157
+ '''
158
+ super().__init__()
159
+ self.setWindowTitle('3D Point Cloud Viewer')
160
+
161
+ # Store data and metadata
162
+ self.points = points
163
+ self.intensities = intensities
164
+ self.metadata = (
165
+ metadata
166
+ if metadata is not None
167
+ else {
168
+ 'point_size': 2,
169
+ 'coordinates': {'z_min': 0, 'z_max': 1},
170
+ 'bin_size': {
171
+ 'x': 10,
172
+ 'y': 10,
173
+ 'z': 10,
174
+ },
175
+ }
176
+ )
177
+
178
+ # Create the main widget and layout
179
+ self.main_layout = QtWidgets.QHBoxLayout()
180
+ self.setLayout(self.main_layout)
181
+
182
+ # Create 3D viewport
183
+ self.init_3d_viewport()
184
+
185
+ # Create control panel
186
+ self.init_control_panel()
187
+
188
+ # Set window size
189
+ self.resize(1200, 800)
190
+
191
+ self.show()
192
+
193
+ # Initialize point cloud visualization if data is provided
194
+ # if points is not None:
195
+ # self.update_point_cloud()
196
+
197
+ # Initialize histogram if we have data
198
+ if self.intensities is not None:
199
+ self.update_histogram()
200
+
201
+ def set_3d_view(self, args: tuple[int]):
202
+ '''Set the 3D view to a specific angle.'''
203
+ self.view.setCameraPosition(
204
+ pos=QtGui.QVector3D(*(np.max(self.points, axis=0) / 2)),
205
+ elevation=args[0],
206
+ azimuth=args[1],
207
+ distance=1.1 * np.max(self.points),
208
+ )
209
+
210
+ def closeEvent(self, event: QtGui.QCloseEvent):
211
+ '''Cleanup and close the window.
212
+
213
+ Params
214
+ ------
215
+ event : QCloseEvent
216
+ Close event object
217
+ '''
218
+ self.view.clear()
219
+ self.view.close()
220
+ event.accept()
221
+
222
+ def init_3d_viewport(self):
223
+ '''Initialize the 3D viewport with PyQtGraph GLViewWidget.'''
224
+ self.view = gl.GLViewWidget()
225
+ self.main_layout.addWidget(self.view, stretch=4)
226
+
227
+ # Set some reasonable defaults for camera
228
+ self.view.setCameraPosition(
229
+ pos=QtGui.QVector3D(*np.mean(self.points, axis=0)),
230
+ elevation=-90,
231
+ azimuth=-90,
232
+ distance=1.1 * np.max(self.points),
233
+ )
234
+ # make view ortho instead of perspective
235
+ self.view.opts['fov'] = 60
236
+ self.view.update()
237
+
238
+ def init_control_panel(self):
239
+ '''Initialize the control panel with point size and color options.'''
240
+ control_widget = QtWidgets.QWidget()
241
+ control_layout = QtWidgets.QVBoxLayout(control_widget)
242
+ self.main_layout.addWidget(control_widget, stretch=1)
243
+
244
+ # Add color map gradient widget
245
+ colormap_group = QtWidgets.QGroupBox('Color Map')
246
+ colormap_layout = QtWidgets.QVBoxLayout()
247
+ colormap_group.setLayout(colormap_layout)
248
+
249
+ self.colormap_preview = pg.GradientWidget(orientation='bottom')
250
+ self.colormap_preview.setMinimumHeight(40)
251
+ self.colormap_preview.item.setColorMap(pg.colormap.get('jet', 'matplotlib'))
252
+ self.colormap_preview.sigGradientChanged.connect(self.update_point_cloud)
253
+ colormap_layout.addWidget(self.colormap_preview)
254
+
255
+ # Add preset buttons
256
+ preset_layout = QtWidgets.QHBoxLayout()
257
+ presets = ['viridis', 'plasma', 'inferno', 'magma']
258
+ for preset in presets:
259
+ btn = QtWidgets.QPushButton(preset)
260
+ btn.setMaximumWidth(60)
261
+ btn.clicked.connect(
262
+ lambda checked, p=preset: self.colormap_preview.loadPreset(p)
263
+ )
264
+ preset_layout.addWidget(btn)
265
+ colormap_layout.addLayout(preset_layout)
266
+ control_layout.addWidget(colormap_group)
267
+
268
+ # Add point size control
269
+ display_options = QtWidgets.QGroupBox('Display Options')
270
+ display_layout = QtWidgets.QFormLayout()
271
+ display_options.setLayout(display_layout)
272
+
273
+ self.size_slider = QtWidgets.QSlider(Qt.Orientation.Horizontal)
274
+ self.size_slider.setMinimum(1)
275
+ self.size_slider.setMaximum(100)
276
+ self.size_slider.setValue(self.metadata['bin_size']['x'])
277
+ self.size_slider.valueChanged.connect(self.update_point_cloud)
278
+
279
+ size_display = QtWidgets.QSpinBox()
280
+ size_display.setMinimum(1)
281
+ size_display.setMaximum(100)
282
+ size_display.setValue(self.size_slider.value())
283
+ self.size_slider.valueChanged.connect(size_display.setValue)
284
+ size_display.valueChanged.connect(self.size_slider.setValue)
285
+
286
+ display_layout.addWidget(self.size_slider)
287
+ display_layout.addWidget(size_display)
288
+
289
+ # Add grid and axis to display
290
+ self.axis_checkbox = QtWidgets.QCheckBox('Show Axis')
291
+ self.axis_checkbox.setChecked(True)
292
+ self.axis_checkbox.stateChanged.connect(lambda: self.update_point_cloud())
293
+ display_layout.addWidget(self.axis_checkbox)
294
+ self.grid_checkbox = QtWidgets.QCheckBox('Show Grid')
295
+ self.grid_checkbox.setChecked(True)
296
+ self.grid_checkbox.stateChanged.connect(lambda: self.update_point_cloud())
297
+ display_layout.addWidget(self.grid_checkbox)
298
+
299
+ fov_spin = QtWidgets.QSpinBox()
300
+ fov_spin.setMinimum(1)
301
+ fov_spin.setMaximum(180)
302
+ fov_spin.setValue(1)
303
+ fov_spin.valueChanged.connect(
304
+ lambda: self.view.setCameraParams(fov=fov_spin.value())
305
+ )
306
+ display_layout.addWidget(fov_spin)
307
+
308
+ control_layout.addWidget(display_options)
309
+
310
+ # View controls
311
+ view_controls = QtWidgets.QGroupBox('View Controls')
312
+ view_layout = QtWidgets.QHBoxLayout()
313
+
314
+ view_buttons = {
315
+ 'T': (-90, -90),
316
+ 'B': (90, 90),
317
+ 'F': (0, 0),
318
+ 'Bck': (0, 180),
319
+ 'L': (0, 90),
320
+ 'R': (0, -90),
321
+ }
322
+
323
+ for label, rotation in view_buttons.items():
324
+ btn = QtWidgets.QPushButton(label)
325
+ btn.clicked.connect(lambda checked, r=rotation: self.set_3d_view(r))
326
+ view_layout.addWidget(btn)
327
+
328
+ view_controls.setLayout(view_layout)
329
+ control_layout.addWidget(view_controls)
330
+
331
+ # Add intensity histogram with range selection
332
+ intensity_group = QtWidgets.QGroupBox('Intensity Range')
333
+ intensity_layout = QtWidgets.QVBoxLayout()
334
+ intensity_group.setLayout(intensity_layout)
335
+
336
+ # Create histogram plot
337
+ self.hist_plot = pg.PlotWidget()
338
+ self.hist_plot.setMinimumHeight(100)
339
+ intensity_layout.addWidget(self.hist_plot)
340
+
341
+ # Reset range
342
+ reset_button = QtWidgets.QPushButton('Reset Range')
343
+ reset_button.clicked.connect(lambda: self.update_histogram())
344
+ intensity_layout.addWidget(reset_button)
345
+
346
+ # Create region selection
347
+ self.region = pg.LinearRegionItem([0, 100])
348
+ self.region.sigRegionChangeFinished.connect(self.update_point_cloud)
349
+ self.hist_plot.addItem(self.region)
350
+
351
+ control_layout.addWidget(intensity_group)
352
+
353
+ # Add spacer
354
+ control_layout.addStretch()
355
+
356
+ def update_histogram(self):
357
+ '''Update the intensity histogram display.'''
358
+ if self.intensities is None:
359
+ return
360
+
361
+ # Clear previous histogram
362
+ self.hist_plot.clear()
363
+
364
+ # Calculate histogram
365
+ y, x = np.histogram(self.intensities, bins=1000)
366
+
367
+ # Create histogram
368
+ self.hist_plot.plot(
369
+ x, y, stepMode='center', fillLevel=0, brush=(0, 0, 255, 150)
370
+ )
371
+
372
+ # Add region selection
373
+ if self.region not in self.hist_plot.items():
374
+ self.hist_plot.addItem(self.region)
375
+
376
+ # Set initial region positions to 1st and 99th percentiles
377
+ p1, p99 = np.percentile(self.intensities, [1, 99])
378
+ self.region.setRegion([p1, p99])
379
+
380
+ def map_colors(self, intensities):
381
+ '''Map intensity values to colors using current colormap.'''
382
+ # Get intensity range from region selection
383
+ lower, upper = self.region.getRegion()
384
+
385
+ # Clip and normalize intensities
386
+ norm_intensities = np.clip(intensities, lower, upper)
387
+ norm_intensities = (norm_intensities - lower) / (upper - lower)
388
+
389
+ # Get color lookup table from gradient widget
390
+ colormap = self.colormap_preview.item.colorMap()
391
+ colors = colormap.map(norm_intensities, mode='float')
392
+
393
+ colors[intensities < lower, 3] = 0
394
+
395
+ return colors
396
+
397
+ def update_point_cloud(self):
398
+ '''Update the point cloud visualization with current settings.'''
399
+ if self.points is None:
400
+ return
401
+
402
+ # Remove existing scatter if any
403
+ self.view.clear()
404
+
405
+ # Map intensities to colors
406
+ if self.intensities is not None:
407
+ colors = self.map_colors(self.intensities)
408
+ else:
409
+ # Use height (z-coordinate) for coloring if no intensities provided
410
+ z_values = self.points[:, 2]
411
+ colors = self.map_colors(z_values)
412
+
413
+ # Add axis and grid based on data extent
414
+ data_range = np.ptp(self.points, axis=0)
415
+ grid_size = max(data_range[0], data_range[1])
416
+
417
+ if self.grid_checkbox.isChecked():
418
+ grid = gl.GLGridItem(QtGui.QVector3D(grid_size, grid_size, 1))
419
+ grid.translate(dx=grid_size / 2, dy=grid_size / 2, dz=0)
420
+ grid.setSpacing(x=grid_size / 10, y=grid_size / 10)
421
+ self.view.addItem(grid)
422
+
423
+ if self.axis_checkbox.isChecked():
424
+ axis = gl.GLAxisItem()
425
+ axis.setSize(x=grid_size / 10, y=grid_size / 10, z=data_range[2] / 10)
426
+ self.view.addItem(axis)
427
+
428
+ # Create scatter plot item
429
+ scatter_item = gl.GLScatterPlotItem(
430
+ pos=self.points,
431
+ color=colors,
432
+ size=self.size_slider.value(),
433
+ pxMode=False,
434
+ )
435
+
436
+ # Add to view
437
+ self.view.addItem(scatter_item)
438
+
439
+
440
+ def show_volume(volume_data, metadata=None):
441
+ '''Convenience function to show volume data in a new window.'''
442
+ import sys
443
+
444
+ from microEye.qt import QApplication
445
+
446
+ # Create QApplication instance if it doesn't exist
447
+ app = QApplication.instance()
448
+ if app is None:
449
+ app = QApplication(sys.argv)
450
+
451
+ # Create and show window
452
+ viewer = VolumeViewerWindow(volume_data, metadata)
453
+ viewer.show()
454
+
455
+ # Return viewer instance
456
+ return viewer
@@ -0,0 +1,3 @@
1
+ from microEye.hardware.mieye import miEye_module
2
+ from microEye.hardware.misc.reglo import reglo_digital_module
3
+ from microEye.hardware.misc.temp import temperature_monitor
@@ -0,0 +1,29 @@
1
+ from microEye.hardware.cams.camera_calibration import dark_calibration
2
+ from microEye.hardware.cams.camera_list import CameraList
3
+ from microEye.hardware.cams.camera_panel import Camera_Panel, CamParams
4
+ from microEye.hardware.cams.jobs import AcquisitionJob
5
+ from microEye.hardware.cams.linescan.IR_Cam import (
6
+ DemoLineScanner,
7
+ IR_Cam,
8
+ ParallaxLineScanner,
9
+ )
10
+ from microEye.hardware.cams.micam import miCamera, miDummy
11
+ from microEye.hardware.cams.thorlabs.thorlabs import CMD, thorlabs_camera
12
+ from microEye.hardware.cams.thorlabs.thorlabs_panel import Thorlabs_Panel
13
+
14
+ try:
15
+ from pyueye import ueye
16
+
17
+ from microEye.hardware.cams.ueye.ueye_camera import IDS_Camera
18
+ from microEye.hardware.cams.ueye.ueye_panel import IDS_Panel
19
+ except Exception:
20
+ ueye = None
21
+ IDS_Camera = None
22
+ IDS_Panel = None
23
+ try:
24
+ import vimba as vb
25
+
26
+ from microEye.hardware.cams.vimba.vimba_cam import vimba_cam
27
+ from microEye.hardware.cams.vimba.vimba_panel import Vimba_Panel
28
+ except Exception:
29
+ vb = None
@@ -0,0 +1,99 @@
1
+ import os
2
+
3
+ import numpy as np
4
+ import tifffile as tf
5
+
6
+
7
+ class dark_calibration:
8
+ '''A class holder for pixelwise average and variance.
9
+
10
+ Intended to be used for photon-free calibration:
11
+ https://www.biorxiv.org/lookup/doi/10.1101/2021.04.16.440125
12
+ http://dx.doi.org/10.1038/s41467-022-30907-2
13
+
14
+ ACCENT (ImageJ/Fiji Plugin):
15
+ https://github.com/ries-lab/Accent
16
+ '''
17
+ def __init__(self, shape, exposure):
18
+ self._counter = 0
19
+ self._exposure = exposure
20
+ self._shape = shape
21
+ self._accumulator = np.zeros(
22
+ shape=shape, dtype=np.float64)
23
+ self._quad_accum = np.zeros(
24
+ shape=shape, dtype=np.float64)
25
+
26
+ def addFrame(self, image: np.ndarray):
27
+ '''Adds an image frame to the mean/variance estimators.
28
+
29
+ Parameters
30
+ ----------
31
+ image : np.ndarray
32
+ image of dark_calibration shape to be added.
33
+
34
+ Raises
35
+ ------
36
+ ValueError
37
+ image of wrong shape.
38
+ '''
39
+ if image.shape != self._shape:
40
+ raise ValueError('Image of wrong shape.')
41
+
42
+ self._accumulator += image
43
+ self._quad_accum += np.square(image)
44
+ self._counter += 1
45
+
46
+ def getResults(self):
47
+ '''Gets the resulting mean and variance frames.
48
+
49
+ Returns
50
+ -------
51
+ tuple(ndarray, ndarray)
52
+ the rasults (mean, variance)
53
+
54
+ Raises
55
+ ------
56
+ ValueError
57
+ in case zero frames are added.
58
+ '''
59
+ if self._counter < 1:
60
+ raise ValueError('Counter should be non-zero.')
61
+
62
+ mean = self._accumulator / self._counter
63
+ variance = (
64
+ self._quad_accum -
65
+ np.square(self._accumulator)/self._counter)/(self._counter - 1)
66
+
67
+ return mean, variance
68
+
69
+ def saveResults(self, path, prefix):
70
+ if not os.path.exists(path):
71
+ os.makedirs(path)
72
+
73
+ def getFilename(index: int):
74
+ return path + \
75
+ f'\\image_{index:05d}.ome.tif'
76
+
77
+ mean, variance = self.getResults()
78
+
79
+ with tf.TiffWriter(
80
+ path + prefix + \
81
+ f'_image_mean_{self._exposure:.5f}_ms'.replace('.', '_') + \
82
+ '.ome.tif',
83
+ append=False,
84
+ bigtiff=False,
85
+ ome=False) as writer:
86
+ writer.write(
87
+ data=mean,
88
+ photometric='minisblack')
89
+
90
+ with tf.TiffWriter(
91
+ path + prefix + \
92
+ f'_image_var_{self._exposure:.5f}_ms'.replace('.', '_') + \
93
+ '.ome.tif',
94
+ append=False,
95
+ bigtiff=False,
96
+ ome=False) as writer:
97
+ writer.write(
98
+ data=variance,
99
+ photometric='minisblack')