pymodaq_plugins_utils 5.0.2__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.2 → pymodaq_plugins_utils-5.0.4}/PKG-INFO +1 -1
  2. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/hardware/camera_base_pylablib.py +145 -64
  3. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/.gitattributes +0 -0
  4. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/.github/workflows/Test.yml +0 -0
  5. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/.github/workflows/Testbase.yml +0 -0
  6. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/.github/workflows/compatibility.yml +0 -0
  7. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/.github/workflows/python-publish.yml +0 -0
  8. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/.github/workflows/updater.yml +0 -0
  9. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/.gitignore +0 -0
  10. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/LICENSE +0 -0
  11. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/README.rst +0 -0
  12. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/hatch_build.py +0 -0
  13. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/icon.ico +0 -0
  14. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/pyproject.toml +5 -5
  15. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/__init__.py +0 -0
  16. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/app/__init__.py +0 -0
  17. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/exporters/__init__.py +0 -0
  18. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/extensions/__init__.py +0 -0
  19. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/extensions/custom_extension_template.py +0 -0
  20. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/hardware/__init__.py +0 -0
  21. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/models/__init__.py +0 -0
  22. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/resources/__init__.py +0 -0
  23. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/resources/config_template.toml +0 -0
  24. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/scanners/__init__.py +0 -0
  25. {pymodaq_plugins_utils-5.0.2 → pymodaq_plugins_utils-5.0.4}/src/pymodaq_plugins_utils/utils.py +0 -0
  26. {pymodaq_plugins_utils-5.0.2 → 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.2
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,7 +218,7 @@ 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)
@@ -175,12 +241,13 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
175
241
  initialized: bool
176
242
  False if initialization failed otherwise True
177
243
  """
244
+
178
245
  self.ini_detector_custom(controller)
179
246
 
180
247
  self.get_device_info()
181
- self.get_set_color()
182
248
  self.get_set_main_parameters()
183
249
  self.setup_callback_thread()
250
+ self.controller.set_frame_format("array")
184
251
 
185
252
  info = "Initialized camera"
186
253
  initialized = True
@@ -196,11 +263,6 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
196
263
  elif hasattr(device_info, 'model'):
197
264
  self.settings.child('camera_name').setValue(device_info.model)
198
265
 
199
- def get_set_color(self):
200
- if 'monochrome' in self.settings['sensor'].lower():
201
- self.settings.child('output_color').setValue('MonoChrome')
202
- self.settings.child('output_color').setOpts(visible=False)
203
-
204
266
  def get_set_main_parameters(self):
205
267
  # Set exposure time
206
268
  self.controller.set_exposure(self.settings['timing_opts', 'exposure_time']/1000)
@@ -222,18 +284,28 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
222
284
  self.settings.child('roi', 'roi_slices').setValue(str(slices))
223
285
  self.compute_axes()
224
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
+
225
295
  def setup_callback_thread(self):
226
296
  # Way to define a wait function with arguments
227
297
  wait_func = lambda: self.controller.wait_for_frame(since=self.settings['buffer', 'mode'],
228
298
  nframes=1, timeout=20.0)
229
- callback = CameraCallback(wait_func)
299
+ callback = CameraCallback(self.controller)
230
300
  self.settings.child('buffer', 'mode').setReadonly(True)
231
301
 
232
302
 
233
303
  self.callback_thread = QtCore.QThread() # creation of a Qt5 thread
234
304
  callback.moveToThread(self.callback_thread) # callback object will live within this thread
305
+
235
306
  callback.data_sig.connect(
236
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)
237
309
 
238
310
  self.callback_signal.connect(callback.set_do_grab)
239
311
  self.callback_thread.callback = callback
@@ -241,6 +313,8 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
241
313
 
242
314
  self._prepare_view()
243
315
 
316
+ def handle_error(self):
317
+ self.stop()
244
318
 
245
319
  def _prepare_view(self):
246
320
  """Preparing a data viewer by emitting temporary data. Typically, needs to be called whenever the
