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,941 @@
1
+ import math
2
+ import os
3
+ import re
4
+ import threading
5
+ import time
6
+ import traceback
7
+ from functools import lru_cache
8
+ from queue import Queue
9
+
10
+ import cv2
11
+ import numpy as np
12
+ import pyqtgraph as pg
13
+
14
+ from microEye.hardware.cams.camera_options import CameraOptions, CamParams
15
+ from microEye.hardware.cams.jobs import AcquisitionJob
16
+ from microEye.hardware.cams.line_profiler import LineProfiler
17
+ from microEye.hardware.cams.micam import miCamera
18
+ from microEye.hardware.cams.shortcuts import (
19
+ CameraShortcutsWidget,
20
+ )
21
+ from microEye.qt import QDateTime, Qt, QtCore, QtWidgets, Signal, Slot
22
+ from microEye.utils.expandable_groupbox import ExpandableGroupBox
23
+ from microEye.utils.metadata_tree import MetadataEditorTree, MetaParams
24
+ from microEye.utils.thread_worker import QThreadWorker
25
+ from microEye.utils.uImage import uImage
26
+
27
+
28
+ class Camera_Panel(QtWidgets.QGroupBox):
29
+ '''
30
+ A Qt Widget base class for controlling a camera | Inherits QGroupBox
31
+ '''
32
+
33
+ exposureChanged = Signal()
34
+ asyncFreerun = Signal(str, threading.Event)
35
+ updateStatsSignal = Signal(tuple)
36
+ updateRangeSignal = Signal(int, int)
37
+
38
+ def __init__(self, cam: miCamera, mini=False, *args, **kwargs):
39
+ '''
40
+ Initializes a new Camera_Panel Qt widget.
41
+
42
+ Inherits QGroupBox.
43
+
44
+ Parameters
45
+ ----------
46
+ cam : miCamera
47
+ Camera python adapter.
48
+
49
+ mini : bool, optional
50
+ Flag indicating if this is a mini camera panel, by default False.
51
+
52
+ Other Parameters
53
+ ---------------
54
+ *args
55
+ Arguments to pass to the QGroupBox constructor.
56
+
57
+ **kwargs
58
+ Keyword arguments to pass to the QGroupBox constructor.
59
+ '''
60
+ super().__init__(*args, **kwargs)
61
+
62
+ self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
63
+
64
+ self._cam = cam # miCamera
65
+ # flag true if master (always the first added camera is master)
66
+ self.master = False
67
+
68
+ self.c_worker = None # worker for capturing
69
+ self.d_worker = None # worker for display
70
+ self.s_worker = None # worker for saving
71
+
72
+ self.asyncFreerun.connect(
73
+ lambda prefix, event: self.start_free_run(Prefix=prefix, Event=event)
74
+ )
75
+
76
+ # number of bins for the histogram (4096 is set for 12bit mono-camera)
77
+ self._nBins = 2**12
78
+ self._hist = np.arange(0, self._nBins) * 0 # arrays for the Hist plot
79
+ self._bins = np.arange(0, self._nBins) # arrays for the Hist plot
80
+
81
+ self.__update_timer = time.time()
82
+ self.updateStatsSignal.connect(self.updateStats)
83
+ self.updateRangeSignal.connect(self.updateRange)
84
+
85
+ self._threadpool = (
86
+ QtCore.QThreadPool.globalInstance()
87
+ ) # the threadpool for workers
88
+
89
+ # flag true to close camera adapter and dispose it
90
+ self._dispose_cam = False
91
+ self._buffer = Queue() # data memory stack
92
+ self._temps = Queue() # camera sensor temperature stack
93
+ # stack of (frame, temperature) tuples (for saving)
94
+ self.frame = None
95
+ self._frames = Queue()
96
+ # reserved for saving
97
+ home = os.path.expanduser('~')
98
+
99
+ self._directory = os.path.join(home, 'Desktop') # save directory
100
+ self._save_path = '' # save path
101
+ self._save_prefix = '' # save prefix
102
+
103
+ # acquisition job info class
104
+ self.acq_job = None
105
+
106
+ self.mini = mini
107
+
108
+ # Line Profiler
109
+ self.lineProfiler = LineProfiler()
110
+
111
+ # main layout
112
+ self.main_layout = QtWidgets.QVBoxLayout()
113
+
114
+ # set main layout
115
+ self.setLayout(self.main_layout)
116
+
117
+ # the main tab widget
118
+ self.main_tab_view = QtWidgets.QTabWidget()
119
+ self.main_layout.addWidget(self.main_tab_view)
120
+
121
+ # tab widgets
122
+ self.first_tab = QtWidgets.QWidget()
123
+
124
+ self.OME_tab = MetadataEditorTree()
125
+
126
+ # add tabs
127
+ self.main_tab_view.addTab(self.first_tab, 'Main')
128
+ if not self.mini:
129
+ self.main_tab_view.addTab(self.OME_tab, 'OME-XML metadata')
130
+
131
+ # first tab vertical layout
132
+ self.first_tab_Layout = QtWidgets.QFormLayout()
133
+ # set as first tab layout
134
+ self.first_tab.setLayout(self.first_tab_Layout)
135
+
136
+ self.camera_options = CameraOptions()
137
+
138
+ # Create the camera shortcuts widget
139
+ self.cam_shortcuts = CameraShortcutsWidget(
140
+ camera=self._cam,
141
+ )
142
+
143
+ def convinient_naming(mode: str):
144
+ name: str = self.camera_options.get_param_value(CamParams.EXPERIMENT_NAME)
145
+
146
+ if mode == 'exposure':
147
+ match = re.search(
148
+ r'(\d+)(?:_ms|ms)', name
149
+ ) # find any number of format '000_ms' or '00ms'
150
+ if match:
151
+ exposure = self._cam.getExposure()
152
+ # update the exposure time in the name
153
+ name = name.replace(match.group(0), f'{exposure:03.0f}ms')
154
+ else:
155
+ name = f'{name}_{self._cam.getExposure():03.0f}ms'
156
+
157
+ elif mode == 'index':
158
+ # only from the start of the name
159
+ match = re.search(r'^\d+', name)
160
+ if match:
161
+ index = int(match.group())
162
+ index += 1
163
+ name = re.sub(r'^\d+', f'{index:03d}', name)
164
+ else:
165
+ name = f'001_{name}'
166
+
167
+
168
+ self.camera_options.set_param_value(CamParams.EXPERIMENT_NAME, name)
169
+
170
+
171
+ self.cam_shortcuts.exposureChanged.connect(self.setExposure)
172
+ self.cam_shortcuts.autostretchChanged.connect(
173
+ self.camera_options.toggleAutostretch
174
+ )
175
+ self.cam_shortcuts.previewChanged.connect(self.camera_options.togglePreview)
176
+ self.cam_shortcuts.saveDataChanged.connect(self.camera_options.toggleSaveData)
177
+ self.cam_shortcuts.zoomChanged.connect(self.camera_options.setZoom)
178
+ self.cam_shortcuts.acquisitionStart.connect(self.start_free_run)
179
+ self.cam_shortcuts.acquisitionStop.connect(self.stop)
180
+ self.cam_shortcuts.adjustName.connect(convinient_naming)
181
+
182
+ # Create the context menu
183
+ menu = self.cam_shortcuts.create_context_menu(self.camera_options)
184
+
185
+ def show_shortcuts(pos):
186
+ self.cam_shortcuts.blockSignals(True)
187
+ self.cam_shortcuts.set_exposure(
188
+ self.camera_options.get_param_value(CamParams.EXPOSURE)
189
+ )
190
+ self.cam_shortcuts.set_zoom(
191
+ self.camera_options.get_param_value(CamParams.RESIZE_DISPLAY)
192
+ )
193
+ self.cam_shortcuts.set_autostretch(
194
+ self.camera_options.get_param_value(CamParams.AUTO_STRETCH)
195
+ )
196
+ self.cam_shortcuts.set_preview(
197
+ self.camera_options.get_param_value(CamParams.PREVIEW)
198
+ )
199
+ self.cam_shortcuts.set_save_data(
200
+ self.camera_options.get_param_value(CamParams.SAVE_DATA)
201
+ )
202
+ self.cam_shortcuts.blockSignals(False)
203
+
204
+ # display menu but centered horizontally
205
+ menu.exec(
206
+ self.main_tab_view.mapToGlobal(
207
+ QtCore.QPoint(self.main_tab_view.rect().left(), pos.y())
208
+ )
209
+ )
210
+
211
+ self.camera_options.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
212
+ self.camera_options.customContextMenuRequested.connect(show_shortcuts)
213
+
214
+ self.first_tab_Layout.addRow(self.camera_options)
215
+
216
+ self.camera_options.get_param(
217
+ CamParams.EXPERIMENT_NAME
218
+ ).sigValueChanged.connect(
219
+ lambda x: self.OME_tab.set_param_value(
220
+ MetaParams.EXPERIMENT_NAME, x.value()
221
+ )
222
+ )
223
+
224
+ self.save_dir_layout = QtWidgets.QHBoxLayout()
225
+
226
+ self.camera_options.set_param_value(CamParams.SAVE_DIRECTORY, self._directory)
227
+ self.camera_options.directoryChanged.connect(
228
+ lambda value: self.directory_changed(value)
229
+ )
230
+
231
+ save_param = self.camera_options.get_param(CamParams.SAVE_DATA)
232
+ save_param.setOpts(enabled=not self.mini)
233
+ save_param.setValue(self.mini)
234
+
235
+ preview_param = self.camera_options.get_param(CamParams.PREVIEW)
236
+ preview_param.setValue(not self.mini)
237
+ preview_param.setOpts(enabled=not self.mini)
238
+
239
+ self.camera_options.get_param(CamParams.DISPLAY_STATS_OPTION).setOpts(
240
+ enabled=not self.mini
241
+ )
242
+ self.camera_options.get_param(CamParams.VIEW_OPTIONS).setOpts(
243
+ visible=not self.mini
244
+ )
245
+ self.camera_options.get_param(CamParams.RESIZE_DISPLAY).setOpts(
246
+ enabled=not self.mini
247
+ )
248
+
249
+ profiler_param = self.camera_options.get_param(CamParams.LINE_PROFILER)
250
+ profiler_param.setOpts(enabled=not self.mini)
251
+ profiler_param.sigStateChanged.connect(
252
+ lambda: self.lineProfiler.show()
253
+ if self.camera_options.get_param_value(CamParams.LINE_PROFILER)
254
+ else self.lineProfiler.hide()
255
+ )
256
+
257
+ self.camera_options.setROI.connect(lambda: self.set_ROI())
258
+ self.camera_options.resetROI.connect(lambda: self.reset_ROI())
259
+ self.camera_options.centerROI.connect(lambda: self.center_ROI())
260
+ self.camera_options.selectROI.connect(lambda: self.select_ROI())
261
+ self.camera_options.selectROIs.connect(lambda: self.select_ROIs())
262
+ self.camera_options.viewOptionChanged.connect(lambda: self.view_toggled())
263
+
264
+ # controls for histogram and cdf
265
+ self.histogram_group = ExpandableGroupBox('Histogram')
266
+ self.first_tab_Layout.addRow(self.histogram_group)
267
+ # Hist plotWidget
268
+ self.histogram = pg.PlotWidget()
269
+ self.hist_cdf = pg.PlotWidget()
270
+ greenP = pg.mkPen(color='g')
271
+ blueP = pg.mkPen(color='b')
272
+ greenB = pg.mkBrush(0, 255, 0, 32)
273
+ blueB = pg.mkBrush(0, 0, 255, 32)
274
+ self._plot_ref = self.histogram.plot(self._bins, self._hist, pen=greenP)
275
+ self._cdf_plot_ref = self.hist_cdf.plot(self._bins, self._hist, pen=greenP)
276
+ self._plot_ref_2 = self.histogram.plot(self._bins, self._hist, pen=blueP)
277
+ self._cdf_plot_ref_2 = self.hist_cdf.plot(self._bins, self._hist, pen=blueP)
278
+
279
+ self.lr_0 = pg.LinearRegionItem(
280
+ (0, self._nBins),
281
+ bounds=(0, self._nBins),
282
+ pen=greenP,
283
+ brush=greenB,
284
+ movable=True,
285
+ swapMode='push',
286
+ span=(0.0, 1),
287
+ )
288
+ self.lr_1 = pg.LinearRegionItem(
289
+ (0, self._nBins),
290
+ bounds=(0, self._nBins),
291
+ pen=blueP,
292
+ brush=blueB,
293
+ movable=True,
294
+ swapMode='push',
295
+ span=(1, 1),
296
+ )
297
+ self.histogram.addItem(self.lr_0)
298
+ self.histogram.addItem(self.lr_1)
299
+
300
+ self.histogram_group.layout().addWidget(self.histogram)
301
+ self.histogram_group.layout().addWidget(self.hist_cdf)
302
+
303
+ CameraOptions.combine_params(self.PARAMS)
304
+
305
+ @property
306
+ def cam(self):
307
+ '''The Camera property.
308
+
309
+ Returns
310
+ -------
311
+ object
312
+ the cam property value
313
+ '''
314
+ return self._cam
315
+
316
+ @cam.setter
317
+ def cam(self, cam: miCamera):
318
+ '''The Camera property.
319
+
320
+ Parameters
321
+ ----------
322
+ cam : object
323
+ the object to set as panel camera.
324
+ '''
325
+ self._cam = cam
326
+
327
+ @property
328
+ def buffer(self):
329
+ '''Gets the frames Queue
330
+
331
+ Returns
332
+ -------
333
+ Queue
334
+ Frames buffer.
335
+ '''
336
+ if self._frames is not None:
337
+ return self._frames
338
+ else:
339
+ return Queue()
340
+
341
+ @property
342
+ def bufferSize(self):
343
+ '''Gets frames Queue size.
344
+
345
+ Returns
346
+ -------
347
+ int
348
+ Frames Queue size.
349
+ '''
350
+ if self._frames is not None:
351
+ return self._frames.qsize()
352
+ else:
353
+ return 0
354
+
355
+ @property
356
+ def isEmpty(self) -> bool:
357
+ '''Is frames Queue empty.
358
+
359
+ Returns
360
+ -------
361
+ bool
362
+ True if frames Queue is empty, otherwise False.
363
+ '''
364
+ return self._frames.empty()
365
+
366
+ @property
367
+ def Event(self):
368
+ '''The acquisition job threading Event.'''
369
+ if hasattr(self, '_acqEvent'):
370
+ return self._acqEvent
371
+ else:
372
+ return None
373
+
374
+ @Event.setter
375
+ def Event(self, value: threading.Event):
376
+ '''The acquisition job threading Event.'''
377
+ if value is None:
378
+ self._acqEvent = threading.Event()
379
+ else:
380
+ self._acqEvent = value
381
+
382
+ def get_event_action(self, param: str):
383
+ return {'name': str(param), 'type': 'action', 'event': 'Event'}
384
+
385
+ def __str__(self):
386
+ return f'{self.cam.name}'
387
+
388
+ def get(self, last=False) -> np.ndarray:
389
+ '''Gets image from frames Queue in FIFO or LIFO manner.
390
+
391
+ Parameters
392
+ ----------
393
+ last : bool, optional
394
+ Gets image from frames Queue in LIFO manner if True, by default False.
395
+
396
+ Returns
397
+ -------
398
+ np.ndarray
399
+ The image first or last in buffer.
400
+ '''
401
+ res = None
402
+ if not self._frames.empty():
403
+ if last:
404
+ while not self.isEmpty:
405
+ res = self._frames.get()
406
+ else:
407
+ res = self._frames.get()
408
+ else:
409
+ res = np.random.normal(size=(256, 256))
410
+ return res
411
+
412
+ def setExposure(self, value: float):
413
+ '''Sets the exposure time widget of camera
414
+
415
+ Parameters
416
+ ----------
417
+ value : float
418
+ selected exposure time
419
+ '''
420
+ self.camera_options.set_param_value(CamParams.EXPOSURE, value)
421
+
422
+ @property
423
+ def isOpen(self) -> bool:
424
+ '''Returns is acquisition active?
425
+
426
+ Returns
427
+ -------
428
+ bool
429
+ True if acquiring, otherwise False.
430
+ '''
431
+ return self.cam.acquisition
432
+
433
+ def set_ROI(self):
434
+ '''Sets the ROI for the slected camera'''
435
+ pass
436
+
437
+ def reset_ROI(self):
438
+ '''Resets the ROI for the slected camera'''
439
+ pass
440
+
441
+ def center_ROI(self):
442
+ '''Sets the ROI for the slected camera'''
443
+ pass
444
+
445
+ def select_ROI(self):
446
+ '''Selects the ROI for the camera'''
447
+ raise NotImplementedError('The select_ROI function is not implemented yet.')
448
+
449
+ def select_ROIs(self):
450
+ '''Selects the ROI for the camera'''
451
+ raise NotImplementedError('The select_ROIs function is not implemented yet.')
452
+
453
+ def get_meta(self):
454
+ return self.camera_options.get_json()
455
+
456
+ def getAcquisitionJob(self, event=None) -> AcquisitionJob:
457
+ self._save_path = os.path.join(
458
+ self._directory,
459
+ self.camera_options.get_param_value(CamParams.EXPERIMENT_NAME) + '\\',
460
+ )
461
+ self.Event = event
462
+ return AcquisitionJob(
463
+ self._temps,
464
+ self._buffer,
465
+ self._frames,
466
+ self._save_path,
467
+ self._cam.getHeight(),
468
+ self._cam.getWidth(),
469
+ biggTiff=self.camera_options.isBiggTiff,
470
+ bytes_per_pixel=self._cam.bytes_per_pixel,
471
+ exposure=self._cam.getExposure(),
472
+ frames=self.camera_options.get_param_value(CamParams.FRAMES),
473
+ full_tif_meta=self.camera_options.isFullMetadata,
474
+ is_dark_cal=self.camera_options.isDarkCalibration,
475
+ meta_file=self.get_meta(),
476
+ meta_func=self.OME_tab.gen_OME_XML
477
+ if self.camera_options.isFullMetadata
478
+ else self.OME_tab.gen_OME_XML_short,
479
+ name=self._cam.name,
480
+ prefix=self._save_prefix,
481
+ save=self.camera_options.isSaveData,
482
+ Zarr=not self.camera_options.isTiff,
483
+ rois=self.camera_options.get_export_rois(),
484
+ seperate_rois=self.camera_options.get_param_value(
485
+ CamParams.EXPORT_ROIS_SEPERATE
486
+ ),
487
+ flip_rois=self.camera_options.get_param_value(
488
+ CamParams.EXPORT_ROIS_FLIPPED
489
+ ),
490
+ s_event=self.Event,
491
+ )
492
+
493
+ def view_toggled(self):
494
+ if self.camera_options.isSingleView:
495
+ self.lr_0.setSpan(0, 1)
496
+ self.lr_1.setMovable(False)
497
+ self.lr_1.setRegion((0, self._nBins))
498
+ self.lr_1.setSpan(1.0, 1.0)
499
+ else:
500
+ self.lr_0.setSpan(0, 0.5)
501
+ self.lr_1.setMovable(True)
502
+ self.lr_1.setSpan(0.5, 1.0)
503
+
504
+ @Slot()
505
+ def directory_changed(self, value: str):
506
+ '''Slot for directory changed signal'''
507
+ if len(value) > 0:
508
+ self._directory = value
509
+
510
+ def start_free_run(self, param=None, Prefix='', Event=None):
511
+ '''
512
+ Starts free run acquisition mode
513
+
514
+ Parameters
515
+ ----------
516
+ param : Parameter
517
+ the parameter that was activated.
518
+ Prefix : str
519
+ an extra prefix added to the image stack file name.
520
+ '''
521
+ if self._cam.acquisition:
522
+ return # if acquisition is already going on
523
+
524
+ self._save_prefix = Prefix
525
+ self.acq_job = self.getAcquisitionJob(Event)
526
+
527
+ self._cam.acquisition = True # set acquisition flag to true
528
+
529
+ # start both capture and display workers
530
+ self.start_all_workers()
531
+
532
+ def stop(self):
533
+ if self.acq_job is not None:
534
+ self.acq_job.c_event.set()
535
+ # set stop acquisition workers flag to true
536
+ if self.acq_job is not None:
537
+ self.acq_job.stop_threads = True
538
+
539
+ def start_all_workers(self):
540
+ '''
541
+ Starts all workers
542
+ '''
543
+
544
+ self._buffer.queue.clear()
545
+ self._temps.queue.clear()
546
+ self._frames.queue.clear()
547
+ self.time = QDateTime.currentDateTime()
548
+
549
+ # Pass the display function to be executed
550
+ if self.d_worker is None or self.d_worker.is_set():
551
+ self.d_worker = QThreadWorker(cam_display, self.acq_job, self)
552
+
553
+ self.d_worker.signals.finished.connect(
554
+ lambda: self.acq_job.setDone(1, True)
555
+ )
556
+ # Execute
557
+ self.d_worker.setAutoDelete(True)
558
+ self._threadpool.start(self.d_worker)
559
+
560
+ # Pass the save function to be executed
561
+ if not self.mini:
562
+ if self.s_worker is None or self.s_worker.is_set():
563
+ self.s_worker = QThreadWorker(cam_save, self.acq_job)
564
+ self.s_worker.setAutoDelete(True)
565
+ # Execute
566
+ self._threadpool.start(self.s_worker)
567
+
568
+ # Pass the capture function to be executed
569
+ if self.c_worker is None or self.c_worker.is_set():
570
+ # Any other args, kwargs are passed to the run function
571
+ self.c_worker = QThreadWorker(self.cam_capture, *self.getCaptureArgs())
572
+ self.c_worker.signals.finished.connect(
573
+ lambda: self.acq_job.setDone(0, True)
574
+ )
575
+ self.c_worker.setAutoDelete(True)
576
+ # Execute
577
+ self._threadpool.start(self.c_worker)
578
+
579
+ def updateInfo(self):
580
+ if isinstance(self.acq_job, AcquisitionJob):
581
+ self.camera_options.set_param_value(
582
+ CamParams.TEMPERATURE, f'T {self._cam.temperature:.2f} °C'
583
+ )
584
+ self.camera_options.set_param_value(
585
+ CamParams.CAPTURE_STATS,
586
+ (
587
+ f'Capture {self.acq_job.frames_captured}/{self.acq_job.frames} '
588
+ + f'{self.acq_job.frames_captured / self.acq_job.frames:.2%} '
589
+ + f'| {self.acq_job.capture_time:.2f} ms'
590
+ ),
591
+ )
592
+ self.camera_options.set_param_value(
593
+ CamParams.DISPLAY_STATS,
594
+ f'Display {self._buffer.qsize()} | {self.acq_job.display_time:.2f} ms',
595
+ )
596
+ self.camera_options.set_param_value(
597
+ CamParams.SAVE_STATS,
598
+ f'Save {self._frames.qsize():d} | {self.acq_job.save_time:.2f} ms ',
599
+ )
600
+
601
+ def getCaptureArgs(self) -> list:
602
+ '''User specific arguments to be passed to the parallelized
603
+ Camera_Panel.cam_capture function.
604
+
605
+ Example
606
+ ------
607
+ check child panels (vimba, ueye or thorlabs)
608
+
609
+ Returns
610
+ -------
611
+ list
612
+ list of args to be passed in order to
613
+ Camera_Panel.cam_capture function.
614
+
615
+ Raises
616
+ ------
617
+ NotImplementedError
618
+ Has to be implemented by the use in child class.
619
+ '''
620
+ args = []
621
+ raise NotImplementedError('The getCaptureArgs function is not implemented yet.')
622
+
623
+ def cam_capture(self, *args, **kwargs):
624
+ '''User specific implemented logic for frame capture
625
+ from a miCamera adapter.
626
+
627
+ Example
628
+ ------
629
+ check child panels (vimba, ueye or thorlabs)
630
+
631
+ Raises
632
+ ------
633
+ NotImplementedError
634
+ Has to be implemented by the use in child class.
635
+ '''
636
+ raise NotImplementedError('The cam_capture function is not implemented yet.')
637
+
638
+ def updateStats(self, frames: tuple[uImage]):
639
+ now = time.time()
640
+ if now - self.__update_timer < 0.1:
641
+ return
642
+
643
+ self.__update_timer = now
644
+ for idx, frame in enumerate(frames):
645
+ if idx < 2:
646
+ _lr = self.lr_0 if idx == 0 else self.lr_1
647
+ _plot_ref = self._plot_ref if idx == 0 else self._plot_ref_2
648
+ _cdf_plot_ref = self._cdf_plot_ref if idx == 0 else self._cdf_plot_ref_2
649
+
650
+ if frame is not None:
651
+ if self.camera_options.isAutostretch:
652
+ _lr.setRegion((frame._min, frame._max))
653
+
654
+ _plot_ref.setData(frame._hist)
655
+ _cdf_plot_ref.setData(frame._cdf)
656
+ else:
657
+ _plot_ref.setData(self._hist)
658
+ _cdf_plot_ref.setData(self._hist)
659
+ else:
660
+ break
661
+
662
+ def updateRange(self, rmin: int, rmax: int):
663
+ if time.time() - self.__update_timer < 0.1:
664
+ return
665
+
666
+ if self.camera_options.isAutostretch:
667
+ self.histogram.setXRange(rmin, rmax)
668
+ self.hist_cdf.setXRange(rmin, rmax)
669
+
670
+
671
+ def cam_save(params: AcquisitionJob, **kwargs):
672
+ '''Save function executed by the save worker thread.
673
+
674
+ Saves the acquired frames using the config in AcquisitionJob
675
+ with a csv file containing the sensor temp for each frame.
676
+
677
+ Parameters
678
+ ----------
679
+ params : AcquisitionJob
680
+ class holding cross-thread info required for an Acquisition Job.
681
+ '''
682
+ try:
683
+ while True:
684
+ # save in case frame stack is not empty
685
+ if not params.save_queue.empty():
686
+ # for save time estimations
687
+ save_time = QDateTime.currentDateTime()
688
+
689
+ # get frame and temp to save from bottom of stack
690
+ frame, temp = params.save_queue.get()
691
+
692
+ if params.frames_saved < params.frames:
693
+ # opens csv file and appends sensor temp and close
694
+ params.addTemp(temp)
695
+ # adds dark frame for camera calibration if needed
696
+ params.addDarkFrame(frame)
697
+ # saves frame to storage
698
+ params.addFrame(frame)
699
+
700
+ # for save time estimations
701
+ with params.lock:
702
+ params.save_time = save_time.msecsTo(QDateTime.currentDateTime())
703
+
704
+ params.frames_saved += 1
705
+
706
+ QtCore.QThread.usleep(100)
707
+ # Flag that ends the loop
708
+ if (
709
+ params.save_queue.empty()
710
+ and params.stop_threads
711
+ and params.display_done
712
+ ):
713
+ print('Save thread break.')
714
+ break
715
+ except Exception:
716
+ traceback.print_exc()
717
+ finally:
718
+ params.s_event.set()
719
+ params.finalize()
720
+ print('Save thread finally finished.')
721
+
722
+
723
+ @lru_cache(maxsize=128)
724
+ def get_font_settings():
725
+ return cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2, 255
726
+
727
+
728
+ def drawStats(frame: uImage):
729
+ lines = [
730
+ f'min/max: {np.min(frame.image)}/{np.max(frame.image)}',
731
+ f'mean: {np.mean(frame.image):.3f}',
732
+ f'median: {np.median(frame.image):.3f}',
733
+ f'std: {np.std(frame.image):.4f}',
734
+ ]
735
+
736
+ # Font settings
737
+ font, font_scale, font_thickness, font_color = get_font_settings()
738
+
739
+ # Position to start adding text
740
+ x, y = 50, 50
741
+
742
+ # Add each line of text to the image
743
+ for line in lines:
744
+ cv2.putText(
745
+ frame._view, line, (x, y), font, font_scale, font_color, font_thickness
746
+ )
747
+ y += int(2 * font_scale * 20) # Adjust vertical spacing
748
+
749
+
750
+ def update_histogram_and_display(params: AcquisitionJob, camp: Camera_Panel):
751
+ '''
752
+ Update histogram and display based on camera options.
753
+
754
+ Parameters
755
+ ----------
756
+ params : AcquisitionJob
757
+ Class holding cross-thread info required for an Acquisition Job.
758
+ camp : Camera_Panel
759
+ The camera panel to display data.
760
+ '''
761
+ # Common logic for updating histogram and display
762
+ if camp.camera_options.isSingleView or (
763
+ camp.camera_options.isROIsView and len(params.rois) == 0
764
+ ):
765
+ _range = (
766
+ None
767
+ if camp.camera_options.isAutostretch
768
+ else tuple(map(math.ceil, camp.lr_0.getRegion()))
769
+ )
770
+
771
+ params.frame.equalizeLUT(_range, camp.camera_options.isNumpyLUT)
772
+
773
+ camp.updateRangeSignal.emit(params.frame._min, params.frame._max)
774
+
775
+ camp.updateStatsSignal.emit((params.frame, None))
776
+
777
+ # resizing the image
778
+ zoom = camp.camera_options.get_param_value(CamParams.RESIZE_DISPLAY)
779
+
780
+ if camp.camera_options.isDisplayStats:
781
+ drawStats(params.frame)
782
+
783
+ params.frame._view = cv2.resize(
784
+ params.frame._view,
785
+ (0, 0),
786
+ fx=zoom,
787
+ fy=zoom,
788
+ interpolation=cv2.INTER_NEAREST,
789
+ )
790
+
791
+ # display it
792
+ cv2.imshow(params.name, params.frame._view)
793
+ elif camp.camera_options.isROIsView:
794
+ if len(params.rois) == 1:
795
+ camp._plot_ref_2.setData(camp._hist)
796
+ camp._cdf_plot_ref_2.setData(camp._hist)
797
+ images, mins, maxs = [], [], []
798
+ for idx, roi in enumerate(params.rois):
799
+ uimage = uImage(
800
+ params.frame.image[roi[1] : roi[1] + roi[3], roi[0] : roi[0] + roi[2]]
801
+ )
802
+
803
+ if idx > 0 and params.flip_rois:
804
+ uimage.image = np.fliplr(uimage.image)
805
+
806
+ _range = None
807
+ # image stretching
808
+ if not camp.camera_options.isAutostretch and idx < 2:
809
+ linear_region = camp.lr_0 if idx == 0 else camp.lr_1
810
+ _range = tuple(map(math.ceil, linear_region.getRegion()))
811
+
812
+ uimage.equalizeLUT(_range, camp.camera_options.isNumpyLUT)
813
+
814
+ mins.append(uimage._min)
815
+ maxs.append(uimage._max)
816
+ images.append(uimage)
817
+
818
+ # resizing the image
819
+ zoom = camp.camera_options.get_param_value(CamParams.RESIZE_DISPLAY)
820
+ uimage._view = cv2.resize(
821
+ uimage._view, (0, 0), fx=zoom, fy=zoom, interpolation=cv2.INTER_NEAREST
822
+ )
823
+
824
+ # display it
825
+ cv2.imshow(params.name + f' ROI {idx + 1}', uimage._view)
826
+
827
+ camp.updateRangeSignal.emit(min(mins), max(maxs))
828
+ camp.updateStatsSignal.emit(tuple(images))
829
+ else:
830
+ _rang_left = None
831
+ _rang_right = None
832
+
833
+ # image stretching
834
+ if not camp.camera_options.isAutostretch:
835
+ _rang_left = tuple(map(math.ceil, camp.lr_0.getRegion()))
836
+ _rang_right = tuple(map(math.ceil, camp.lr_1.getRegion()))
837
+
838
+ left, right = params.frame.hsplitView()
839
+
840
+ left.equalizeLUT(_rang_left, camp.camera_options.isNumpyLUT)
841
+ right.equalizeLUT(_rang_right, camp.camera_options.isNumpyLUT)
842
+
843
+ camp.updateRangeSignal.emit(
844
+ min(left._min, right._min), max(left._max, right._max)
845
+ )
846
+ camp.updateStatsSignal.emit((left, right))
847
+
848
+ zoom = camp.camera_options.get_param_value(CamParams.RESIZE_DISPLAY)
849
+ if camp.camera_options.isOverlaidView:
850
+ _img = np.zeros(left._view.shape[:2] + (3,), dtype=np.uint8)
851
+ _img[..., 1] = left._view
852
+ _img[..., 0] = right._view
853
+ _img = cv2.resize(
854
+ _img, (0, 0), fx=zoom, fy=zoom, interpolation=cv2.INTER_NEAREST
855
+ )
856
+
857
+ cv2.imshow(params.name, _img)
858
+ else:
859
+ _img = cv2.resize(
860
+ np.concatenate([left._view, right._view], axis=1),
861
+ (0, 0),
862
+ fx=zoom,
863
+ fy=zoom,
864
+ interpolation=cv2.INTER_NEAREST,
865
+ )
866
+
867
+ cv2.imshow(params.name, _img)
868
+
869
+
870
+ def cam_display(params: AcquisitionJob, camp: Camera_Panel, **kwargs):
871
+ '''Display function executed by the display worker.
872
+
873
+ Processes the acquired frame, displays it, and sends it to the save
874
+ stack.
875
+
876
+ Parameters
877
+ ----------
878
+ params : AcquisitionJob
879
+ class holding cross-thread info required for an Acquisition Job.
880
+ camp : Camera_Panel
881
+ the camera panel to display data.
882
+ '''
883
+ try:
884
+ time = QDateTime.currentDateTime()
885
+
886
+ # Continuous image display
887
+ while True:
888
+ # proceed only if the buffer is not empty
889
+ if not params.display_queue.empty():
890
+ # for display time estimations
891
+ with params.lock:
892
+ params.display_time = time.msecsTo(QDateTime.currentDateTime())
893
+ time = QDateTime.currentDateTime()
894
+
895
+ # reshape image into proper shape
896
+ # (height, width, bytes per pixel)
897
+ params.frame = uImage.fromBuffer(
898
+ params.display_queue.get(),
899
+ params.cam_height,
900
+ params.cam_width,
901
+ params.bytes_per_pixel,
902
+ )
903
+
904
+ # add to saving stack
905
+ if params.save:
906
+ if not camp.mini:
907
+ params.save_queue.put(
908
+ (params.frame.image, params.temp_queue.get())
909
+ )
910
+ else:
911
+ params.save_queue.put(params.frame.image)
912
+
913
+ if camp.camera_options.isPreview:
914
+ if camp.camera_options.isLineProfiler:
915
+ camp.lineProfiler.imageUpdate.emit(params.frame._image)
916
+
917
+ update_histogram_and_display(params, camp)
918
+ cv2.waitKey(1)
919
+ else:
920
+ cv2.waitKey(1)
921
+ else:
922
+ cv2.waitKey(1)
923
+
924
+ QtCore.QThread.usleep(100)
925
+ # Flag that ends the loop
926
+ if (
927
+ params.stop_threads
928
+ and params.display_queue.empty()
929
+ and params.capture_done
930
+ ):
931
+ print('Display thread break.')
932
+ break
933
+ except Exception:
934
+ traceback.print_exc()
935
+ finally:
936
+ try:
937
+ if not camp.mini:
938
+ cv2.destroyAllWindows()
939
+ except Exception:
940
+ traceback.print_exc()
941
+ print('Display thread finally finished.')