pymodaq_plugins_utils 5.0.3__tar.gz → 5.0.4__tar.gz

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 (26) hide show
  1. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/PKG-INFO +1 -1
  2. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/hardware/camera_base_pylablib.py +145 -69
  3. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/.gitattributes +0 -0
  4. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/.github/workflows/Test.yml +0 -0
  5. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/.github/workflows/Testbase.yml +0 -0
  6. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/.github/workflows/compatibility.yml +0 -0
  7. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/.github/workflows/python-publish.yml +0 -0
  8. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/.github/workflows/updater.yml +0 -0
  9. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/.gitignore +0 -0
  10. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/LICENSE +0 -0
  11. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/README.rst +0 -0
  12. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/hatch_build.py +0 -0
  13. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/icon.ico +0 -0
  14. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/pyproject.toml +0 -0
  15. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/__init__.py +0 -0
  16. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/app/__init__.py +0 -0
  17. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/exporters/__init__.py +0 -0
  18. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/extensions/__init__.py +0 -0
  19. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/extensions/custom_extension_template.py +0 -0
  20. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/hardware/__init__.py +0 -0
  21. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/models/__init__.py +0 -0
  22. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/resources/__init__.py +0 -0
  23. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/resources/config_template.toml +0 -0
  24. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/scanners/__init__.py +0 -0
  25. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/utils.py +0 -0
  26. {pymodaq_plugins_utils-5.0.3 → pymodaq_plugins_utils-5.0.4}/tests/test_plugin_package_structure.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymodaq_plugins_utils
3
- Version: 5.0.3
3
+ Version: 5.0.4
4
4
  Summary: Set of utility methods and classes to interact with instruments
5
5
  Project-URL: Homepage, https://pymodaq.cnrs.fr
6
6
  Project-URL: Documentation , https://pymodaq.cnrs.fr
@@ -1,4 +1,9 @@
1
+ import dataclasses
2
+ from typing import Type
3
+
1
4
  import cv2
5
+
6
+ from pymodaq_data import DataToExport
2
7
  from pymodaq_utils.logger import set_logger, get_module_name
3
8
  from pymodaq_utils.utils import ThreadCommand
4
9
  from pymodaq_gui.parameter import Parameter
@@ -10,15 +15,20 @@ except ImportError:
10
15
  from pymodaq.utils.data import DataFromPlugins, Axis
11
16
  from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main
12
17
 
18
+ from pylablib.devices.interface.camera import trim_frames
19
+
13
20
  from qtpy import QtWidgets, QtCore
14
21
  import numpy as np
15
22
  from time import perf_counter
16
23
 
17
24
 
