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,497 @@
1
+ import json
2
+ import os
3
+ import threading
4
+ import time
5
+ from queue import Queue
6
+ from typing import Callable
7
+
8
+ import numpy as np
9
+ import ome_types.model as om
10
+ import tifffile as tf
11
+ import zarr
12
+
13
+ from microEye.hardware.cams.camera_calibration import dark_calibration
14
+
15
+
16
+ class AcquisitionJob:
17
+ '''A class holding cross-thread info required for an Acquisition Job.
18
+
19
+ Attributes
20
+ ----------
21
+ temp_queue : Queue[float]
22
+ The queue for temporary data.
23
+ display_queue : Queue[np.ndarray]
24
+ The queue for display data.
25
+ save_queue : Queue[tuple[np.ndarray, float]]
26
+ The queue for data to be saved along with associated timestamps.
27
+ path : str
28
+ The base path where files will be saved.
29
+ height : int
30
+ The height of the image frames.
31
+ width : int
32
+ The width of the image frames.
33
+ biggTiff : bool, optional
34
+ Flag indicating whether to use big TIFF format, by default True.
35
+ bytes_per_pixel : int, optional
36
+ The number of bytes per pixel, by default 1.
37
+ exposure : float, optional
38
+ The exposure time in milliseconds, by default 50.0.
39
+ frames : int, optional
40
+ The number of frames, by default 100.
41
+ full_tif_meta : bool, optional
42
+ Flag indicating whether to include full TIFF metadata, by default True.
43
+ is_dark_cal : bool, optional
44
+ Flag indicating whether dark calibration is enabled, by default False.
45
+ meta_file : str, optional
46
+ Additional metadata file path, by default ''.
47
+ meta_func : Callable, optional
48
+ A function to generate metadata, by default a lambda function.
49
+ name : str, optional
50
+ The name of the acquisition job, by default 'Camera'.
51
+ prefix : str, optional
52
+ A prefix for filenames, by default ''.
53
+ save : bool, optional
54
+ Flag indicating whether to save data, by default False.
55
+ Zarr : bool, optional
56
+ Flag indicating whether to use Zarr format, by default False.
57
+ rois : list[list[int]], optional
58
+ List of regions of interest, by default empty list.
59
+ seperate_rois : bool, optional
60
+ Flag indicating whether to save ROIs into seperate files,
61
+ by default False.
62
+ (For Zarr format different ROIs are treated as channels)
63
+ flip_rois : bool, optional
64
+ Flag indicating whether to flip n-th ROIs horizontally for n > 1,
65
+ by default True.
66
+
67
+
68
+ Methods
69
+ -------
70
+ getExt()
71
+ Return the file extension based on the Zarr attribute.
72
+ getFilename()
73
+ Generate the filename based on the current state.
74
+ getTempFilename()
75
+ Generate the temperatures filename based on the current state.
76
+ addDarkFrame(frame)
77
+ Add a dark frame for calibration.
78
+ addFrame(frame)
79
+ Add a frame to the acquisition job.
80
+ addTemp(temp)
81
+ Add temperature data to the acquisition job.
82
+ getMetaFilename()
83
+ Return the metadata file path.
84
+ writeMetaFile()
85
+ Write metadata to a file.
86
+ getTiffWriter()
87
+ Return a TiffWriter instance for saving TIFF files.
88
+ getZarrArray()
89
+ Return the Zarr array for saving Zarr files.
90
+ saveMetadata()
91
+ Save metadata to the current file.
92
+ finalize()
93
+ Finalize and clean up resources.
94
+ setDone(index, value)
95
+ Set the completion status for a specific stage.
96
+ '''
97
+
98
+ def __init__(self, temp_queue: Queue[float], display_queue: Queue[np.ndarray],
99
+ save_queue: Queue[tuple[np.ndarray, float]], path: str,
100
+ height: int, width: int, **kwargs) -> None:
101
+ '''Initialize the AcquisitionJob instance.
102
+
103
+ Parameters
104
+ ----------
105
+ temp_queue : Queue[float]
106
+ The queue for temporary data.
107
+ display_queue : Queue[np.ndarray]
108
+ The queue for display data.
109
+ save_queue : Queue[tuple[np.ndarray, float]]
110
+ The queue for data to be saved along with associated timestamps.
111
+ path : str
112
+ The base path where files will be saved.
113
+ height : int
114
+ The height of the image frames.
115
+ width : int
116
+ The width of the image frames.
117
+ **kwargs
118
+ Optional keyword arguments for customization.
119
+
120
+ Optional Parameters
121
+ -------------------
122
+ biggTiff : bool, optional
123
+ Flag indicating whether to use big TIFF format, by default True.
124
+ bytes_per_pixel : int, optional
125
+ The number of bytes per pixel, by default 1.
126
+ exposure : float, optional
127
+ The exposure time in milliseconds, by default 50.0.
128
+ frames : int, optional
129
+ The number of frames, by default 100.
130
+ full_tif_meta : bool, optional
131
+ Flag indicating whether to include full TIFF metadata, by default True.
132
+ is_dark_cal : bool, optional
133
+ Flag indicating whether dark calibration is enabled, by default False.
134
+ meta_file : str, optional
135
+ Additional metadata file path, by default ''.
136
+ meta_func : Callable, optional
137
+ A function to generate metadata, by default a lambda function.
138
+ name : str, optional
139
+ The name of the acquisition job, by default 'Camera'.
140
+ prefix : str, optional
141
+ A prefix for filenames, by default ''.
142
+ save : bool, optional
143
+ Flag indicating whether to save data, by default False.
144
+ Zarr : bool, optional
145
+ Flag indicating whether to use Zarr format, by default False.
146
+ rois : list[list[int]], optional
147
+ List of regions of interest, by default empty list.
148
+ seperate_rois : bool, optional
149
+ Flag indicating whether to save ROIs into seperate files,
150
+ by default False.
151
+ (For Zarr format different ROIs are treated as channels)
152
+ flip_rois : bool, optional
153
+ Flag indicating whether to flip n-th ROIs horizontally for n > 1,
154
+ by default True.
155
+ '''
156
+ self.display_queue = display_queue
157
+ self.height = height
158
+ self.cam_height = height
159
+ self.index = 0
160
+ self.lock = threading.Lock()
161
+ self.major = 0
162
+ self.path = path
163
+ self.save_queue = save_queue
164
+ self.temp_queue = temp_queue
165
+ self.timestamp = time.strftime('_%Y_%m_%d_%H%M%S')
166
+ self.width = width
167
+ self.cam_width = width
168
+ self.channels = 1
169
+ self.zarr_array = None
170
+
171
+ self.biggTiff: bool = kwargs.get('biggTiff', True)
172
+ self.bytes_per_pixel: int = kwargs.get('bytes_per_pixel', 1)
173
+ self.exposure: float = kwargs.get('exposure', 50.0)
174
+ self.frames: int = kwargs.get('frames', 100)
175
+ self.full_tif_meta: bool = kwargs.get('full_tif_meta', True)
176
+ self.is_dark_cal: bool = kwargs.get('is_dark_cal', False)
177
+ self.meta_file: str = kwargs.get('meta_file', '')
178
+ self.meta_func: Callable = kwargs.get('meta_func', lambda *args: '')
179
+ self.name: str = kwargs.get('name', 'Camera')
180
+ self.prefix: str = kwargs.get('prefix', '')
181
+ self.save: bool = kwargs.get('save', False)
182
+ '''cross-thread attribute (use lock)'''
183
+ self.zarr: bool = kwargs.get('Zarr', False)
184
+ self.rois: list[list[int]] = kwargs.get('rois', [])
185
+ self.seperate_rois: bool = kwargs.get('seperate_rois', False)
186
+ self.flip_rois: bool = kwargs.get('flip_rois', True)
187
+
188
+ if self.rois:
189
+ same_width = all(
190
+ [self.rois[0][2] == roi[2] for roi in self.rois])
191
+ same_height = all(
192
+ [self.rois[0][3] == roi[3] for roi in self.rois])
193
+ if not same_width or not same_height:
194
+ self.rois = None
195
+ else:
196
+ self.width = self.rois[0][2]
197
+ self.height = self.rois[0][3]
198
+ self.channels = len(self.rois)
199
+
200
+ self.capture_done = False
201
+ '''cross-thread attribute (use lock)'''
202
+ self.capture_time = 0.0
203
+ '''cross-thread attribute (use lock)'''
204
+ self.display_done = False
205
+ '''cross-thread attribute (use lock)'''
206
+ self.display_time = 0.0
207
+ '''cross-thread attribute (use lock)'''
208
+ self.frame = None
209
+ '''cross-thread attribute (use lock)'''
210
+ self.frames_captured = 0
211
+ '''cross-thread attribute (use lock)'''
212
+ self.frames_saved = 0
213
+ '''cross-thread attribute (use lock)'''
214
+ self.save_time = 0.0
215
+ '''cross-thread attribute (use lock)'''
216
+ self.stop_threads = False
217
+ '''cross-thread attribute (use lock)'''
218
+
219
+ self.c_event = threading.Event()
220
+ self.s_event = kwargs.get('s_event', threading.Event())
221
+
222
+ self.tiffWriter: list[tf.TiffWriter] = []
223
+ self.dark_cal = None
224
+
225
+
226
+ if self.save:
227
+ if not os.path.exists(self.path):
228
+ os.makedirs(self.path)
229
+
230
+ while os.path.exists(self.getFilename()):
231
+ self.major += 1
232
+
233
+ self.writeMetaFile()
234
+
235
+ def getExt(self):
236
+ '''Return the file extension based on the Zarr attribute.
237
+
238
+ Returns
239
+ -------
240
+ str
241
+ File extension ('ome.tif' or 'zarr').
242
+ '''
243
+ return 'ome.tif' if not self.zarr else 'zarr'
244
+
245
+ def getFilename(self, roi_index: int=None) -> str:
246
+ '''Generate the filename based on the current state.
247
+
248
+ Parameters
249
+ ----------
250
+ roi_index : int, optional
251
+ the roi index for file name suffix, if None then not added. Default is None.
252
+
253
+ Returns
254
+ -------
255
+ str
256
+ Generated filename.
257
+ '''
258
+ roi_suffix = '' if roi_index is None else f'_ROI_{roi_index:02d}'
259
+ return self.path + \
260
+ f'{self.major:02d}_{self.prefix}_image_{self.index:05d}{roi_suffix}.{self.getExt()}'
261
+
262
+ def getTempFilename(self) -> str:
263
+ '''Generate the temperature log filename based on the current state.
264
+
265
+ Returns
266
+ -------
267
+ str
268
+ Generated temperature log filename.
269
+ '''
270
+ return self.path + \
271
+ f'{self.major:02d}_{self.prefix}_temp_log.csv'
272
+
273
+ def addDarkFrame(self, frame: np.ndarray):
274
+ '''Add a dark frame for calibration.
275
+
276
+ Parameters
277
+ ----------
278
+ frame : np.ndarray
279
+ Dark frame data.
280
+ '''
281
+ if self.is_dark_cal:
282
+ if self.dark_cal is None:
283
+ self.dark_cal = dark_calibration(
284
+ frame.shape, self.exposure)
285
+ self.dark_cal.addFrame(frame)
286
+
287
+ def addFrame(self, frame: np.ndarray):
288
+ '''Add a frame to the acquisition job.
289
+
290
+ Parameters
291
+ ----------
292
+ frame : np.ndarray
293
+ Image frame data.
294
+ '''
295
+ if self.zarr:
296
+ if self.rois:
297
+ for idx, roi in enumerate(self.rois):
298
+ roi_data = frame[
299
+ roi[1]: roi[1] + roi[3],
300
+ roi[0]: roi[0] + roi[2]]
301
+ if idx > 0 and self.flip_rois:
302
+ roi_data = np.fliplr(roi_data)
303
+
304
+ self.getZarrArray()[self.frames_saved, idx, 0] = roi_data
305
+ else:
306
+ self.getZarrArray()[self.frames_saved, 0, 0] = frame
307
+ else:
308
+ if not self.tiffWriter:
309
+ if self.rois:
310
+ if self.seperate_rois:
311
+ for idx in range(len(self.rois)):
312
+ self.tiffWriter.append(
313
+ self.getTiffWriter(idx))
314
+ else:
315
+ self.tiffWriter.append(self.getTiffWriter())
316
+ else:
317
+ self.tiffWriter.append(self.getTiffWriter())
318
+ # append frame to tiff
319
+ if self.rois:
320
+ if self.seperate_rois:
321
+ for idx, roi in enumerate(self.rois):
322
+ roi_data = frame[
323
+ roi[1]: roi[1] + roi[3],
324
+ roi[0]: roi[0] + roi[2]]
325
+ if idx > 0 and self.flip_rois:
326
+ roi_data = np.fliplr(roi_data)
327
+
328
+ self.tiffWriter[idx] = self.writeTiffFrame(
329
+ self.tiffWriter[idx],
330
+ roi_data, idx)
331
+ else:
332
+ data = np.zeros(
333
+ (len(self.rois),
334
+ self.rois[0][3], self.rois[0][2]), dtype=frame.dtype)
335
+ for idx, roi in enumerate(self.rois):
336
+ data[idx] = frame[
337
+ roi[1]: roi[1] + roi[3],
338
+ roi[0]: roi[0] + roi[2]]
339
+ if idx > 0 and self.flip_rois:
340
+ data[idx] = np.fliplr(data[idx])
341
+
342
+ self.tiffWriter[0] = self.writeTiffFrame(
343
+ self.tiffWriter[0], data)
344
+ else:
345
+ self.tiffWriter[0] = self.writeTiffFrame(
346
+ self.tiffWriter[0], frame)
347
+
348
+ def writeTiffFrame(
349
+ self,
350
+ writer: tf.TiffWriter, frame: np.ndarray, roi_index=None):
351
+ '''writes an image into a Tiff file.
352
+
353
+ Parameters
354
+ ----------
355
+ writer : tf.TiffWriter
356
+ the TiffWriter class of the Tiff file.
357
+ frame : np.ndarray
358
+ Image frame data.
359
+ roi_index : int, optional
360
+ the roi index for file name suffix, if None then not added. Default is None.
361
+ '''
362
+
363
+ try:
364
+ writer.write(
365
+ data=frame[np.newaxis, np.newaxis, np.newaxis, ...
366
+ ] if frame.ndim == 2 else frame[
367
+ np.newaxis, :, np.newaxis, ...],
368
+ photometric='minisblack')
369
+ except ValueError as ve:
370
+ if str(ve) == \
371
+ 'data too large for standard TIFF file':
372
+ writer.close()
373
+ self.saveMetadata(roi_index)
374
+ self.frames_saved = 0
375
+ self.index += 1
376
+ writer = self.getTiffWriter(roi_index)
377
+ writer.write(
378
+ data=frame[np.newaxis, np.newaxis, np.newaxis, ...
379
+ ] if frame.ndim == 2 else frame[
380
+ np.newaxis, :, np.newaxis, ...],
381
+ photometric='minisblack')
382
+ else:
383
+ raise ve
384
+
385
+ return writer
386
+
387
+ def addTemp(self, temp: float):
388
+ '''Add temperature data to the log file.
389
+
390
+ Parameters
391
+ ----------
392
+ temp : float
393
+ Temperature data.
394
+ '''
395
+ # open csv file and append sensor temp and close
396
+ with open(self.getTempFilename(), 'ab') as f:
397
+ np.savetxt(f, [temp], delimiter=';')
398
+
399
+ def getMetaFilename(self) -> str:
400
+ '''Return the metadata file path.
401
+
402
+ Returns
403
+ -------
404
+ str
405
+ Metadata file path.
406
+ '''
407
+ return self.path + \
408
+ f'{self.major:02d}_{self.prefix}' + self.name.replace(
409
+ ' ', '_') + self.timestamp + '.json'
410
+
411
+ def writeMetaFile(self):
412
+ '''Write metadata to a file.'''
413
+ with open(self.getMetaFilename(), 'w+') as metaFile:
414
+ json.dump(self.meta_file, metaFile, indent=2)
415
+
416
+ def getTiffWriter(self, roi_index: int =None) -> tf.TiffWriter:
417
+ '''Return a TiffWriter instance for saving TIFF files.
418
+
419
+ Parameters
420
+ ----------
421
+ roi_index : int, optional
422
+ the roi index for file name suffix, if None then not added. Default is None.
423
+
424
+ Returns
425
+ -------
426
+ tf.TiffWriter
427
+ TiffWriter instance.
428
+ '''
429
+ return tf.TiffWriter(
430
+ self.getFilename(roi_index), append=False,
431
+ bigtiff=self.biggTiff, ome=False)
432
+
433
+ def getZarrArray(self) -> zarr.Array:
434
+ '''Return the Zarr array for saving Zarr files.
435
+
436
+ Returns
437
+ -------
438
+ zarr.Array
439
+ Zarr array.
440
+ '''
441
+ if self.zarr_array is None:
442
+ self.zarr_array = zarr.open_array(
443
+ self.getFilename(),
444
+ shape=(self.frames, self.channels, 1, self.height, self.width),
445
+ compressor=None,
446
+ chunks=(1, 1, 1, self.height, self.width),
447
+ dtype=np.uint16)
448
+ return self.zarr_array
449
+
450
+ def saveMetadata(self, roi_index: int =None):
451
+ '''
452
+ Save metadata to the current file.
453
+
454
+ Parameters
455
+ ----------
456
+ roi_index : int, optional
457
+ the roi index for file name suffix, if None then not added. Default is None.
458
+ '''
459
+ ome: om.OME = self.meta_func(
460
+ self.frames_saved, self.width, self.height,
461
+ 1 if self.seperate_rois else self.channels)
462
+
463
+ tf.tiffcomment(self.getFilename(roi_index), ome.to_xml())
464
+
465
+ def finalize(self):
466
+ '''Finalize and clean up resources.'''
467
+ if self.dark_cal is not None:
468
+ if self.dark_cal._counter > 1:
469
+ self.dark_cal.saveResults(
470
+ self.path,
471
+ f'{self.major:02d}_{self.prefix}')
472
+
473
+ if self.tiffWriter:
474
+ if self.rois and self.seperate_rois:
475
+ for idx, writer in enumerate(self.tiffWriter):
476
+ writer.close()
477
+ if self.save and not self.zarr:
478
+ self.saveMetadata(idx)
479
+ else:
480
+ self.tiffWriter[0].close()
481
+ if self.save and not self.zarr:
482
+ self.saveMetadata()
483
+
484
+ def setDone(self, index: int, value: bool):
485
+ '''Set the completion status for a specific stage.
486
+
487
+ Parameters
488
+ ----------
489
+ index : int
490
+ The index representing the stage (0 for capture, 1 for display, etc.).
491
+ value : bool
492
+ The completion status.
493
+ '''
494
+ if index == 0:
495
+ self.capture_done = value
496
+ elif index == 1:
497
+ self.display_done = value
@@ -0,0 +1,99 @@
1
+ import numpy as np
2
+ import pyqtgraph as pg
3
+ import qdarkstyle
4
+ from pyqtgraph.multiprocess import proxy
5
+
6
+ from microEye.analysis.tools.kymograms import get_kymogram_row
7
+ from microEye.qt import QApplication, QtWidgets, Signal, Slot
8
+
9
+
10
+ class LineProfiler(QtWidgets.QWidget):
11
+ imageUpdate = Signal(np.ndarray)
12
+
13
+ def __init__(self) -> None:
14
+ super().__init__()
15
+
16
+ self.setWindowTitle('Line Profiler')
17
+
18
+ self.lineProfiles = []
19
+ self.image = np.random.normal(50, 2, (512, 512))
20
+
21
+ layout = QtWidgets.QFormLayout()
22
+ self.setLayout(layout)
23
+
24
+ params = QtWidgets.QHBoxLayout()
25
+
26
+ self.average = QtWidgets.QSpinBox()
27
+ self.average.setMinimum(1)
28
+ self.average.setMaximum(128)
29
+ self.average.setValue(1)
30
+
31
+ # params.addWidget(
32
+ # QLabel('Average [frames]'))
33
+ # params.addWidget(
34
+ # self.average)
35
+ self.data = np.random.normal(size=(512, 512))
36
+ self.remote_view = pg.GraphicsView()
37
+ pg.setConfigOptions(antialias=True, imageAxisOrder='row-major')
38
+ self.remote_plt = pg.ViewBox(invertY=True)
39
+ self.remote_view.setCentralItem(self.remote_plt)
40
+ self.remote_plt.setAspectLocked()
41
+ self.remote_img = pg.ImageItem(axisOrder='row-major')
42
+ self.remote_img.setImage(self.data)
43
+
44
+ self.roi = pg.ROI([10, 10], [0, 128], angle=0, pen='r')
45
+ self.roi.addTranslateHandle([0, 0], [0, 1])
46
+ self.roi.addScaleRotateHandle([0, 1], [0, 0])
47
+ self.roi.setZValue(10)
48
+
49
+ self.X, self.Y = self.getRoiCoords()
50
+ self.line_width = 16
51
+
52
+ self.remote_plt.addItem(self.remote_img)
53
+ self.remote_plt.addItem(self.roi)
54
+
55
+ self.line = pg.GraphicsView()
56
+ self.line.setMaximumHeight(250)
57
+ self.lineP = pg.PlotItem()
58
+ self.line.setCentralItem(self.lineP)
59
+ self.lineP_ref = self.lineP.plot()
60
+
61
+ def updateRoiCoords(cls=None):
62
+ self.X, self.Y = self.getRoiCoords()
63
+
64
+ self.roi.sigRegionChangeFinished.connect(updateRoiCoords)
65
+
66
+ self.setMinimumHeight(700)
67
+ layout.addRow(params)
68
+ layout.addRow(self.remote_view)
69
+ layout.addRow(self.line)
70
+
71
+ def setData(data: np.ndarray):
72
+ # if self.data.shape == data.shape:
73
+ # self.data[:, :] = data
74
+ # else:
75
+ # self.data = data.copy()
76
+ self.remote_img.setImage(data)
77
+
78
+ line_roi = get_kymogram_row(
79
+ data, self.X, self.Y, self.line_width)
80
+ self.lineP_ref.setData(line_roi)
81
+
82
+ self.imageUpdate.connect(setData)
83
+
84
+ def getRoiCoords(self):
85
+ dy = self.roi.size()[1] * np.cos(np.pi * self.roi.angle() / 180)
86
+ dx = self.roi.size()[1] * np.sin(np.pi * self.roi.angle() / 180)
87
+ x = self.roi.x()
88
+ y = self.roi.y()
89
+ return np.array([x, x - dx]), np.array([y, y + dy])
90
+
91
+
92
+ if __name__ == '__main__':
93
+ app = QApplication([])
94
+ app.setStyleSheet(qdarkstyle.load_stylesheet(qt_api='pyqt5'))
95
+
96
+ win = LineProfiler()
97
+ win.show()
98
+
99
+ app.exec()