@@ -275,14 +349,20 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
275
349
  """
276
350
  try:
277
351
  # Warning, acquisition_in_progress returns 1,0 and not a real bool
278
- if not kwargs.get('live', False):
279
- self.emit_data(self.controller.snap())
280
- else:
281
- if not self.controller.acquisition_in_progress():
282
- self.controller.clear_acquisition()
283
- self.controller.start_acquisition(nframes=self.settings['buffer', 'size'])
284
- #Then start the acquisition
285
- 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']))
286
366
 
287
367
  except Exception as e:
288
368
  self.emit_status(ThreadCommand('Update_Status', [str(e), "log"]))
@@ -304,21 +384,48 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
304
384
  if frame is None:
305
385
  frame = self.controller.read_newest_image()
306
386
  # Emit the frame.
307
- if frame is not None: # happens for last frame when stopping camera
308
- if self.settings['output_color'] == 'RGB':
309
- rgb_image = cv2.cvtColor(frame, cv2.COLOR_BAYER_BG2RGB)
310
- 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']
311
418
  else:
312
- if 'monochrome' in self.settings['sensor'].lower():
313
- data_arrays = [np.atleast_1d(frame)]
314
- else:
315
- data_arrays = [np.atleast_1d(cv2.cvtColor(frame, cv2.COLOR_BAYER_BG2GRAY))]
316
-
317
- self.data_grabed_signal.emit([DataFromPlugins(name='Thorlabs Camera',
318
- data=data_arrays,
319
- dim=self.data_shape,
320
- labels=[f'ThorCam_{self.data_shape}'],
321
- 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])]))
322
429
  if self.settings.child('timing_opts', 'fps_on').value():
323
430
  self.update_fps()
324
431
 
@@ -362,37 +469,11 @@ class CameraBasePyLabLib(DAQ_Viewer_base):
362
469
 
363
470
  def stop(self):
364
471
  """Stop the acquisition."""
365
- self.callback_signal.emit(False)
472
+ self.callback_signal.emit(Grab(do_acquisition=False))
366
473
  QtWidgets.QApplication.processEvents()
367
-
368
474
  self.controller.clear_acquisition()
369
475
  return ''
370
476
 
371
477
 
372
- class CameraCallback(QtCore.QObject):
373
- """Callback object """
374
- data_sig = QtCore.Signal()
375
-
376
- def __init__(self, wait_fn):
377
- super().__init__()
378
- # Set the wait function
379
- self.wait_fn = wait_fn
380
- self.do_grab = True
381
-
382
- def set_do_grab(self, do_grab=True):
383
- self.do_grab = do_grab
384
- if do_grab:
385
- self.wait_for_acquisition()
386
-
387
- def wait_for_acquisition(self):
388
- while self.do_grab:
389
- try:
390
- new_data = self.wait_fn()
391
- if new_data is not False: # will be returned if the main thread called CancelWait
392
- self.data_sig.emit()
393
- except Exception as e:
394
- pass
395
- QtWidgets.QApplication.processEvents()
396
-
397
478
 
398
479
 
@@ -8,7 +8,10 @@ scanners = false # true if plugin contains custom scan layout (daq_scan extensi
8
8
  [urls]
9
9
  package-url = 'https://github.com/PyMoDAQ/pymodaq_plugins_utils'
10
10
 
11
-
11
+ [project.optional-dependencies]
12
+ serial = [
13
+ "pyvisa",
14
+ ]
12
15
 
13
16
  [project]
14
17
  name = "pymodaq_plugins_utils"
@@ -50,10 +53,7 @@ classifiers = [
50
53
  "Topic :: Software Development :: User Interfaces",
51
54
  ]
52
55
 
53
- [project.optional-dependencies]
54
- serial = [
55
- "pyvisa",
56
- ]
56
+
57
57
 
58
58
 
59
59
  [build-system]