25
+ logger = set_logger(get_module_name(__file__))
26
+
27
+
18
28
  cam_params = [
19
29
  {'title': 'Camera name:', 'name': 'camera_name', 'type': 'str', 'value': '', 'readonly': True},
20
- {'title': 'Sensor type:', 'name': 'sensor', 'type': 'list', 'limits': ['Monochrome', 'Bayer']},
21
- {'title': 'Ouput Color:', 'name': 'output_color', 'type': 'list', 'limits': ['RGB', 'MonoChrome']},
30
+ {'title': 'Color Conversion:', 'name': 'color_conversion', 'type': 'list',
31
+ 'limits': ['None', 'RGB2GRAY', 'BAYER_BG2RGB', 'BAYER_BG2GRAY']},
22
32
  {'title': 'ROI', 'name': 'roi', 'type': 'group', 'children': [
23
33
  {'title': 'Update ROI from Viewer', 'name': 'update_roi', 'type': 'led', 'value': False},
24
34
  {'title': 'Apply ROI', 'name': 'apply_roi', 'type': 'led', 'value': False},
@@ -42,6 +52,60 @@ cam_params = [
42
52
  ]
43
53
 
44
54
 
55
+ @dataclasses.dataclass
56
+ class Grab:
57
+ do_acquisition: bool = True
58
+ snap: bool = False
59
+ since: str = 'now'
60
+ nframes: int = 1
61
+ n_average: int = 1
62
+
63
+
64
+ class CameraCallback(QtCore.QObject):
65
+ """Callback object """
66
+ data_sig = QtCore.Signal(np.ndarray)
67
+ error = QtCore.Signal()
68
+
69
+ def __init__(self, controller):
70
+ super().__init__()
71
+ # Set the wait function
72
+ self.controller = controller
73
+ self.do_acquisition = True
74
+
75
+ def set_do_grab(self, mode: Grab):
76
+ self.do_acquisition = mode.do_acquisition
77
+ if mode.do_acquisition:
78
+ self.wait_for_acquisition(mode)
79
+
80
+ def wait_for_acquisition(self, mode: Grab):
81
+ while self.do_acquisition:
82
+ try:
83
+ ind_average = 0
84
+ while ind_average < mode.n_average:
85
+ ind_frames = 0
86
+ while ind_frames < mode.nframes:
87
+ self.controller.wait_for_frame(since='now')
88
+ new_frames, rng = self.controller.read_multiple_images(missing_frame='skip', return_rng=True)
89
+ if ind_average == 0 and ind_frames == 0:
90
+ shape = list(new_frames.shape[1:])
91
+ shape = [mode.n_average, mode.nframes] + shape
92
+ frames = np.zeros(shape, dtype=new_frames.dtype)
93
+ nacq = rng[1] - rng[0]
94
+ frames[ind_average, ind_frames:nacq, ...] = new_frames
95
+ ind_frames += nacq
96
+ ind_average += 1
97
+ self.data_sig.emit(frames)
98
+ QtCore.QThread.msleep(10)
99
+ except Exception as e:
100
+ logger.exception(str(e))
101
+ self.error.emit()
102
+ break
103
+ QtWidgets.QApplication.processEvents()
104
+ if not self.do_acquisition or mode.snap:
105
+ break
106
+
107
+
108
+
45
109
  class CameraBasePyLabLib(DAQ_Viewer_base):
46
110
  """
47
111
  Base implementation for Camera using pylablib framework. Works for TSI and uc480 thorlabs camera and rpobaly others
@@ -52,13 +116,15 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
52
116
 
53
117
  params = comon_parameters + serial_params + cam_params
54
118
 
55
- callback_signal = QtCore.Signal(bool)
119
+ callback_signal = QtCore.Signal(Grab)
56
120
  live_mode_available = True
57
-
121
+ hardware_averaging = True
58
122
 
59
123
  def ini_attributes(self):
60
124
  self.controller = None
61
125
  self.callback_thread: QtCore.QThread = None
126
+ self.is_live: bool = False
127
+ self.Naverage: int = 1
62
128
 
63
129
  self.x_axis: Axis = None
64
130
  self.y_axis: Axis = None
@@ -135,16 +201,16 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
135
201
  if param.name() == "exposure_time":
136
202
  self.controller.set_exposure(param.value()/1000)
137
203
 
138
- if param.name() == "fps_on":
204
+ elif param.name() == "fps_on":
139
205
  self.settings.child('timing_opts', 'fps').setOpts(visible=param.value())
140
206
 
141
- if param.name() == "apply_roi":
207
+ elif param.name() == "apply_roi":
142
208
  if param.value(): # Switching on ROI
143
209
  self.apply_roi()
144
210
  else:
145
211
  self.clear_roi()
146
212
 
147
- if param.name() in ['x_binning', 'y_binning']:
213
+ elif param.name() in ['x_binning', 'y_binning']:
148
214
  # We handle ROI and binning separately for clarity
149
215
  (x0, w, y0, h, *_) = self.controller.get_roi() # Get current ROI
150
216
  xbin = self.settings['roi', 'x_binning']
@@ -152,14 +218,11 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
152
218
  new_roi = (x0, w, xbin, y0, h, ybin)
153
219
  self.update_rois(new_roi)
154
220
 
155
- if param.name() == "clear_roi":
221
+ elif param.name() == "clear_roi":
156
222
  if param.value(): # Switching on ROI
157
223
  self.clear_roi()
158
224
  param.setValue(False)
159
225
 
160
- if param.name() == 'sensor':
161
- self.get_set_color()
162
-
163
226
  def ini_detector_custom(self, controller=None):
164
227
  raise NotImplementedError
165
228
 
@@ -178,12 +241,13 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
178
241
  initialized: bool
179
242
  False if initialization failed otherwise True
180
243
  """
244
+
181
245
  self.ini_detector_custom(controller)
182
246
 
183
247
  self.get_device_info()
184
- self.get_set_color()
185
248
  self.get_set_main_parameters()
186
249
  self.setup_callback_thread()
250
+ self.controller.set_frame_format("array")
187
251
 
188
252
  info = "Initialized camera"
189
253
  initialized = True
@@ -199,13 +263,6 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
199
263
  elif hasattr(device_info, 'model'):
200
264
  self.settings.child('camera_name').setValue(device_info.model)
201
265
 
202
- def get_set_color(self):
203
- if 'monochrome' in self.settings['sensor'].lower():
204
- self.settings.child('output_color').setValue('MonoChrome')
205
- self.settings.child('output_color').setOpts(visible=False)
206
- else:
207
- self.settings.child('output_color').setOpts(visible=True)
208
-
209
266
  def get_set_main_parameters(self):
210
267
  # Set exposure time
211
268
  self.controller.set_exposure(self.settings['timing_opts', 'exposure_time']/1000)
@@ -227,18 +284,28 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
227
284
  self.settings.child('roi', 'roi_slices').setValue(str(slices))
228
285
  self.compute_axes()
229
286
 
287
+ @property
288
+ def callback(self) -> Type[CameraCallback]:
289
+ """ Return the class handling the wait for acquisition and signal emission
290
+
291
+ Should be reimplement as well as CameraCallback if needed
292
+ """
293
+ return CameraCallback
294
+
230
295
  def setup_callback_thread(self):
231
296
  # Way to define a wait function with arguments
232
297
  wait_func = lambda: self.controller.wait_for_frame(since=self.settings['buffer', 'mode'],
233
298
  nframes=1, timeout=20.0)
234
- callback = CameraCallback(wait_func)
299
+ callback = CameraCallback(self.controller)
235
300
  self.settings.child('buffer', 'mode').setReadonly(True)
236
301
 
237
302
 
238
303
  self.callback_thread = QtCore.QThread() # creation of a Qt5 thread
239
304
  callback.moveToThread(self.callback_thread) # callback object will live within this thread
305
+
240
306
  callback.data_sig.connect(
241
307
  self.emit_data) # when the wait for acquisition returns (with data taken), emit_data will be fired
308
+ callback.error.connect(self.handle_error)
242
309
 
243
310
  self.callback_signal.connect(callback.set_do_grab)
244
311
  self.callback_thread.callback = callback
@@ -246,6 +313,8 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
246
313
 
247
314
  self._prepare_view()
248
315
 
316
+ def handle_error(self):
317
+ self.stop()
249
318
 
250
319
  def _prepare_view(self):
251
320
  """Preparing a data viewer by emitting temporary data. Typically, needs to be called whenever the
@@ -280,14 +349,20 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
280
349
  """
281
350
  try:
282
351
  # Warning, acquisition_in_progress returns 1,0 and not a real bool
283
- if not kwargs.get('live', False):
284
- self.emit_data(self.controller.snap())
285
- else:
286
- if not self.controller.acquisition_in_progress():
287
- self.controller.clear_acquisition()
288
- self.controller.start_acquisition(nframes=self.settings['buffer', 'size'])
289
- #Then start the acquisition
290
- self.callback_signal.emit(True) # will trigger the wait for acquisition
352
+ self.is_live = kwargs.get('live', False)
353
+ self.Naverage = Naverage
354
+
355
+ self.n_frames = 1
356
+
357
+ if not self.controller.acquisition_in_progress():
358
+ self.controller.clear_acquisition()
359
+ self.controller.start_acquisition(nframes=self.n_frames)
360
+ #Then start the acquisition
361
+ self.callback_signal.emit(Grab(do_acquisition=True,
362
+ snap=not self.is_live,
363
+ n_average=Naverage,
364
+ nframes=self.n_frames,
365
+ since=self.settings['buffer', 'mode']))
291
366
 
292
367
  except Exception as e:
293
368
  self.emit_status(ThreadCommand('Update_Status', [str(e), "log"]))
@@ -309,21 +384,48 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
309
384
  if frame is None:
310
385
  frame = self.controller.read_newest_image()
311
386
  # Emit the frame.
312
- if frame is not None: # happens for last frame when stopping camera
313
- if self.settings['output_color'] == 'RGB':
314
- rgb_image = cv2.cvtColor(frame, cv2.COLOR_BAYER_BG2RGB)
315
- data_arrays = [np.atleast_1d(rgb_image[..., ind]) for ind in range(3)]
387
+ if frame is not None:
388
+ conversion_str = self.settings['color_conversion']
389
+ if conversion_str != "None":
390
+ for ind_average in range(frame.shape[0]):
391
+ for ind_frame in range(frame.shape[1]):
392
+ if ind_frame == 0 and ind_average == 0:
393
+ new_frame = cv2.cvtColor(frame[ind_average, ind_frame, ...],
394
+ getattr(cv2, f'COLOR_{conversion_str}'))
395
+ shape = [frame.shape[0], frame.shape[1]] + list(new_frame.shape)
396
+ out_frames = np.zeros(shape, dtype=new_frame.dtype)
397
+ out_frames[ind_average, ind_frame, ...] = new_frame
398
+ else:
399
+ cv2.cvtColor(frame[ind_average, ind_frame, ...],
400
+ getattr(cv2, f'COLOR_{conversion_str}'),
401
+ out_frames[ind_average, ind_frame, ...])
402
+ else:
403
+ out_frames = frame
404
+ if self.Naverage > 1:
405
+ out_frames = np.sum(out_frames, axis=0) / self.Naverage
406
+ else:
407
+ out_frames = out_frames[0, ...]
408
+
409
+ if self.n_frames > 1:
410
+ pass
411
+ #todo handle chunks of frames in ND data
412
+ else:
413
+ out_frames = out_frames[0, ...]
414
+
415
+ if out_frames.shape[-1] == 3:
416
+ data_arrays = [np.atleast_1d(out_frames[..., ind]) for ind in range(3)]
417
+ labels = ['Red', 'Green', 'Blue']
316
418
  else:
317
- if 'monochrome' in self.settings['sensor'].lower():
318
- data_arrays = [np.atleast_1d(frame)]
319
- else:
320
- data_arrays = [np.atleast_1d(cv2.cvtColor(frame, cv2.COLOR_BAYER_BG2GRAY))]
321
-
322
- self.data_grabed_signal.emit([DataFromPlugins(name='Thorlabs Camera',
323
- data=data_arrays,
324
- dim=self.data_shape,
325
- labels=[f'ThorCam_{self.data_shape}'],
326
- axes=[self.x_axis, self.y_axis])])
419
+ labels = ['Intensity']
420
+ data_arrays = [out_frames]
421
+
422
+ self.dte_signal.emit(
423
+ DataToExport('Camera',
424
+ data=[DataFromPlugins(name='Camera',
425
+ data=data_arrays,
426
+ dim=self.data_shape,
427
+ labels=labels,
428
+ axes=[self.y_axis, self.x_axis])]))
327
429
  if self.settings.child('timing_opts', 'fps_on').value():
328
430
  self.update_fps()
329
431
 
@@ -367,37 +469,11 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
367
469
 
368
470
  def stop(self):
369
471
  """Stop the acquisition."""
370
- self.callback_signal.emit(False)
472
+ self.callback_signal.emit(Grab(do_acquisition=False))
371
473
  QtWidgets.QApplication.processEvents()
372
-
373
474
  self.controller.clear_acquisition()
374
475
  return ''
375
476
 
376
477
 
377
- class CameraCallback(QtCore.QObject):
378
- """Callback object """
379
- data_sig = QtCore.Signal()
380
-
381
- def __init__(self, wait_fn):
382
- super().__init__()
383
- # Set the wait function
384
- self.wait_fn = wait_fn
385
- self.do_grab = True
386
-
387
- def set_do_grab(self, do_grab=True):
388
- self.do_grab = do_grab
389
- if do_grab:
390
- self.wait_for_acquisition()
391
-
392
- def wait_for_acquisition(self):
393
- while self.do_grab:
394
- try:
395
- new_data = self.wait_fn()
396
- if new_data is not False: # will be returned if the main thread called CancelWait
397
- self.data_sig.emit()
398
- except Exception as e:
399
- pass
400
- QtWidgets.QApplication.processEvents()
401
-
402
478
 
403
479