pymodaq 4.1.5__py3-none-any.whl → 4.2.1__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 (80) hide show
  1. pymodaq/__init__.py +23 -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/__init__.py +25 -0
  35. pymodaq/utils/leco/daq_move_LECODirector.py +172 -0
  36. pymodaq/utils/leco/daq_xDviewer_LECODirector.py +170 -0
  37. pymodaq/utils/leco/desktop.ini +2 -0
  38. pymodaq/utils/leco/director_utils.py +58 -0
  39. pymodaq/utils/leco/leco_director.py +88 -0
  40. pymodaq/utils/leco/pymodaq_listener.py +279 -0
  41. pymodaq/utils/leco/utils.py +41 -0
  42. pymodaq/utils/managers/action_manager.py +20 -6
  43. pymodaq/utils/managers/parameter_manager.py +6 -4
  44. pymodaq/utils/managers/roi_manager.py +63 -54
  45. pymodaq/utils/math_utils.py +1 -1
  46. pymodaq/utils/plotting/data_viewers/__init__.py +3 -1
  47. pymodaq/utils/plotting/data_viewers/base.py +286 -0
  48. pymodaq/utils/plotting/data_viewers/viewer.py +29 -202
  49. pymodaq/utils/plotting/data_viewers/viewer0D.py +94 -47
  50. pymodaq/utils/plotting/data_viewers/viewer1D.py +341 -174
  51. pymodaq/utils/plotting/data_viewers/viewer1Dbasic.py +1 -1
  52. pymodaq/utils/plotting/data_viewers/viewer2D.py +271 -181
  53. pymodaq/utils/plotting/data_viewers/viewerND.py +26 -22
  54. pymodaq/utils/plotting/items/crosshair.py +3 -3
  55. pymodaq/utils/plotting/items/image.py +2 -1
  56. pymodaq/utils/plotting/plotter/plotter.py +94 -0
  57. pymodaq/utils/plotting/plotter/plotters/__init__.py +0 -0
  58. pymodaq/utils/plotting/plotter/plotters/matplotlib_plotters.py +134 -0
  59. pymodaq/utils/plotting/plotter/plotters/qt_plotters.py +78 -0
  60. pymodaq/utils/plotting/utils/axes_viewer.py +1 -1
  61. pymodaq/utils/plotting/utils/filter.py +194 -147
  62. pymodaq/utils/plotting/utils/lineout.py +13 -11
  63. pymodaq/utils/plotting/utils/plot_utils.py +89 -12
  64. pymodaq/utils/scanner/__init__.py +0 -3
  65. pymodaq/utils/scanner/scan_config.py +1 -9
  66. pymodaq/utils/scanner/scan_factory.py +10 -36
  67. pymodaq/utils/scanner/scanner.py +3 -2
  68. pymodaq/utils/scanner/scanners/_1d_scanners.py +7 -5
  69. pymodaq/utils/scanner/scanners/_2d_scanners.py +36 -49
  70. pymodaq/utils/scanner/scanners/sequential.py +10 -4
  71. pymodaq/utils/scanner/scanners/tabular.py +10 -5
  72. pymodaq/utils/slicing.py +1 -1
  73. pymodaq/utils/tcp_ip/serializer.py +38 -5
  74. pymodaq/utils/tcp_ip/tcp_server_client.py +25 -17
  75. {pymodaq-4.1.5.dist-info → pymodaq-4.2.1.dist-info}/METADATA +4 -2
  76. {pymodaq-4.1.5.dist-info → pymodaq-4.2.1.dist-info}/RECORD +79 -63
  77. pymodaq/resources/config_scan_template.toml +0 -42
  78. {pymodaq-4.1.5.dist-info → pymodaq-4.2.1.dist-info}/WHEEL +0 -0
  79. {pymodaq-4.1.5.dist-info → pymodaq-4.2.1.dist-info}/entry_points.txt +0 -0
  80. {pymodaq-4.1.5.dist-info → pymodaq-4.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,7 @@
1
1
  import sys
2
2
  import datetime
3
3
  from collections import OrderedDict
4
- from typing import List, Iterable
4
+ from typing import List, Iterable, Union, Dict, Tuple
5
5
 
6
6
  from qtpy import QtWidgets
7
7
  from qtpy.QtCore import QObject, Slot, Signal, Qt, QRectF
@@ -16,44 +16,17 @@ from pymodaq.utils import daq_utils as utils
16
16
  import pymodaq.utils.math_utils as mutils
17
17
  from pymodaq.utils.managers.action_manager import ActionManager
18
18
  from pymodaq.utils.plotting.data_viewers.viewer import ViewerBase
19
-
19
+ from pymodaq.utils.plotting.utils.plot_utils import make_dashed_pens, RoiInfo
20
20
  from pymodaq.utils.managers.roi_manager import ROIManager
21
21
  from pymodaq.utils.plotting.utils.filter import Filter1DFromCrosshair, Filter1DFromRois
22
- from pymodaq.utils.plotting.utils.lineout import LineoutPlotter
23
22
  from pymodaq.utils.plotting.widgets import PlotWidget
24
-
23
+ from pymodaq.utils.plotting.data_viewers.viewer0D import Viewer0D
25
24
 
26
25
  logger = set_logger(get_module_name(__file__))
27
26
 
28
27
  PLOT_COLORS = utils.plot_colors
29
28
 
30
29
 
31
- class LineoutPlotter(LineoutPlotter):
32
- """class to manage and display data filtered out into lineouts (1D, 0D)
33
-
34
- Should be inherited and subclass some methods as appropriate
35
-
36
- Parameters
37
- ----------
38
- graph_widgets: OrderedDict
39
- Includes plotwidgets to display data
40
- roi_manager:
41
- The ROIManager to create ROIs and manage their properties
42
- crosshair:
43
- The Crosshair object
44
- """
45
- lineout_widgets = ['int']
46
-
47
- def __init__(self, graph_widgets: OrderedDict, roi_manager: ROIManager, crosshair: Crosshair):
48
- super().__init__(graph_widgets, roi_manager, crosshair)
49
-
50
- def plot_other_lineouts(self, roi_dicts):
51
- pass
52
-
53
- def plot_other_crosshair_lineouts(self, crosshair_dict):
54
- pass
55
-
56
-
57
30
  class DataDisplayer(QObject):
58
31
  """
59
32
  This Object deals with the display of 1D data on a plotitem
@@ -62,14 +35,30 @@ class DataDisplayer(QObject):
62
35
  updated_item = Signal(list)
63
36
  labels_changed = Signal(list)
64
37
 
65
- def __init__(self, plotitem: pg.PlotItem):
38
+ def __init__(self, plotitem: pg.PlotItem, flip_axes=False, plot_colors=PLOT_COLORS):
66
39
  super().__init__()
40
+ self._doxy = False
41
+ self._do_sort = False
42
+ self._do_scatter = False
43
+ self._show_errors = False
44
+ self._flip_axes = flip_axes
67
45
  self._plotitem = plotitem
68
46
  self._plotitem.addLegend()
69
47
  self._plot_items: List[pg.PlotDataItem] = []
48
+ self._boundary_items: List[Tuple[pg.PlotDataItem, pg.PlotDataItem]] = []
49
+ self._fill_items: List[pg.FillBetweenItem] = []
70
50
  self._overlay_items: List[pg.PlotDataItem] = []
71
51
  self._axis: Axis = None
72
- self._data: DataRaw = None
52
+ self._data: DataWithAxes = None
53
+ self._plot_colors = plot_colors
54
+
55
+ @property
56
+ def Ndata(self):
57
+ return len(self._data) if self._data is not None else 0
58
+
59
+ def update_colors(self, colors: list):
60
+ self._plot_colors[0:len(colors)] = colors
61
+ self.update_data(self._data, force_update=True)
73
62
 
74
63
  self._doxy = False
75
64
  self._do_sort = False
@@ -92,94 +81,153 @@ class DataDisplayer(QObject):
92
81
  def get_plot_item(self, index: int):
93
82
  return self._plot_items[index]
94
83
 
95
- def update_data(self, data: DataRaw, do_xy=False, sort_data=False):
96
- self._data = data
97
- if len(data) != len(self._plot_items):
98
- self.update_display_items(data)
99
- self.update_plot(do_xy, data, sort_data)
84
+ def update_data(self, data: DataRaw, do_xy=False, sort_data=False, force_update=False,
85
+ do_scatter=False, show_errors=False):
86
+ if data is not None:
87
+ if data.labels != self.labels or self._show_errors != show_errors:
88
+ force_update = True
89
+ self._data = data
90
+ if len(data) != len(self._plot_items) or force_update:
91
+ self.update_display_items(data, show_errors)
92
+ self.update_plot(do_xy, data, sort_data, do_scatter, show_errors)
100
93
 
101
94
  def update_xy(self, do_xy=False):
102
95
  self._doxy = do_xy
103
- self.update_plot(do_xy, sort_data=self._do_sort)
96
+ self.update_plot(do_xy, sort_data=self._do_sort, scatter=self._do_scatter)
104
97
 
105
98
  def update_sort(self, do_sort=False):
106
99
  self._do_sort = do_sort
107
- self.update_plot(self._doxy, sort_data=self._do_sort)
100
+ self.update_plot(self._doxy, sort_data=self._do_sort, scatter=self._do_scatter)
108
101
 
109
- def update_plot(self, do_xy=True, data=None, sort_data=False):
110
- if data is None:
111
- data = self._data
112
- if sort_data:
113
- data = data.sort_data()
102
+ def update_scatter(self, do_scatter=False):
103
+ self._do_scatter = do_scatter
104
+ self.update_plot(self._doxy, sort_data=self._do_sort, scatter=self._do_scatter)
114
105
 
115
- axis = data.get_axis_from_index(0, create=False)[0]
116
- if axis is not None:
117
- self.update_axis(axis)
106
+ def update_errors(self, show_errors=False):
107
+ self._show_errors = show_errors
108
+ self.update_data(self._data, self._doxy, sort_data=self._do_sort, force_update=True,
109
+ do_scatter=self._do_scatter, show_errors=show_errors)
118
110
 
119
- self._doxy = do_xy
120
- self._do_sort = sort_data
121
-
122
- self.update_xyplot(do_xy, data)
123
-
124
- def update_xyplot(self, do_xy=True, data=None):
111
+ def update_plot(self, do_xy=True, data=None, sort_data=False, scatter=False, show_errors=False):
125
112
  if data is None:
126
113
  data = self._data
127
-
128
- for ind_data, dat in enumerate(data.data):
129
- if data.size > 0:
114
+ if data is not None:
115
+ if sort_data:
116
+ data = data.sort_data()
117
+ if len(data.axes) == 0:
118
+ data.create_missing_axes()
119
+ self._doxy = do_xy
120
+ self._do_sort = sort_data
121
+ self._show_errors = show_errors
122
+ self.update_xyplot(do_xy, data)
123
+
124
+ if scatter and self.get_plot_items()[0].opts['symbol'] is None:
125
+ if 'symbol_size' in data.extra_attributes:
126
+ symbol_size = data.symbol_size
127
+ else:
128
+ symbol_size = 5
129
+ if 'symbol' in data.extra_attributes:
130
+ symbol = data.symbol
131
+ else:
132
+ symbol = 'o'
133
+ if 'color' in data.extra_attributes:
134
+ color = data.color
135
+ else:
136
+ color = None
137
+ self.plot_with_scatter(True, symbol_size, symbol, color)
138
+ elif not scatter and self.get_plot_items()[0].opts['symbol'] is not None:
139
+ self.plot_with_scatter(False)
140
+
141
+ def update_xyplot(self, do_xy=True, dwa: DataWithAxes=None):
142
+ if dwa is None:
143
+ dwa = self._data
144
+ _axis = dwa.get_axis_from_index(0)[0]
145
+ _axis_array = _axis.get_data()
146
+ for ind_data, dat in enumerate(dwa.data):
147
+ if dwa.size > 0:
130
148
  if not do_xy:
131
- if self._axis is None:
132
- self.update_axis(Axis('index', data=Axis.create_simple_linear_data(dat.size)))
133
- self._plot_items[ind_data].setData(self._axis.get_data(), dat)
149
+ if self._flip_axes:
150
+ self._plot_items[ind_data].setData(dat, _axis_array)
151
+ else:
152
+ self._plot_items[ind_data].setData(_axis_array, dat)
153
+ if self._show_errors:
154
+ self._boundary_items[ind_data][0].setData(
155
+ _axis_array,
156
+ dat + dwa.get_error(ind_data))
157
+ self._boundary_items[ind_data][1].setData(
158
+ _axis_array,
159
+ dat - dwa.get_error(ind_data))
134
160
  else:
135
161
  self._plot_items[ind_data].setData(np.array([]), np.array([]))
136
162
 
137
- if do_xy and len(data) == 2:
138
- self._plot_items[0].setData(data.data[0], data.data[1])
139
- axis = self._plotitem.getAxis('bottom')
140
- axis.setLabel(text=data.labels[0], units='')
141
- axis = self._plotitem.getAxis('left')
142
- axis.setLabel(text=data.labels[1], units='')
143
- self.legend.setVisible(False)
163
+ if do_xy:
164
+ for ind, data_array in enumerate(dwa[1:]):
165
+ self._plot_items[ind].setData(dwa.data[0], data_array)
166
+ axis = self._plotitem.getAxis('bottom')
167
+ axis.setLabel(text=dwa.labels[0], units='')
168
+ axis = self._plotitem.getAxis('left')
169
+ axis.setLabel(text=' / '.join(dwa.labels[1:]), units='')
170
+ self.legend.setVisible(False)
144
171
 
145
172
  else:
146
173
  axis = self._plotitem.getAxis('bottom')
147
- axis.setLabel(text=self._axis.label, units=self._axis.units)
174
+ axis.setLabel(text=_axis.label, units=_axis.units)
148
175
  axis = self._plotitem.getAxis('left')
149
176
  axis.setLabel(text='', units='')
150
177
  self.legend.setVisible(True)
151
178
 
152
- def plot_with_scatter(self, with_scatter=True):
153
- symbol_size = 5
179
+ def plot_with_scatter(self, with_scatter=True, symbol_size=5, symbol='o', color=None):
180
+
154
181
  for ind, plot_item in enumerate(self.get_plot_items()):
182
+ if color is None:
183
+ color = self._plot_colors[ind]
155
184
  if with_scatter:
156
185
  pen = None
157
- symbol = 'o'
158
- brush = PLOT_COLORS[ind]
186
+ symbol_type = symbol
187
+ brush = color
159
188
 
160
189
  else:
161
- pen = PLOT_COLORS[ind]
162
- symbol = None
190
+ pen = color
191
+ symbol_type = None
163
192
  brush = None
164
193
 
165
194
  plot_item.setPen(pen)
166
195
  plot_item.setSymbolBrush(brush)
167
- plot_item.setSymbol(symbol)
196
+ plot_item.setSymbol(symbol_type)
168
197
  plot_item.setSymbolSize(symbol_size)
169
198
 
170
- def update_display_items(self, data: DataRaw):
199
+ def update_display_items(self, data: DataWithAxes = None, show_errors=False):
171
200
  while len(self._plot_items) > 0:
172
201
  self._plotitem.removeItem(self._plot_items.pop(0))
173
- for ind in range(len(data)):
174
- self._plot_items.append(pg.PlotDataItem(pen=PLOT_COLORS[ind]))
175
- self._plotitem.addItem(self._plot_items[-1])
176
- self.legend.addItem(self._plot_items[-1], data.labels[ind])
177
- self.updated_item.emit(self._plot_items)
178
- self.labels_changed.emit(data.labels)
202
+ if len(self._boundary_items) > 0:
203
+ b_items = self._boundary_items.pop(0)
204
+ self._plotitem.removeItem(b_items[0])
205
+ self._plotitem.removeItem(b_items[1])
206
+ self._plotitem.removeItem(self._fill_items.pop(0))
207
+
208
+ if data is not None:
209
+ for ind in range(len(data)):
210
+ self._plot_items.append(pg.PlotDataItem(pen=self._plot_colors[ind]))
211
+ self._plotitem.addItem(self._plot_items[-1])
212
+ self.legend.addItem(self._plot_items[-1], data.labels[ind])
213
+ if show_errors:
214
+ self._boundary_items.append((pg.PlotDataItem(pen=list(self._plot_colors[ind]) + [100]),
215
+ pg.PlotDataItem(pen=list(self._plot_colors[ind]) + [100])))
216
+ self._plotitem.addItem(self._boundary_items[-1][0])
217
+ self._plotitem.addItem(self._boundary_items[-1][1])
218
+ self._fill_items.append(pg.FillBetweenItem(*self._boundary_items[-1],
219
+ list(self._plot_colors[ind]) + [100]))
220
+ self._plotitem.addItem(self._fill_items[-1])
221
+
222
+ self.updated_item.emit(self._plot_items)
223
+ self.labels_changed.emit(data.labels)
179
224
 
180
225
  @property
181
226
  def labels(self):
182
- return self._data.labels
227
+ if self._data is None:
228
+ return []
229
+ else:
230
+ return self._data.labels
183
231
 
184
232
  def legend_items(self):
185
233
  return [item[1].text for item in self.legend.items]
@@ -190,27 +238,34 @@ class DataDisplayer(QObject):
190
238
  self._plotitem.removeItem(self._overlay_items.pop(0))
191
239
  else:
192
240
  for ind in range(len(self._data)):
193
- pen = pg.mkPen(color=PLOT_COLORS[ind], style=Qt.CustomDashLine)
241
+ pen = pg.mkPen(color=self._plot_colors[ind], style=Qt.CustomDashLine)
194
242
  pen.setDashPattern([10, 10])
195
243
  self._overlay_items.append(pg.PlotDataItem(pen=pen))
196
244
  self._plotitem.addItem(self._overlay_items[-1])
197
245
  if self._do_sort:
198
- self._overlay_items[ind].setData(self._axis.get_data(),
199
- self._data.sort_data()[ind])
246
+ self._overlay_items[ind].setData(
247
+ self._data.sort_data().get_axis_from_index(0)[0].get_data(),
248
+ self._data.sort_data()[ind])
200
249
  else:
201
- self._overlay_items[ind].setData(self._axis.get_data(), self._data[ind])
250
+ self._overlay_items[ind].setData(
251
+ self._data.get_axis_from_index(0)[0].get_data(),
252
+ self._data[ind])
202
253
 
203
254
 
204
255
 
205
256
  class View1D(ActionManager, QObject):
206
- def __init__(self, parent_widget: QtWidgets.QWidget = None):
257
+ def __init__(self, parent_widget: QtWidgets.QWidget = None, show_toolbar=True,
258
+ no_margins=False, flip_axes=False):
207
259
  QObject.__init__(self)
208
260
  ActionManager.__init__(self, toolbar=QtWidgets.QToolBar())
209
261
 
262
+ self.no_margins = no_margins
263
+ self.flip_axes = flip_axes
264
+
210
265
  self.data_displayer: DataDisplayer = None
211
266
  self.plot_widget: PlotWidget = None
212
- self.lineout_widgets: PlotWidget = None
213
- self.graphical_widgets: dict = None
267
+ self.lineout_widgets: QtWidgets.QWidget = None
268
+ self.lineout_viewers: Viewer0D = None
214
269
  self.crosshair: Crosshair = None
215
270
 
216
271
  self.roi_target = pg.InfiniteLine(pen='w')
@@ -225,11 +280,10 @@ class View1D(ActionManager, QObject):
225
280
 
226
281
  self.plot_widget = PlotWidget()
227
282
  self.roi_manager = ROIManager('1D')
228
- self.data_displayer = DataDisplayer(self.plotitem)
229
-
283
+ self.data_displayer = DataDisplayer(self.plotitem, flip_axes=self.flip_axes)
284
+ self.other_data_displayers: Dict[str, DataDisplayer] = {}
230
285
  self.setup_widgets()
231
286
 
232
- self.lineout_plotter = LineoutPlotter(self.graphical_widgets, self.roi_manager, self.crosshair)
233
287
  self.connect_things()
234
288
  self.prepare_ui()
235
289
 
@@ -238,6 +292,29 @@ class View1D(ActionManager, QObject):
238
292
  self.roi_target.setVisible(False)
239
293
  self.ROIselect.setVisible(False)
240
294
 
295
+ self.show_toolbar = show_toolbar
296
+ if not self.show_toolbar:
297
+ self.splitter_ver.setSizes([0, 1, 0])
298
+
299
+ def add_data_displayer(self, displayer_name: str, plot_colors=PLOT_COLORS):
300
+ self.other_data_displayers[displayer_name] = DataDisplayer(self.plotitem, self.flip_axes,
301
+ plot_colors)
302
+
303
+ def remove_data_displayer(self, displayer_name: str):
304
+ displayer = self.other_data_displayers.pop(displayer_name, None)
305
+ if displayer is not None:
306
+ displayer.update_display_items()
307
+
308
+ @Slot(int, str, str)
309
+ def add_roi_displayer(self, index, roi_type='', roi_name=''):
310
+ color = self.roi_manager.ROIs[roi_name].color
311
+ self.lineout_viewers.view.add_data_displayer(
312
+ roi_name, make_dashed_pens(color, self.data_displayer.Ndata))
313
+
314
+ @Slot(str)
315
+ def remove_roi_displayer(self, roi_name=''):
316
+ self.lineout_viewers.view.remove_data_displayer(roi_name)
317
+
241
318
  def move_roi_target(self, pos: Iterable[float], **kwargs):
242
319
  if not self.roi_target.isVisible():
243
320
  self.roi_target.setVisible(True)
@@ -246,8 +323,10 @@ class View1D(ActionManager, QObject):
246
323
  def get_double_clicked(self):
247
324
  return self.plot_widget.view.sig_double_clicked
248
325
 
249
- def display_roi_lineouts(self, roi_dict):
250
- self.lineout_plotter.plot_roi_lineouts(roi_dict)
326
+ def display_roi_lineouts(self, roi_dte: DataToExport):
327
+ for roi_name in roi_dte.get_origins('Data0D'):
328
+ self.lineout_viewers.view.display_data(
329
+ roi_dte.get_data_from_name_origin('IntData', roi_name), displayer=roi_name)
251
330
 
252
331
  @property
253
332
  def axis(self):
@@ -270,13 +349,29 @@ class View1D(ActionManager, QObject):
270
349
  """Convenience function from the Crosshair"""
271
350
  self.crosshair.set_crosshair_position(*positions)
272
351
 
273
- def display_data(self, data: DataWithAxes):
274
- self.set_action_visible('xyplot', len(data) == 2)
275
- self.data_displayer.update_data(data, self.is_action_checked('xyplot'), self.is_action_checked('sort'))
352
+ def display_data(self, data: Union[DataWithAxes, DataToExport], displayer: str = None):
353
+ if displayer is None:
354
+ if isinstance(data, DataWithAxes):
355
+ self.set_action_visible('xyplot', len(data) == 2)
356
+ self.data_displayer.update_data(data, self.is_action_checked('xyplot'),
357
+ self.is_action_checked('sort'),
358
+ do_scatter=self.is_action_checked('scatter'),
359
+ show_errors=self.is_action_checked('errors'))
360
+ elif isinstance(data, DataToExport):
361
+ self.set_action_visible('xyplot', len(data[0]) == 2)
362
+ self.data_displayer.update_data(data.pop(0), self.is_action_checked('xyplot'),
363
+ self.is_action_checked('sort'),
364
+ do_scatter=self.is_action_checked('scatter'),
365
+ show_errors=self.is_action_checked('errors'))
366
+ if len(data) > 0:
367
+ self.data_displayer.add_other_data(data)
368
+ elif displayer in self.other_data_displayers:
369
+ self.other_data_displayers[displayer].update_data(data,
370
+ self.is_action_checked('xyplot'),
371
+ self.is_action_checked('sort'))
276
372
 
277
373
  def prepare_ui(self):
278
374
  self.show_hide_crosshair(False)
279
- self.show_lineout_widgets()
280
375
 
281
376
  def do_math(self):
282
377
  try:
@@ -293,27 +388,30 @@ class View1D(ActionManager, QObject):
293
388
  @Slot(int, str)
294
389
  def update_roi_channels(self, index, roi_type=''):
295
390
  """Update the use_channel setting each time a ROI is added"""
296
- item_param = self.roi_manager.settings.child('ROIs', self.roi_manager.roi_format(index))
297
- item_param.child('use_channel').setOpts(limits=self.data_displayer.labels)
298
- item_param.child('use_channel').setValue(self.data_displayer.labels[0])
391
+ self.roi_manager.update_use_channel(self.data_displayer.labels.copy())
299
392
 
300
393
  def setup_widgets(self):
301
394
  self.parent_widget.setLayout(QtWidgets.QVBoxLayout())
395
+ if self.no_margins:
396
+ self.parent_widget.layout().setContentsMargins(0, 0, 0, 0)
302
397
  splitter_hor = QtWidgets.QSplitter(Qt.Horizontal)
303
398
  self.parent_widget.layout().addWidget(splitter_hor)
304
399
 
305
- splitter_ver = QtWidgets.QSplitter(Qt.Vertical)
306
- splitter_hor.addWidget(splitter_ver)
400
+ self.splitter_ver = QtWidgets.QSplitter(Qt.Vertical)
401
+ splitter_hor.addWidget(self.splitter_ver)
307
402
  splitter_hor.addWidget(self.roi_manager.roiwidget)
308
403
  self.roi_manager.roiwidget.hide()
309
404
 
310
- splitter_ver.addWidget(self.toolbar)
405
+ self.splitter_ver.addWidget(self.toolbar)
311
406
 
312
- self.lineout_widgets = PlotWidget()
313
- self.graphical_widgets = dict(lineouts=dict(int=self.lineout_widgets))
407
+ self.lineout_widgets = QtWidgets.QWidget()
408
+ self.lineout_viewers = Viewer0D(self.lineout_widgets, show_toolbar=False,
409
+ no_margins=True)
410
+ self.lineout_widgets.setContentsMargins(0, 0, 0, 0)
411
+ self.lineout_widgets.hide()
314
412
 
315
- splitter_ver.addWidget(self.plot_widget)
316
- splitter_ver.addWidget(self.lineout_widgets)
413
+ self.splitter_ver.addWidget(self.plot_widget)
414
+ self.splitter_ver.addWidget(self.lineout_widgets)
317
415
  self.roi_manager.viewer_widget = self.plot_widget
318
416
 
319
417
  self.crosshair = Crosshair(self.plotitem, orientation='vertical')
@@ -323,41 +421,47 @@ class View1D(ActionManager, QObject):
323
421
  self.connect_action('aspect_ratio', self.lock_aspect_ratio)
324
422
 
325
423
  self.connect_action('do_math', self.do_math)
326
- self.connect_action('do_math', self.lineout_plotter.roi_clicked)
327
-
328
- self.connect_action('scatter', self.data_displayer.plot_with_scatter)
424
+ self.connect_action('scatter', self.data_displayer.update_scatter)
329
425
  self.connect_action('xyplot', self.data_displayer.update_xy)
330
426
  self.connect_action('sort', self.data_displayer.update_sort)
331
427
  self.connect_action('crosshair', self.show_hide_crosshair)
332
- self.connect_action('crosshair', self.lineout_plotter.crosshair_clicked)
333
428
  self.connect_action('overlay', self.data_displayer.show_overlay)
429
+ self.connect_action('errors', self.data_displayer.update_errors)
334
430
  self.connect_action('ROIselect', self.show_ROI_select)
335
431
 
336
432
  self.roi_manager.new_ROI_signal.connect(self.update_roi_channels)
433
+ self.roi_manager.new_ROI_signal.connect(self.add_roi_displayer)
434
+ self.roi_manager.new_ROI_signal.connect(self.lineout_viewers.get_action('clear').click)
435
+ self.roi_manager.remove_ROI_signal.connect(self.remove_roi_displayer)
436
+
437
+ self.roi_manager.color_signal.connect(self.update_colors)
337
438
  self.data_displayer.labels_changed.connect(self.roi_manager.update_use_channel)
338
439
 
440
+ def update_colors(self, colors: list):
441
+ for ind, roi_name in enumerate(self.roi_manager.ROIs):
442
+ self.lineout_viewers.update_colors(make_dashed_pens(colors[ind]), displayer=roi_name)
443
+
339
444
  def show_ROI_select(self):
340
445
  self.ROIselect.setVisible(self.is_action_checked('ROIselect'))
341
446
 
342
- def show_lineout_widgets(self):
343
- state = self.is_action_checked('do_math') or self.is_action_checked('crosshair')
344
- for lineout_name in LineoutPlotter.lineout_widgets:
345
- lineout = self.lineout_plotter.get_lineout_widget(lineout_name)
346
- lineout.setMouseEnabled(state, state)
347
- lineout.showAxis('left', state)
348
- lineout.setVisible(state)
349
- lineout.update()
350
-
351
447
  def setup_actions(self):
352
448
  self.add_action('do_math', 'Math', 'Calculator', 'Do Math using ROI', checkable=True)
353
449
  self.add_action('crosshair', 'Crosshair', 'reset', 'Show data cursor', checkable=True)
354
- self.add_action('aspect_ratio', 'AspectRatio', 'Zoom_1_1', 'Fix the aspect ratio', checkable=True)
355
- self.add_action('scatter', 'Scatter', 'Marker', 'Switch between line or scatter plots', checkable=True)
450
+ self.add_action('aspect_ratio', 'AspectRatio', 'Zoom_1_1', 'Fix the aspect ratio',
451
+ checkable=True)
452
+ self.add_action('scatter', 'Scatter', 'Marker', 'Switch between line or scatter plots',
453
+ checkable=True)
356
454
  self.add_action('xyplot', 'XYPlotting', '2d',
357
- 'Switch between normal or XY representation (valid for 2 channels)', checkable=True,
455
+ 'Switch between normal or XY representation (valid for 2 channels)',
456
+ checkable=True,
358
457
  visible=False)
359
- self.add_action('overlay', 'Overlay', 'overlay', 'Plot overlays of current data', checkable=True)
360
- self.add_action('sort', 'Sort Data', 'sort_ascend', 'Display data in a sorted fashion with respect to axis',
458
+ self.add_action('overlay', 'Overlay', 'overlay', 'Plot overlays of current data',
459
+ checkable=True)
460
+ self.add_action('errors', 'Errors', 'Statistics2', 'Plot boundaries (~error bars) of '
461
+ 'the data',
462
+ checkable=True)
463
+ self.add_action('sort', 'Sort Data', 'sort_ascend',
464
+ 'Display data in a sorted fashion with respect to axis',
361
465
  checkable=True)
362
466
  self.add_action('ROIselect', 'ROI Select', 'Select_24',
363
467
  tip='Show/Hide ROI selection area', checkable=True)
@@ -370,16 +474,14 @@ class View1D(ActionManager, QObject):
370
474
  else:
371
475
  self.plotitem.vb.setAspectLocked(lock=False)
372
476
 
373
- def update_crosshair_data(self, crosshair_dict: dict):
374
- try:
477
+ def update_crosshair_data(self, crosshair_dte: DataToExport):
478
+ if len(crosshair_dte) > 0:
479
+ dwa = crosshair_dte[0]
375
480
  string = "y="
376
- for key in crosshair_dict:
377
- string += "{:.3e} / ".format(crosshair_dict[key]['value'])
481
+ for data_array in dwa:
482
+ string += f"{float(data_array[0]):.3e} / "
378
483
  self.get_action('y_label').setText(string)
379
- self.get_action('x_label').setText(f"x={crosshair_dict[key]['pos']:.3e} ")
380
-
381
- except Exception as e:
382
- pass
484
+ self.get_action('x_label').setText(f"x={float(dwa.axes[0].get_data()[0]):.3e} ")
383
485
 
384
486
  @Slot(bool)
385
487
  def show_hide_crosshair(self, show=True):
@@ -395,16 +497,24 @@ class View1D(ActionManager, QObject):
395
497
 
396
498
 
397
499
  class Viewer1D(ViewerBase):
398
- """this plots 1D data on a plotwidget. Math and measurement can be done on it.
500
+ """ DataWithAxis of type Data1D can be plotted using this data viewer
501
+
502
+ Methods
503
+ -------
504
+ show_data:
505
+ parameter:
506
+ * dwa: a DataWithaxis
507
+ * scatter_dwa: an optional extra DataWithAxis to be plotted with scatter points
508
+ it could define extra_attributes such as symbol: str (to define the symbol layout
509
+ default: 'o') and symbol_size: int (to define the symbol size)
399
510
 
400
- Datas and measurements are then exported with the signal data_to_export_signal
401
511
  """
402
- ROI_select_signal = Signal(QRectF)
403
512
 
404
- def __init__(self, parent: QtWidgets.QWidget = None, title=''):
513
+ def __init__(self, parent: QtWidgets.QWidget = None, title='', show_toolbar=True, no_margins=False,
514
+ flip_axes=False):
405
515
  super().__init__(parent=parent, title=title)
406
516
 
407
- self.view = View1D(self.parent)
517
+ self.view = View1D(self.parent, show_toolbar=show_toolbar, no_margins=no_margins, flip_axes=flip_axes)
408
518
 
409
519
  self.filter_from_rois = Filter1DFromRois(self.view.roi_manager)
410
520
  self.filter_from_rois.register_activation_signal(self.view.get_action('do_math').triggered)
@@ -418,6 +528,12 @@ class Viewer1D(ViewerBase):
418
528
 
419
529
  self._labels = []
420
530
 
531
+ def update_colors(self, colors: List, displayer=None):
532
+ if displayer is None:
533
+ self.view.data_displayer.update_colors(colors)
534
+ elif displayer in self.view.other_data_displayers:
535
+ self.view.other_data_displayers[displayer].update_colors(colors)
536
+
421
537
  @property
422
538
  def roi_manager(self):
423
539
  """Convenience method """
@@ -443,57 +559,47 @@ class Viewer1D(ViewerBase):
443
559
  def add_plot_item(self, item):
444
560
  self.view.add_plot_item(item)
445
561
 
446
- @Slot(dict)
447
- def process_crosshair_lineouts(self, crosshair_dict):
448
- self.view.update_crosshair_data(crosshair_dict)
562
+ def process_crosshair_lineouts(self, crosshair_dte: DataToExport):
563
+ self.view.update_crosshair_data(crosshair_dte)
449
564
  self.crosshair_dragged.emit(*self.view.crosshair.get_positions())
450
565
 
451
- @Slot(dict)
452
- def process_roi_lineouts(self, roi_dict):
453
- self.view.display_roi_lineouts(roi_dict)
566
+ def process_roi_lineouts(self, roi_dte: DataToExport):
567
+ self.view.display_roi_lineouts(roi_dte)
454
568
  self.measure_data_dict = dict([])
455
- for roi_key, lineout_data in roi_dict.items():
456
- if not self._display_temporary:
457
- if lineout_data.hor_data.size != 0:
458
- self.data_to_export.append(
459
- DataFromRoi(name=f'Hlineout_{roi_key}', data=[lineout_data.hor_data],
460
- axes=[Axis(data=lineout_data.hor_axis.get_data(),
461
- units=lineout_data.hor_axis.units,
462
- label=lineout_data.hor_axis.label,
463
- index=0)]))
464
-
465
- self.data_to_export.append(DataCalculated(name=f'Integrated_{roi_key}',
466
- data=[lineout_data.math_data]))
467
-
468
- self.measure_data_dict[f'{roi_key}:'] = lineout_data.math_data
469
-
470
- QtWidgets.QApplication.processEvents()
471
-
569
+ self.measure_data_dict = roi_dte.merge_as_dwa('Data0D').to_dict()
570
+ QtWidgets.QApplication.processEvents()
472
571
  self.view.roi_manager.settings.child('measurements').setValue(self.measure_data_dict)
572
+
473
573
  if not self._display_temporary:
574
+ roi_dte_bis = roi_dte.deepcopy()
575
+ for dwa in roi_dte_bis:
576
+ if dwa.name == 'HorData':
577
+ dwa.name = f'Hlineout_{dwa.origin}'
578
+ elif dwa.name == 'IntData':
579
+ dwa.name = f'Integrated_{dwa.origin}'
580
+ self.data_to_export.append(roi_dte_bis.data)
474
581
  self.data_to_export_signal.emit(self.data_to_export)
475
582
  self.ROI_changed.emit()
476
583
 
477
584
  def prepare_connect_ui(self):
478
585
  self.view.ROIselect.sigRegionChangeFinished.connect(self.selected_region_changed)
479
586
  self._data_to_show_signal.connect(self.view.display_data)
480
- self.view.lineout_plotter.roi_changed.connect(self.roi_changed)
587
+ self.roi_manager.roi_changed.connect(self.roi_changed)
588
+ self.roi_manager.roi_value_changed.connect(self.roi_changed)
589
+
481
590
  self.view.get_crosshair_signal().connect(self.crosshair_changed)
482
591
  self.view.get_double_clicked().connect(self.double_clicked)
483
592
 
484
593
  def selected_region_changed(self):
485
594
  if self.view.is_action_checked('ROIselect'):
486
- pos = self.view.ROIselect.getRegion()
487
- self.ROI_select_signal.emit(QRectF(pos[0], pos[1], 0, 0))
595
+ self.roi_select_signal.emit(RoiInfo.info_from_linear_roi(self.view.ROIselect))
488
596
 
489
- @Slot(float, float)
490
597
  def double_clicked(self, posx, posy=0):
491
598
  if self.view.is_action_checked('crosshair'):
492
599
  self.view.crosshair.set_crosshair_position(posx)
493
600
  self.crosshair_changed()
494
601
  self.sig_double_clicked.emit(posx, posy)
495
602
 
496
- @Slot(dict)
497
603
  def roi_changed(self):
498
604
  self.filter_from_rois.filter_data(self._raw_data)
499
605
 
@@ -513,12 +619,21 @@ class Viewer1D(ViewerBase):
513
619
  if labels != self._labels:
514
620
  self._labels = labels
515
621
 
516
- def _show_data(self, data: DataWithAxes):
622
+ def _show_data(self, data: DataWithAxes, *args, scatter_dwa: DataWithAxes =None,
623
+ **kwargs):
517
624
  self.labels = data.labels
625
+ if len(data.axes) == 0:
626
+ self.get_axis_from_view(data)
518
627
 
519
- self.get_axis_from_view(data)
520
628
  self.view.display_data(data)
521
629
 
630
+ if scatter_dwa is not None:
631
+ if isinstance(scatter_dwa, DataWithAxes):
632
+ if scatter_dwa.name not in self.view.other_data_displayers:
633
+ self.view.add_data_displayer(scatter_dwa.name, [(255, 0, 0)])
634
+ self.view.other_data_displayers[scatter_dwa.name].update_data(
635
+ scatter_dwa, do_scatter=True)
636
+
522
637
  if len(self.view.roi_manager.ROIs) == 0:
523
638
  self.data_to_export_signal.emit(self.data_to_export)
524
639
  else:
@@ -527,9 +642,10 @@ class Viewer1D(ViewerBase):
527
642
  self.crosshair_changed()
528
643
 
529
644
  def get_axis_from_view(self, data: DataWithAxes):
530
- if len(data.axes) == 0:
531
- if self.view.axis is not None:
532
- data.axes = [self.view.axis]
645
+ if self.view.axis is not None:
646
+ data.axes = [self.view.axis]
647
+ else:
648
+ data.create_missing_axes()
533
649
 
534
650
  def update_status(self, txt):
535
651
  logger.info(txt)
@@ -556,7 +672,7 @@ def main():
556
672
 
557
673
  # x = np.sin(np.linspace(0,6*np.pi,201))
558
674
  # y = np.sin(np.linspace(0, 6*np.pi, 201)+np.pi/2)
559
- data = DataRaw('mydata', data=[y1, ydata_expodec],
675
+ data = DataRaw('mydata', data=[y1, ydata_expodec, -ydata_expodec, -y1],
560
676
  axes=[Axis('myaxis', 'units', data=x)])
561
677
 
562
678
  Form.show()
@@ -617,6 +733,55 @@ def main_random():
617
733
  QtWidgets.QApplication.processEvents()
618
734
  sys.exit(app.exec_())
619
735
 
736
+ def main_extra_scatter():
737
+ app = QtWidgets.QApplication(sys.argv)
738
+ widget = QtWidgets.QWidget()
739
+ prog = Viewer1D(widget)
740
+
741
+ from pymodaq.utils.math_utils import gauss1D
742
+ x = np.linspace(0, 200, 201)
743
+ xlow = np.linspace(0, 200, 21)
744
+ y = gauss1D(x, 75, 25)
745
+ ylow = gauss1D(xlow, 75, 25)
746
+
747
+ QtWidgets.QApplication.processEvents()
748
+ data = DataRaw('mydata', data=[y],
749
+ axes=[Axis('myaxis', 'units', data=x, index=0, spread_order=0)],
750
+ labels=['Initial data'],
751
+ )
752
+ scatter_dwa = DataRaw('scatter', data=[ylow],
753
+ axes=[Axis('myaxis', 'units', data=xlow, index=0, spread_order=0)],
754
+ labels=['subsampled'],
755
+ symbol='d',
756
+ symbol_size=18,
757
+ color='b')
758
+
759
+ widget.show()
760
+ prog.show_data(data, scatter_dwa=scatter_dwa)
761
+ QtWidgets.QApplication.processEvents()
762
+ sys.exit(app.exec_())
763
+
764
+ def main_errors():
765
+ app = QtWidgets.QApplication(sys.argv)
766
+ widget = QtWidgets.QWidget()
767
+ prog = Viewer1D(widget)
768
+
769
+ from pymodaq.utils.math_utils import gauss1D
770
+ x = np.linspace(0, 200, 201)
771
+ y1 = gauss1D(x, 75, 25)
772
+ y2 = gauss1D(x, 120, 50, 2)
773
+
774
+ QtWidgets.QApplication.processEvents()
775
+ data = DataRaw('mydata', data=[y1, y2],
776
+ axes=[Axis('myaxis', 'units', data=x, index=0, spread_order=0)],
777
+ errors=(np.random.random_sample(x.shape),
778
+ np.random.random_sample(x.shape)))
779
+
780
+
781
+ widget.show()
782
+ prog.show_data(data)
783
+ QtWidgets.QApplication.processEvents()
784
+ sys.exit(app.exec_())
620
785
 
621
786
  def main_view1D():
622
787
  app = QtWidgets.QApplication(sys.argv)
@@ -648,6 +813,8 @@ def main_nans():
648
813
 
649
814
  if __name__ == '__main__': # pragma: no cover
650
815
  # main()
651
- main_random()
816
+ # main_random()
817
+ #main_errors()
818
+ main_extra_scatter()
652
819
  #main_view1D()
653
820
  #main_nans()