pymodaq 4.1.5__py3-none-any.whl → 4.2.0__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.

Potentially problematic release.


This version of pymodaq might be problematic. Click here for more details.

Files changed (79) hide show
  1. pymodaq/__init__.py +41 -4
  2. pymodaq/control_modules/daq_move.py +32 -73
  3. pymodaq/control_modules/daq_viewer.py +73 -98
  4. pymodaq/control_modules/daq_viewer_ui.py +2 -1
  5. pymodaq/control_modules/move_utility_classes.py +17 -7
  6. pymodaq/control_modules/utils.py +153 -5
  7. pymodaq/control_modules/viewer_utility_classes.py +31 -20
  8. pymodaq/dashboard.py +23 -5
  9. pymodaq/examples/tcp_client.py +97 -0
  10. pymodaq/extensions/__init__.py +4 -0
  11. pymodaq/extensions/bayesian/__init__.py +2 -0
  12. pymodaq/extensions/bayesian/bayesian_optimisation.py +673 -0
  13. pymodaq/extensions/bayesian/utils.py +403 -0
  14. pymodaq/extensions/daq_scan.py +4 -4
  15. pymodaq/extensions/daq_scan_ui.py +2 -1
  16. pymodaq/extensions/pid/pid_controller.py +12 -7
  17. pymodaq/extensions/pid/utils.py +9 -26
  18. pymodaq/extensions/utils.py +3 -0
  19. pymodaq/post_treatment/load_and_plot.py +42 -19
  20. pymodaq/resources/VERSION +1 -1
  21. pymodaq/resources/config_template.toml +9 -24
  22. pymodaq/resources/setup_plugin.py +1 -1
  23. pymodaq/utils/config.py +103 -5
  24. pymodaq/utils/daq_utils.py +35 -134
  25. pymodaq/utils/data.py +614 -95
  26. pymodaq/utils/enums.py +17 -1
  27. pymodaq/utils/factory.py +2 -2
  28. pymodaq/utils/gui_utils/custom_app.py +5 -2
  29. pymodaq/utils/gui_utils/dock.py +33 -4
  30. pymodaq/utils/gui_utils/utils.py +14 -1
  31. pymodaq/utils/h5modules/backends.py +9 -1
  32. pymodaq/utils/h5modules/data_saving.py +254 -57
  33. pymodaq/utils/h5modules/saving.py +1 -0
  34. pymodaq/utils/leco/daq_move_LECODirector.py +172 -0
  35. pymodaq/utils/leco/daq_xDviewer_LECODirector.py +170 -0
  36. pymodaq/utils/leco/desktop.ini +2 -0
  37. pymodaq/utils/leco/director_utils.py +58 -0
  38. pymodaq/utils/leco/leco_director.py +88 -0
  39. pymodaq/utils/leco/pymodaq_listener.py +279 -0
  40. pymodaq/utils/leco/utils.py +41 -0
  41. pymodaq/utils/managers/action_manager.py +20 -6
  42. pymodaq/utils/managers/parameter_manager.py +6 -4
  43. pymodaq/utils/managers/roi_manager.py +63 -54
  44. pymodaq/utils/math_utils.py +1 -1
  45. pymodaq/utils/plotting/data_viewers/__init__.py +3 -1
  46. pymodaq/utils/plotting/data_viewers/base.py +286 -0
  47. pymodaq/utils/plotting/data_viewers/viewer.py +29 -202
  48. pymodaq/utils/plotting/data_viewers/viewer0D.py +94 -47
  49. pymodaq/utils/plotting/data_viewers/viewer1D.py +341 -174
  50. pymodaq/utils/plotting/data_viewers/viewer1Dbasic.py +1 -1
  51. pymodaq/utils/plotting/data_viewers/viewer2D.py +271 -181
  52. pymodaq/utils/plotting/data_viewers/viewerND.py +26 -22
  53. pymodaq/utils/plotting/items/crosshair.py +3 -3
  54. pymodaq/utils/plotting/items/image.py +2 -1
  55. pymodaq/utils/plotting/plotter/plotter.py +94 -0
  56. pymodaq/utils/plotting/plotter/plotters/__init__.py +0 -0
  57. pymodaq/utils/plotting/plotter/plotters/matplotlib_plotters.py +134 -0
  58. pymodaq/utils/plotting/plotter/plotters/qt_plotters.py +78 -0
  59. pymodaq/utils/plotting/utils/axes_viewer.py +1 -1
  60. pymodaq/utils/plotting/utils/filter.py +194 -147
  61. pymodaq/utils/plotting/utils/lineout.py +13 -11
  62. pymodaq/utils/plotting/utils/plot_utils.py +89 -12
  63. pymodaq/utils/scanner/__init__.py +0 -3
  64. pymodaq/utils/scanner/scan_config.py +1 -9
  65. pymodaq/utils/scanner/scan_factory.py +10 -36
  66. pymodaq/utils/scanner/scanner.py +3 -2
  67. pymodaq/utils/scanner/scanners/_1d_scanners.py +7 -5
  68. pymodaq/utils/scanner/scanners/_2d_scanners.py +36 -49
  69. pymodaq/utils/scanner/scanners/sequential.py +10 -4
  70. pymodaq/utils/scanner/scanners/tabular.py +10 -5
  71. pymodaq/utils/slicing.py +1 -1
  72. pymodaq/utils/tcp_ip/serializer.py +38 -5
  73. pymodaq/utils/tcp_ip/tcp_server_client.py +25 -17
  74. {pymodaq-4.1.5.dist-info → pymodaq-4.2.0.dist-info}/METADATA +4 -2
  75. {pymodaq-4.1.5.dist-info → pymodaq-4.2.0.dist-info}/RECORD +78 -63
  76. pymodaq/resources/config_scan_template.toml +0 -42
  77. {pymodaq-4.1.5.dist-info → pymodaq-4.2.0.dist-info}/WHEEL +0 -0
  78. {pymodaq-4.1.5.dist-info → pymodaq-4.2.0.dist-info}/entry_points.txt +0 -0
  79. {pymodaq-4.1.5.dist-info → pymodaq-4.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,7 @@
1
+ from abc import abstractmethod
1
2
  import os
2
3
  import sys
3
- from typing import List, TYPE_CHECKING
4
+ from typing import List, TYPE_CHECKING, Tuple
4
5
  import pymodaq.utils
5
6
  from qtpy import QtCore, QtGui, QtWidgets
6
7
  from qtpy.QtCore import QObject, Slot, Signal, QPointF
@@ -83,10 +84,25 @@ class ROIPositionMapper(QtWidgets.QWidget):
83
84
 
84
85
 
85
86
  class ROI(pgROI):
86
- def __init__(self, *args, **kwargs):
87
+ index_signal = Signal(int)
88
+
89
+ def __init__(self, *args, index=0, name='roi', **kwargs):
87
90
  super().__init__(*args, **kwargs)
91
+ self.name = name
92
+ self.index = index
88
93
  self._menu = QtWidgets.QMenu()
89
94
  self._menu.addAction('Set ROI positions', self.set_positions)
95
+ self.sigRegionChangeFinished.connect(self.emit_index_signal)
96
+
97
+ def emit_index_signal(self):
98
+ self.index_signal.emit(self.index)
99
+
100
+ @property
101
+ def color(self):
102
+ return self.pen.color()
103
+
104
+ def center(self):
105
+ return QPointF(self.pos().x() + self.size().x() / 2, self.pos().y() + self.size().y() / 2)
90
106
 
91
107
  def set_positions(self):
92
108
  mapper = ROIPositionMapper(self.pos(), self.size())
@@ -100,6 +116,12 @@ class ROI(pgROI):
100
116
  if self._menu is not None:
101
117
  self._menu.exec(event.screenPos())
102
118
 
119
+ def width(self) -> float:
120
+ return self.size().x()
121
+
122
+ def height(self) -> float:
123
+ return self.size().y()
124
+
103
125
 
104
126
  class ROIBrushable(ROI):
105
127
  def __init__(self, brush=None, *args, **kwargs):
@@ -133,20 +155,29 @@ class ROIBrushable(ROI):
133
155
  class LinearROI(pgLinearROI):
134
156
  index_signal = Signal(int)
135
157
 
136
- def __init__(self, index=0, pos=[0, 10], **kwargs):
158
+ def __init__(self, index=0, pos=[0, 10], name = 'roi', **kwargs):
137
159
  super().__init__(values=pos, **kwargs)
160
+ self.name = name
138
161
  self.index = index
139
162
  self.sigRegionChangeFinished.connect(self.emit_index_signal)
140
163
 
141
- def pos(self):
164
+ def pos(self) -> Tuple[float, float]:
142
165
  return self.getRegion()
143
166
 
144
- def setPos(self, pos):
167
+ def center(self) -> float:
168
+ pos = self.pos()
169
+ return (pos[0] + pos[1]) / 2
170
+
171
+ def setPos(self, pos: Tuple[int, int]):
145
172
  self.setRegion(pos)
146
173
 
147
174
  def setPen(self, color):
148
175
  self.setBrush(color)
149
176
 
177
+ @property
178
+ def color(self):
179
+ return self.brush.color()
180
+
150
181
  def emit_index_signal(self):
151
182
  self.index_signal.emit(self.index)
152
183
 
@@ -164,23 +195,13 @@ class EllipseROI(ROI):
164
195
  ============== =============================================================
165
196
 
166
197
  """
167
- index_signal = Signal(int)
198
+
168
199
 
169
200
  def __init__(self, index=0, pos=[0, 0], size=[10, 10], **kwargs):
170
201
  # QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1])
171
- super().__init__(pos=pos, size=size, **kwargs)
202
+ super().__init__(pos=pos, size=size, index=index, **kwargs)
172
203
  self.addRotateHandle([1.0, 0.5], [0.5, 0.5])
173
204
  self.addScaleHandle([0.5 * 2. ** -0.5 + 0.5, 0.5 * 2. ** -0.5 + 0.5], [0.5, 0.5])
174
- self.index = index
175
- self.sigRegionChangeFinished.connect(self.emit_index_signal)
176
-
177
- def center(self):
178
- # Project width/height in rotated frame
179
- width,height = rotate2D((0,0),(self.size().x(),self.size().y()),np.deg2rad(self.angle()))
180
- return QPointF(self.pos().x() + width / 2, self.pos().y() + height / 2)
181
-
182
- def emit_index_signal(self):
183
- self.index_signal.emit(self.index)
184
205
 
185
206
  def getArrayRegion(self, arr, img=None, axes=(0, 1), **kwds):
186
207
  """
@@ -211,9 +232,6 @@ class EllipseROI(ROI):
211
232
  else:
212
233
  return arr * mask
213
234
 
214
- def height(self):
215
- return self.size().y()
216
-
217
235
  def paint(self, p, opt, widget):
218
236
  r = self.boundingRect()
219
237
  p.setRenderHint(QtGui.QPainter.Antialiasing)
@@ -229,9 +247,6 @@ class EllipseROI(ROI):
229
247
  self.path.addEllipse(self.boundingRect())
230
248
  return self.path
231
249
 
232
- def width(self):
233
- return self.size().x()
234
-
235
250
 
236
251
  class SimpleRectROI(ROI):
237
252
  r"""
@@ -252,22 +267,10 @@ class SimpleRectROI(ROI):
252
267
 
253
268
 
254
269
  class RectROI(ROI):
255
- index_signal = Signal(int)
256
-
257
- def __init__(self, index=0, pos=[0, 0], size=[10, 10]):
258
- super().__init__(pos=pos, size=size) # , scaleSnap=True, translateSnap=True)
270
+ def __init__(self, index=0, pos=[0, 0], size=[10, 10], **kwargs):
271
+ super().__init__(pos=pos, size=size, index=index, **kwargs) # , scaleSnap=True, translateSnap=True)
259
272
  self.addScaleHandle([1, 1], [0, 0])
260
273
  self.addRotateHandle([0, 0], [0.5, 0.5])
261
- self.index = index
262
- self.sigRegionChangeFinished.connect(self.emit_index_signal)
263
-
264
- def center(self):
265
- # Project width/height in rotated frame
266
- width,height = rotate2D((0,0),(self.size().x(),self.size().y()),np.deg2rad(self.angle()))
267
- return QPointF(self.pos().x() + width / 2, self.pos().y() + height / 2)
268
-
269
- def emit_index_signal(self):
270
- self.index_signal.emit(self.index)
271
274
 
272
275
 
273
276
  ROI_NAME_PREFIX = 'ROI_'
@@ -298,7 +301,7 @@ class ROIScalableGroup(GroupParameter):
298
301
  if self.roi_type == '2D':
299
302
  children.extend([{'title': 'ROI Type', 'name': 'roi_type', 'type': 'str', 'value': typ, 'readonly': True},
300
303
  {'title': 'Use channel', 'name': 'use_channel', 'type': 'list',
301
- 'limits': ['red', 'green', 'blue', 'spread']}, ])
304
+ 'limits': ['red', 'green', 'blue']}, ])
302
305
  children.append({'title': 'Math type:', 'name': 'math_function', 'type': 'list',
303
306
  'limits': data_processors.functions_filtered('Data2D')})
304
307
  else:
@@ -332,15 +335,13 @@ class ROIScalableGroup(GroupParameter):
332
335
 
333
336
 
334
337
  class ROIManager(QObject):
335
- ROI_changed = Signal()
336
- ROI_changed_finished = Signal()
337
338
 
338
- new_ROI_signal = Signal(int, str)
339
+ new_ROI_signal = Signal(int, str, str)
339
340
  remove_ROI_signal = Signal(str)
340
341
  roi_value_changed = Signal(str, tuple)
341
-
342
+ color_signal = Signal(list)
342
343
  roi_update_children = Signal(list)
343
-
344
+ roi_changed = Signal()
344
345
  color_list = np.array(plot_colors)
345
346
 
346
347
  def __init__(self, viewer_widget=None, ROI_type='1D'):
@@ -348,7 +349,7 @@ class ROIManager(QObject):
348
349
  self.ROI_type = ROI_type
349
350
  self.roiwidget = QtWidgets.QWidget()
350
351
  self.viewer_widget = viewer_widget # either a PlotWidget or a ImageWidget
351
- self._ROIs = OrderedDict([])
352
+ self._ROIs: OrderedDict[str, ROI] = OrderedDict([])
352
353
  self.setupUI()
353
354
 
354
355
  @staticmethod
@@ -374,6 +375,9 @@ class ROIManager(QObject):
374
375
  else:
375
376
  raise KeyError(f'{roi_key} is not a valid ROI identifier for {self.ROIs}')
376
377
 
378
+ def emit_colors(self):
379
+ self.color_signal.emit([self._ROIs[roi_key].color for roi_key in self._ROIs])
380
+
377
381
  def add_roi_programmatically(self, roitype=ROI2D_TYPES[0]):
378
382
  self.settings.child('ROIs').addNew(roitype)
379
383
 
@@ -420,15 +424,16 @@ class ROIManager(QObject):
420
424
  else:
421
425
  childName = param.name()
422
426
  if change == 'childAdded': # new roi to create
423
- par = data[0]
427
+ par: Parameter = data[0]
424
428
  newindex = int(par.name()[-2:])
425
-
429
+ roi_type = ''
426
430
  if par.child('type').value() == '1D':
427
431
  roi_type = ''
428
432
 
429
433
  pos = self.viewer_widget.plotItem.vb.viewRange()[0]
430
434
  pos = pos[0] + np.diff(pos)*np.array([2,4])/6
431
435
  newroi = LinearROI(index=newindex, pos=pos)
436
+
432
437
  newroi.setZValue(-10)
433
438
  newroi.setBrush(par.child('Color').value())
434
439
  newroi.setOpacity(0.2)
@@ -443,14 +448,13 @@ class ROIManager(QObject):
443
448
 
444
449
  if roi_type == 'RectROI':
445
450
  newroi = RectROI(index=newindex, pos=pos,
446
- size=[width, height])
451
+ size=[width, height], name=par.name())
447
452
  else:
448
453
  newroi = EllipseROI(index=newindex, pos=pos,
449
- size=[width, height])
454
+ size=[width, height], name=par.name())
450
455
  newroi.setPen(par['Color'])
451
456
 
452
- newroi.sigRegionChanged.connect(lambda: self.ROI_changed.emit())
453
- newroi.sigRegionChangeFinished.connect(lambda: self.ROI_changed_finished.emit())
457
+ newroi.sigRegionChangeFinished.connect(lambda: self.roi_changed.emit())
454
458
  newroi.index_signal[int].connect(self.update_roi_tree)
455
459
  try:
456
460
  self.settings.sigTreeStateChanged.disconnect()
@@ -461,27 +465,31 @@ class ROIManager(QObject):
461
465
 
462
466
  self._set_roi_from_index(newindex, newroi)
463
467
 
464
- self.new_ROI_signal.emit(newindex, roi_type)
468
+ self.new_ROI_signal.emit(newindex, roi_type, par.name())
465
469
  self.update_roi_tree(newindex)
470
+ self.emit_colors()
471
+ self.roi_changed.emit()
466
472
 
467
473
  elif change == 'value':
468
474
  if param.name() in putils.iter_children(self.settings.child('ROIs'), []):
469
475
  parent_name = putils.get_param_path(param)[putils.get_param_path(param).index('ROIs')+1]
470
476
  self.update_roi(parent_name, param)
471
477
  self.roi_value_changed.emit(parent_name, (param, param.value()))
478
+ if param.name() == 'Color':
479
+ self.emit_colors()
472
480
 
473
481
  elif change == 'parent':
474
482
  if 'ROI' in param.name():
475
483
  roi = self._ROIs.pop(param.name())
476
484
  self.viewer_widget.plotItem.removeItem(roi)
477
485
  self.remove_ROI_signal.emit(param.name())
478
-
479
- self.ROI_changed_finished.emit()
486
+ self.emit_colors()
480
487
 
481
488
  def update_use_channel(self, channels: List[str]):
489
+ channels.append('All')
482
490
  for ind in range(len(self)):
483
491
  val = self.settings['ROIs', self.roi_format(ind), 'use_channel']
484
- self.settings.child('ROIs', self.roi_format(ind), 'use_channel').setOpts(limits=channels)
492
+ self.settings.child('ROIs', self.roi_format(ind), 'use_channel').setLimits(channels)
485
493
  if val not in channels:
486
494
  self.settings.child('ROIs', self.roi_format(ind), 'use_channel').setValue(channels[0])
487
495
 
@@ -489,6 +497,7 @@ class ROIManager(QObject):
489
497
  self._ROIs[roi_key].index_signal[int].disconnect()
490
498
  if param.name() == 'Color':
491
499
  self._ROIs[roi_key].setPen(param.value())
500
+ self.emit_colors()
492
501
  elif param.name() == 'left' or param.name() == 'x':
493
502
  pos = self._ROIs[roi_key].pos()
494
503
  poss = [param.value(), pos[1]]
@@ -105,7 +105,7 @@ def linspace_step(start, stop, step):
105
105
  scalar array
106
106
  The computed distribution axis as an array.
107
107
  """
108
- if np.abs(step) < 1e-12 or np.sign(stop - start) != np.sign(step) or start == stop:
108
+ if np.sign(stop - start) != np.sign(step) or start == stop:
109
109
  raise ValueError('Invalid value for one parameter')
110
110
  Nsteps = int(np.ceil((stop - start) / step))
111
111
  new_stop = start + (Nsteps - 1) * step
@@ -1,8 +1,10 @@
1
+ from .base import ViewersEnum
2
+
1
3
  from .viewer0D import Viewer0D
2
4
  from .viewer1D import Viewer1D
3
5
  from .viewer2D import Viewer2D
4
6
  from .viewerND import ViewerND
5
7
  from .viewer import ViewerDispatcher
6
- from .viewer import ViewersEnum
8
+
7
9
 
8
10
  DATA_TYPES = ['Data0D', 'Data1D', 'Data2D', 'DataND']
@@ -0,0 +1,286 @@
1
+
2
+ from typing import Union, TYPE_CHECKING, Iterable
3
+
4
+ from pymodaq.utils.enums import BaseEnum
5
+ from pyqtgraph.graphicsItems import InfiniteLine, ROI
6
+ from qtpy import QtWidgets
7
+ from qtpy.QtCore import QObject, Signal, QRectF
8
+
9
+ from pymodaq.utils.data import DataToExport, DataWithAxes, DataDim, DataDistribution
10
+ from pymodaq.utils.exceptions import ViewerError
11
+ from pymodaq.utils.plotting.utils.plot_utils import RoiInfo
12
+
13
+ if TYPE_CHECKING:
14
+ from pymodaq.utils.plotting.data_viewers.viewer0D import Viewer0D
15
+ from pymodaq.utils.plotting.data_viewers.viewer1D import Viewer1D
16
+ from pymodaq.utils.plotting.data_viewers.viewer2D import Viewer2D
17
+ from pymodaq.utils.plotting.data_viewers.viewerND import ViewerND
18
+
19
+
20
+ class ViewersEnum(BaseEnum):
21
+ """enum relating a given viewer with data type"""
22
+ Viewer0D = 'Data0D'
23
+ Viewer1D = 'Data1D'
24
+ Viewer2D = 'Data2D'
25
+ ViewerND = 'DataND'
26
+ ViewerSequential = 'DataSequential'
27
+
28
+ def get_dim(self):
29
+ return self.value.split('Data')[1].split('D')[0]
30
+
31
+ def increase_dim(self, ndim: int):
32
+ dim = self.get_dim()
33
+ if dim != 'N':
34
+ dim_as_int = int(dim) + ndim
35
+ if dim_as_int > 2:
36
+ dim = 'N'
37
+ else:
38
+ dim = str(dim_as_int)
39
+ else:
40
+ dim = 'N'
41
+ return ViewersEnum[f'Viewer{dim}D']
42
+
43
+ @classmethod
44
+ def from_n_axes(cls, n_axes: int):
45
+ if n_axes == 0:
46
+ return ViewersEnum['Viewer0D']
47
+ elif n_axes == 1:
48
+ return ViewersEnum['Viewer1D']
49
+ elif n_axes == 2:
50
+ return ViewersEnum['Viewer2D']
51
+ elif n_axes > 2:
52
+ return ViewersEnum['ViewerND']
53
+
54
+ @staticmethod
55
+ def get_viewers_enum_from_metadata(dim: DataDim,
56
+ distribution: DataDistribution,
57
+ n_nav_axes: int,
58
+ n_sig_indexes: int,
59
+ shape_len: int,
60
+ size: int) -> 'ViewersEnum':
61
+ if dim.name == 'Data0D':
62
+ viewer = 'Viewer0D'
63
+ elif dim.name == 'Data1D':
64
+ viewer = 'Viewer1D'
65
+ elif dim.name == 'Data2D':
66
+ viewer = 'Viewer2D'
67
+ else:
68
+ if distribution.name == 'uniform':
69
+ if shape_len < 3:
70
+ if shape_len == 1 and size == 1:
71
+ viewer = 'Viewer0D'
72
+ elif shape_len == 1 and size > 1:
73
+ viewer = 'Viewer1D'
74
+ elif shape_len == 2:
75
+ viewer = 'Viewer2D'
76
+ else:
77
+ viewer = 'ViewerND'
78
+ else:
79
+ viewer = 'ViewerND'
80
+ else:
81
+ if n_sig_indexes == 0:
82
+ if n_nav_axes == 1:
83
+ viewer = 'Viewer1D'
84
+ elif n_nav_axes == 2:
85
+ viewer = 'Viewer2D'
86
+ else:
87
+ viewer = 'ViewerND'
88
+ else:
89
+ viewer = 'ViewerND'
90
+ return ViewersEnum[viewer]
91
+
92
+ @staticmethod
93
+ def get_viewers_enum_from_data(dwa: DataWithAxes) -> 'ViewersEnum':
94
+ if dwa.dim.name == 'Data0D':
95
+ viewer = 'Viewer0D'
96
+ elif dwa.dim.name == 'Data1D':
97
+ viewer = 'Viewer1D'
98
+ elif dwa.dim.name == 'Data2D':
99
+ viewer = 'Viewer2D'
100
+ else:
101
+ if dwa.distribution.name == 'uniform':
102
+ if len(dwa.shape) < 3:
103
+ dwa.nav_indexes = ()
104
+ if len(dwa.shape) == 1 and dwa.size == 1:
105
+ viewer = 'Viewer0D'
106
+ elif len(dwa.shape) == 1 and dwa.size > 1:
107
+ viewer = 'Viewer1D'
108
+ elif len(dwa.shape) == 2:
109
+ viewer = 'Viewer2D'
110
+ else:
111
+ viewer = 'ViewerND'
112
+ else:
113
+ viewer = 'ViewerND'
114
+ else:
115
+ if len(dwa.sig_indexes) == 0:
116
+ if len(dwa.get_nav_axes()) == 1:
117
+ viewer = 'Viewer1D'
118
+ elif len(dwa.get_nav_axes()) == 2:
119
+ viewer = 'Viewer2D'
120
+ else:
121
+ viewer = 'ViewerND'
122
+ else:
123
+ viewer = 'ViewerND'
124
+ return ViewersEnum[viewer]
125
+
126
+
127
+ class ViewerBase(QObject):
128
+ """Base Class for data viewers implementing all common functionalities
129
+
130
+ Parameters
131
+ ----------
132
+ parent: QtWidgets.QWidget
133
+ title: str
134
+
135
+ Attributes
136
+ ----------
137
+ view: QObject
138
+ Ui interface of the viewer
139
+
140
+ data_to_export_signal: Signal[DataToExport]
141
+ ROI_changed: Signal
142
+ crosshair_dragged: Signal[float, float]
143
+ crosshair_clicked: Signal[bool]
144
+ sig_double_clicked: Signal[float, float]
145
+ status_signal: Signal[str]
146
+ """
147
+ data_to_export_signal = Signal(DataToExport)
148
+ _data_to_show_signal = Signal(DataWithAxes)
149
+
150
+ ROI_changed = Signal()
151
+ crosshair_dragged = Signal(float, float) # Crosshair position in units of scaled top/right axes
152
+ status_signal = Signal(str)
153
+ crosshair_clicked = Signal(bool)
154
+ sig_double_clicked = Signal(float, float)
155
+ ROI_select_signal = Signal(QRectF) # deprecated: use roi_select_signal
156
+ roi_select_signal = Signal(RoiInfo)
157
+
158
+ def __init__(self, parent: QtWidgets.QWidget = None, title=''):
159
+ super().__init__()
160
+ self.title = title if title != '' else self.__class__.__name__
161
+
162
+ self._raw_data = None
163
+ self.data_to_export: DataToExport = DataToExport(name=self.title)
164
+ self.view: Union[Viewer0D, Viewer1D, Viewer2D, ViewerND] = None
165
+
166
+ if parent is None:
167
+ parent = QtWidgets.QWidget()
168
+ parent.show()
169
+ self.parent = parent
170
+
171
+ self._display_temporary = False
172
+
173
+ @property
174
+ def has_action(self):
175
+ """Convenience method"""
176
+ if hasattr(self.view, 'has_action'):
177
+ return self.view.has_action
178
+
179
+ @property
180
+ def is_action_checked(self):
181
+ """Convenience method"""
182
+ if hasattr(self.view, 'is_action_checked'):
183
+ return self.view.is_action_checked
184
+
185
+ @property
186
+ def is_action_visible(self):
187
+ """Convenience method"""
188
+ if hasattr(self.view, 'is_action_visible'):
189
+ return self.view.is_action_visible
190
+
191
+ @property
192
+ def set_action_checked(self):
193
+ """Convenience method"""
194
+ if hasattr(self.view, 'set_action_checked'):
195
+ return self.view.set_action_checked
196
+
197
+ @property
198
+ def set_action_visible(self):
199
+ """Convenience method"""
200
+ if hasattr(self.view, 'set_action_visible'):
201
+ return self.view.set_action_visible
202
+
203
+ @property
204
+ def get_action(self):
205
+ """Convenience method"""
206
+ if hasattr(self.view, 'get_action'):
207
+ return self.view.get_action
208
+
209
+ @property
210
+ def toolbar(self):
211
+ """Convenience property"""
212
+ if hasattr(self.view, 'toolbar'):
213
+ return self.view.toolbar
214
+
215
+ @property
216
+ def viewer_type(self):
217
+ """str: the viewer data type see DATA_TYPES"""
218
+ return ViewersEnum[self.__class__.__name__].value
219
+
220
+ def show_data(self, data: DataWithAxes, **kwargs):
221
+ """Entrypoint to display data into the viewer
222
+
223
+ Parameters
224
+ ----------
225
+ data: data_mod.DataFromPlugins
226
+ """
227
+ if len(data.shape) > 4:
228
+ raise ViewerError(f'Ndarray of dim: {len(data.shape)} cannot be plotted using a {self.viewer_type}')
229
+
230
+ self.data_to_export = DataToExport(name=self.title)
231
+ self._raw_data = data
232
+
233
+ self._display_temporary = False
234
+
235
+ self._show_data(data, **kwargs)
236
+
237
+ def show_data_temp(self, data: DataWithAxes, **kwargs):
238
+ """Entrypoint to display temporary data into the viewer
239
+
240
+ No processed data signal is emitted from the viewer
241
+
242
+ Parameters
243
+ ----------
244
+ data: data_mod.DataFromPlugins
245
+ """
246
+ self._display_temporary = True
247
+ self.show_data(data, **kwargs)
248
+
249
+ def _show_data(self, data: DataWithAxes, *args, **kwargs):
250
+ """Specific viewers should implement it"""
251
+ raise NotImplementedError
252
+
253
+ def add_attributes_from_view(self):
254
+ """Convenience function to add attributes from the view to self"""
255
+ for attribute in self.convenience_attributes:
256
+ if hasattr(self.view, attribute):
257
+ setattr(self, attribute, getattr(self.view, attribute))
258
+
259
+ def trigger_action(self, action_name: str):
260
+ """Convenience function to trigger programmatically one of the action of the related view"""
261
+ if self.has_action(action_name):
262
+ self.get_action(action_name).trigger()
263
+
264
+ def activate_roi(self, activate=True):
265
+ """Activate the Roi manager using the corresponding action"""
266
+ raise NotImplementedError
267
+
268
+ def setVisible(self, show=True):
269
+ """convenience method to show or hide the paretn widget"""
270
+ self.parent.setVisible(show)
271
+
272
+ @property
273
+ def roi_target(self) -> Union[InfiniteLine.InfiniteLine, ROI.ROI]:
274
+ """To be implemented if necessary (Viewer1D and above)"""
275
+ return None
276
+
277
+ def move_roi_target(self, pos: Iterable[float] = None, **kwargs):
278
+ """move a specific read only ROI at the given position on the viewer"""
279
+ ...
280
+
281
+ def show_roi_target(self, show=True):
282
+ """Show/Hide a specific read only ROI"""
283
+ if self.roi_target is not None:
284
+ self.roi_target.setVisible(show)
285
+
286
+