pymodaq_data 0.0.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.
@@ -0,0 +1,411 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created the 15/11/2022
4
+
5
+ @author: Sebastien Weber
6
+ """
7
+ import copy
8
+ import datetime
9
+ from dateutil import parser
10
+ from numbers import Number
11
+ import os
12
+ from pathlib import Path
13
+ from typing import Union, Iterable
14
+
15
+
16
+ import numpy as np
17
+
18
+ from pymodaq_utils.logger import set_logger, get_module_name
19
+
20
+ from pymodaq_utils import utils
21
+ from pymodaq_utils.config import Config
22
+ from pymodaq_data.data import DataDim, DataToExport, Axis, DataWithAxes
23
+ from pymodaq_utils.enums import BaseEnum, enum_checker
24
+ from pymodaq_utils.warnings import deprecation_msg
25
+
26
+
27
+ from .backends import (H5Backend, backends_available, SaveType, InvalidSave, InvalidExport,
28
+ Node, GroupType, InvalidDataDimension, InvalidScanType,
29
+ GROUP, VLARRAY)
30
+ from . import browsing
31
+
32
+
33
+ config = Config()
34
+ logger = set_logger(get_module_name(__file__))
35
+
36
+
37
+ class FileType(BaseEnum):
38
+ detector = 0
39
+ actuator = 1
40
+ axis = 2
41
+ scan = 3
42
+
43
+
44
+ class DataType(BaseEnum):
45
+ data = 'Data'
46
+ axis = 'Axis'
47
+ live_scan = 'Live'
48
+ external_h5 = 'ExtData'
49
+ strings = 'Strings'
50
+ bkg = 'Bkg'
51
+ data_enlargeable = 'EnlData'
52
+ error = 'ErrorBar'
53
+
54
+
55
+ class H5SaverLowLevel(H5Backend):
56
+ """Object containing basic methods in order to structure and interact with a h5file compatible
57
+ with the h5browser
58
+
59
+ See Also
60
+ --------
61
+ H5Browser
62
+
63
+ Attributes
64
+ ----------
65
+ h5_file: pytables hdf5 file
66
+ object used to save all datas and metadas
67
+ h5_file_path: str or Path
68
+ The file path
69
+ """
70
+
71
+ def __init__(self, save_type: SaveType = 'scan', backend='tables'):
72
+ H5Backend.__init__(self, backend)
73
+
74
+ self.save_type = enum_checker(SaveType, save_type)
75
+
76
+ self.h5_file_path = None
77
+ self.h5_file_name = None
78
+ self.file_loaded = False
79
+
80
+ self._current_group = None
81
+ self._raw_group: Union[GROUP, str] = '/RawData'
82
+ self._logger_array = None
83
+
84
+ @property
85
+ def raw_group(self):
86
+ return self._raw_group
87
+
88
+ @property
89
+ def h5_file(self):
90
+ return self._h5file
91
+
92
+ def init_file(self, file_name: Path, raw_group_name='RawData', new_file=False, metadata: dict = None):
93
+ """Initializes a new h5 file.
94
+
95
+ Parameters
96
+ ----------
97
+ file_name: Path
98
+ a complete Path pointing to a h5 file
99
+ raw_group_name: str
100
+ Base node name
101
+ new_file: bool
102
+ If True create a new file, otherwise append to a potential existing one
103
+
104
+ Returns
105
+ -------
106
+ bool
107
+ True if new file has been created, False otherwise
108
+ """
109
+ datetime_now = datetime.datetime.now()
110
+
111
+ if file_name is not None and isinstance(file_name, Path):
112
+ self.h5_file_name = file_name.stem + ".h5"
113
+ self.h5_file_path = file_name.parent
114
+ if not self.h5_file_path.joinpath(self.h5_file_name).is_file():
115
+ new_file = True
116
+
117
+ else:
118
+ return
119
+
120
+ self.close_file()
121
+ self.open_file(self.h5_file_path.joinpath(self.h5_file_name), 'w' if new_file else 'a', title='PyMoDAQ file')
122
+
123
+ self._raw_group = self.get_set_group(self.root(), raw_group_name, title='Data from PyMoDAQ modules')
124
+ self.get_set_logger(self._raw_group)
125
+
126
+ if new_file:
127
+ self._raw_group.attrs['type'] = self.save_type.name # first possibility to set a node attribute
128
+ self.root().set_attr('file', self.h5_file_name) # second possibility
129
+
130
+ self.set_attr(self.root(), 'date', datetime_now.date().isoformat())
131
+ self.set_attr(self.root(), 'time', datetime_now.time().isoformat())
132
+
133
+ if metadata is not None:
134
+ for metadata_key in metadata:
135
+ self._raw_group.attrs[metadata_key] = metadata[metadata_key]
136
+
137
+ def save_file(self, filename=None):
138
+ if isinstance(filename, str) or isinstance(filename, Path) and filename != '':
139
+ file_path = Path(filename)
140
+ if str(file_path) != '':
141
+ super().save_file_as(filename)
142
+
143
+ def get_set_logger(self, where: Node = None) -> VLARRAY:
144
+ """ Retrieve or create (if absent) a logger enlargeable array to store logs
145
+ Get attributed to the class attribute ``logger_array``
146
+ Parameters
147
+ ----------
148
+ where: node
149
+ location within the tree where to save or retrieve the array
150
+
151
+ Returns
152
+ -------
153
+ vlarray
154
+ enlargeable array accepting strings as elements
155
+ """
156
+ if where is None:
157
+ where = self.raw_group
158
+ if isinstance(where, Node):
159
+ where = where.node
160
+ logger = 'Logger'
161
+ if logger not in list(self.get_children(where)):
162
+ # check if logger node exist
163
+ self._logger_array = self.add_string_array(where, logger)
164
+ self._logger_array.attrs['type'] = 'log'
165
+ else:
166
+ self._logger_array = self.get_node(where, name=logger)
167
+ return self._logger_array
168
+
169
+ def add_log(self, msg):
170
+ self._logger_array.append(msg)
171
+
172
+ def add_string_array(self, where, name, title='', metadata=dict([])):
173
+ array = self.create_vlarray(where, name, dtype='string', title=title)
174
+ array.attrs['shape'] = (0,)
175
+ array.attrs['data_type'] = 'strings'
176
+
177
+ for metadat in metadata:
178
+ array.attrs[metadat] = metadata[metadat]
179
+ return array
180
+
181
+ def add_array(self, where: Union[GROUP, str], name: str, data_type: DataType, array_to_save: np.ndarray = None,
182
+ data_shape: tuple = None, array_type: np.dtype = None, data_dimension: DataDim = None,
183
+ scan_shape: tuple = tuple([]), add_scan_dim=False, enlargeable: bool = False,
184
+ title: str = '', metadata=dict([]), ):
185
+
186
+ """save data arrays on the hdf5 file together with metadata
187
+ Parameters
188
+ ----------
189
+ where: GROUP
190
+ node where to save the array
191
+ name: str
192
+ name of the array in the hdf5 file
193
+ data_type: DataType
194
+ mandatory so that the h5Browser can interpret correctly the array
195
+ data_shape: Iterable
196
+ the shape of the array to save, mandatory if array_to_save is None
197
+ data_dimension: DataDim
198
+ The data's dimension
199
+ scan_shape: Iterable
200
+ the shape of the scan dimensions
201
+ title: str
202
+ the title attribute of the array node
203
+ array_to_save: ndarray or None
204
+ data to be saved in the array. If None, array_type and data_shape should be specified in order to init
205
+ correctly the memory
206
+ array_type: np.dtype or numpy types
207
+ eg np.float, np.int32 ...
208
+ enlargeable: bool
209
+ if False, data are saved as a CARRAY, otherwise as a EARRAY (for ragged data, see add_string_array)
210
+ metadata: dict
211
+ dictionnary whose keys will be saved as the array attributes
212
+ add_scan_dim: if True, the scan axes dimension (scan_shape iterable) is prepended to the array shape on the hdf5
213
+ In that case, the array is usually initialized as zero and further populated
214
+
215
+ Returns
216
+ -------
217
+ array (CARRAY or EARRAY)
218
+
219
+ See Also
220
+ --------
221
+ add_data, add_string_array
222
+ """
223
+ if array_type is None:
224
+ if array_to_save is None:
225
+ array_type = config('data_saving', 'data_type', 'dynamic')
226
+ else:
227
+ array_type = array_to_save.dtype
228
+
229
+ data_type = enum_checker(DataType, data_type)
230
+ data_dimension = enum_checker(DataDim, data_dimension)
231
+
232
+ if enlargeable:
233
+ # if data_shape == (1,):
234
+ # data_shape = None
235
+ array = self.create_earray(where, utils.capitalize(name), dtype=np.dtype(array_type),
236
+ data_shape=data_shape, title=title)
237
+ else:
238
+ if add_scan_dim: # means it is an array initialization to zero
239
+ shape = list(scan_shape[:])
240
+ if not(len(data_shape) == 1 and data_shape[0] == 1): # means data are not ndarrays of scalars
241
+ shape.extend(data_shape)
242
+ if array_to_save is None:
243
+ array_to_save = np.zeros(shape, dtype=np.dtype(array_type))
244
+
245
+ array = self.create_carray(where, utils.capitalize(name), obj=array_to_save, title=title)
246
+ self.set_attr(array, 'data_type', data_type.name)
247
+ self.set_attr(array, 'data_dimension', data_dimension.name)
248
+
249
+ for metadat in metadata:
250
+ self.set_attr(array, metadat, metadata[metadat])
251
+ return array
252
+
253
+ def get_set_group(self, where, name, title=''):
254
+ """Get the group located at where if it exists otherwise creates it
255
+
256
+ This also set the _current_group property
257
+ """
258
+ self._current_group = super().get_set_group(where, name, title)
259
+ return self._current_group
260
+
261
+ def get_groups(self, where: Union[str, GROUP], group_type: GroupType):
262
+ """Get all groups hanging from a Group and of a certain type"""
263
+ groups = []
264
+ for node_name in list(self.get_children(where)):
265
+ group = self.get_node(where, node_name)
266
+ if 'type' in group.attrs and group.attrs['type'] == group_type.name:
267
+ groups.append(group)
268
+ return groups
269
+
270
+ def get_last_group(self, where: GROUP, group_type: GroupType):
271
+ groups = self.get_groups(where, group_type)
272
+ if len(groups) != 0:
273
+ return groups[-1]
274
+ else:
275
+ return None
276
+
277
+ def get_node_from_attribute_match(self, where, attr_name, attr_value):
278
+ """Get a Node starting from a given node (Group) matching a pair of node attribute name and value"""
279
+ for node in self.walk_nodes(where):
280
+ if attr_name in node.attrs and node.attrs[attr_name] == attr_value:
281
+ return node
282
+
283
+ def get_node_from_title(self, where, title: str):
284
+ """Get a Node starting from a given node (Group) matching the given title"""
285
+ return self.get_node_from_attribute_match(where, 'TITLE', title)
286
+
287
+ def add_data_group(self, where, data_dim: DataDim, title='', settings_as_xml='', metadata=dict([])):
288
+ """Creates a group node at given location in the tree
289
+
290
+ Parameters
291
+ ----------
292
+ where: group node
293
+ where to create data group
294
+ group_data_type: DataDim
295
+ title: str, optional
296
+ a title for this node, will be saved as metadata
297
+ settings_as_xml: str, optional
298
+ XML string created from a Parameter object to be saved as metadata
299
+ metadata: dict, optional
300
+ will be saved as a new metadata attribute with name: key and value: dict value
301
+
302
+ Returns
303
+ -------
304
+ group: group node
305
+
306
+ See Also
307
+ --------
308
+ :py:meth:`add_group`
309
+ """
310
+ data_dim = enum_checker(DataDim, data_dim)
311
+ metadata.update(settings=settings_as_xml)
312
+ group = self.add_group(data_dim.name, 'data_dim', where, title, metadata)
313
+ return group
314
+
315
+ def add_incremental_group(self, group_type, where, title='', settings_as_xml='', metadata=dict([])):
316
+ """
317
+ Add a node in the h5 file tree of the group type with an increment in the given name
318
+ Parameters
319
+ ----------
320
+ group_type: str or GroupType enum
321
+ one of the possible values of **group_types**
322
+ where: str or node
323
+ parent node where to create the new group
324
+ title: str
325
+ node title
326
+ settings_as_xml: str
327
+ XML string containing Parameter representation
328
+ metadata: dict
329
+ extra metadata to be saved with this new group node
330
+
331
+ Returns
332
+ -------
333
+ node: newly created group node
334
+ """
335
+ group_type = enum_checker(GroupType, group_type)
336
+
337
+ nodes = [name for name in self.get_children(self.get_node(where))]
338
+ nodes_tmp = []
339
+ for node in nodes:
340
+ if utils.capitalize(group_type.name) in node:
341
+ nodes_tmp.append(node)
342
+ nodes_tmp.sort()
343
+ if len(nodes_tmp) == 0:
344
+ ind_group = -1
345
+ else:
346
+ ind_group = int(nodes_tmp[-1][-3:])
347
+ group = self.get_set_group(where, f'{utils.capitalize(group_type.name)}{ind_group + 1:03d}', title)
348
+ self.set_attr(group, 'settings', settings_as_xml)
349
+ if group_type.name.lower() != 'ch':
350
+ self.set_attr(group, 'type', group_type.name.lower())
351
+ else:
352
+ self.set_attr(group, 'type', '')
353
+ for metadat in metadata:
354
+ self.set_attr(group, metadat, metadata[metadat])
355
+ return group
356
+
357
+ def add_act_group(self, where, title='', settings_as_xml='', metadata=dict([])):
358
+ """
359
+ Add a new group of type detector
360
+ See Also
361
+ -------
362
+ add_incremental_group
363
+ """
364
+ group = self.add_incremental_group('actuator', where, title, settings_as_xml, metadata)
365
+ return group
366
+
367
+ def add_det_group(self, where, title='', settings_as_xml='', metadata=dict([])):
368
+ """
369
+ Add a new group of type detector
370
+ See Also
371
+ -------
372
+ add_incremental_group
373
+ """
374
+ group = self.add_incremental_group('detector', where, title, settings_as_xml, metadata)
375
+ return group
376
+
377
+ def add_scan_group(self, where='/RawData', title='', settings_as_xml='', metadata=dict([])):
378
+ """Add a new group of type scan
379
+
380
+ At creation adds the attributes description and scan_done to be used elsewhere
381
+
382
+ See Also
383
+ -------
384
+ add_incremental_group
385
+ """
386
+ metadata.update(dict(description='', scan_done=False))
387
+ group = self.add_incremental_group(GroupType['scan'], where, title, settings_as_xml, metadata)
388
+ return group
389
+
390
+ def add_ch_group(self, where, title='', settings_as_xml='', metadata=dict([])):
391
+ """
392
+ Add a new group of type channel
393
+ See Also
394
+ -------
395
+ add_incremental_group
396
+ """
397
+ group = self.add_incremental_group('ch', where, title, settings_as_xml, metadata)
398
+ return group
399
+
400
+
401
+ def add_move_group(self, where, title='', settings_as_xml='', metadata=dict([])):
402
+ """
403
+ Add a new group of type actuator
404
+ See Also
405
+ -------
406
+ add_incremental_group
407
+ """
408
+ group = self.add_incremental_group('actuator', where, title, settings_as_xml, metadata)
409
+ return group
410
+
411
+
@@ -0,0 +1,115 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created the 19/01/2023
4
+
5
+ @author: Sebastien Weber and N Tappy
6
+ """
7
+ # Standard imports
8
+ from collections import OrderedDict
9
+ from typing import List, Dict
10
+ from pathlib import Path
11
+ from importlib import import_module
12
+ from pymodaq_utils.utils import get_entrypoints
13
+
14
+ # 3rd party imports
15
+ import numpy as np
16
+
17
+
18
+ def register_exporter(parent_module_name: str = 'pymodaq_data.h5modules'):
19
+ exporters = []
20
+ try:
21
+ exporter_module = import_module(f'{parent_module_name}.exporters')
22
+
23
+ exporter_path = Path(exporter_module.__path__[0])
24
+
25
+ for file in exporter_path.iterdir():
26
+ if file.is_file() and 'py' in file.suffix and file.stem != '__init__':
27
+ try:
28
+ exporters.append(import_module(f'.{file.stem}', exporter_module.__name__))
29
+ except ModuleNotFoundError:
30
+ pass
31
+ except ModuleNotFoundError:
32
+ pass
33
+ finally:
34
+ return exporters
35
+
36
+
37
+ def register_exporters() -> list:
38
+ exporters = register_exporter('pymodaq_data.h5modules')
39
+ discovered_exporter_plugins = get_entrypoints('pymodaq.h5exporters')
40
+ for entry in discovered_exporter_plugins:
41
+ exporters.extend(register_exporter(entry.value))
42
+ return exporters
43
+
44
+
45
+ def find_scan_node(scan_node):
46
+ """
47
+ utility function to find the parent node of "scan" type, meaning some of its children (DAQ_scan case)
48
+ or co-nodes (daq_logger case) are navigation axes
49
+ Parameters
50
+ ----------
51
+ scan_node: (pytables node)
52
+ data node from where this function look for its navigation axes if any
53
+ Returns
54
+ -------
55
+ node: the parent node of 'scan' type
56
+ list: the data nodes of type 'navigation_axis' corresponding to the initial data node
57
+
58
+
59
+ """
60
+ try:
61
+ while True:
62
+ if scan_node.attrs['type'] == 'scan':
63
+ break
64
+ else:
65
+ scan_node = scan_node.parent_node
66
+ children = list(scan_node.children().values()) # for data saved using daq_scan
67
+ children.extend([scan_node.parent_node.children()[child] for child in
68
+ scan_node.parent_node.children_name()]) # for data saved using the daq_logger
69
+ nav_children = []
70
+ for child in children:
71
+ if 'type' in child.attrs.attrs_name:
72
+ if child.attrs['type'] == 'navigation_axis':
73
+ nav_children.append(child)
74
+ return scan_node, nav_children
75
+ except Exception:
76
+ return None, []
77
+
78
+
79
+ def get_h5_attributes(self, node_path):
80
+ """
81
+ """
82
+ node = self.get_node(node_path)
83
+ attrs_names = node.attrs.attrs_name
84
+ attr_dict = OrderedDict([])
85
+ for attr in attrs_names:
86
+ # if attr!='settings':
87
+ attr_dict[attr] = node.attrs[attr]
88
+
89
+ settings = None
90
+ scan_settings = None
91
+ if 'settings' in attrs_names:
92
+ if node.attrs['settings'] != '':
93
+ settings = node.attrs['settings']
94
+
95
+ if 'scan_settings' in attrs_names:
96
+ if node.attrs['scan_settings'] != '':
97
+ scan_settings = node.attrs['scan_settings']
98
+ pixmaps = []
99
+ for attr in attrs_names:
100
+ if 'pixmap' in attr:
101
+ pixmaps.append(node.attrs[attr])
102
+
103
+ return attr_dict, settings, scan_settings, pixmaps
104
+
105
+
106
+ def get_h5_data_from_node():
107
+ pass
108
+
109
+
110
+ def extract_axis():
111
+ pass
112
+
113
+
114
+ def verify_axis_data_uniformity():
115
+ pass
pymodaq_data/icon.ico ADDED
Binary file
File without changes
@@ -0,0 +1,91 @@
1
+ # Standard imports
2
+ import typing
3
+ from abc import ABCMeta, abstractmethod
4
+ from importlib import import_module
5
+ from pathlib import Path
6
+ from typing import Callable, List, Union
7
+
8
+ # 3rd party imports
9
+ import numpy as np
10
+
11
+ # project imports
12
+ from pymodaq_utils.abstract import abstract_attribute
13
+ from pymodaq_utils.logger import set_logger, get_module_name
14
+
15
+ from pymodaq_utils.factory import ObjectFactory
16
+
17
+
18
+ logger = set_logger(get_module_name(__file__))
19
+
20
+
21
+ def register_plotter(parent_module_name: str = 'pymodaq.utils.plotting.plotter'):
22
+ plotters = []
23
+ try:
24
+ plotter_module = import_module(f'{parent_module_name}.plotters')
25
+
26
+ plotter_path = Path(plotter_module.__path__[0])
27
+
28
+ for file in plotter_path.iterdir():
29
+ if file.is_file() and 'py' in file.suffix and file.stem != '__init__':
30
+ try:
31
+ plotters.append(import_module(f'.{file.stem}', plotter_module.__name__))
32
+ except Exception as e:
33
+ logger.warning(str(e))
34
+ except Exception as e:
35
+ logger.warning(str(e))
36
+ finally:
37
+ return plotters
38
+
39
+
40
+ class PlotterBase(metaclass=ABCMeta):
41
+ """Base class for a plotter. """
42
+
43
+ backend: str = abstract_attribute()
44
+ def __init__(self):
45
+ """Abstract Exporter Constructor"""
46
+ pass
47
+
48
+ @abstractmethod
49
+ def plot(self, data, *args, **kwargs) -> None:
50
+ """Abstract method to plot data object with a given backend"""
51
+ pass
52
+
53
+
54
+ class PlotterFactory(ObjectFactory):
55
+ """Factory class registering and storing interactive plotter"""
56
+
57
+ @classmethod
58
+ def register(cls) -> Callable:
59
+ """ To be used as a decorator
60
+
61
+ Register in the class registry a new plotter class using its 2 identifiers: backend and
62
+ data_dim
63
+ """
64
+ def inner_wrapper(wrapped_class: PlotterBase) -> Callable:
65
+ if cls.__name__ not in cls._builders:
66
+ cls._builders[cls.__name__] = {}
67
+ key = wrapped_class.backend
68
+
69
+ if key not in cls._builders[cls.__name__]:
70
+ cls._builders[cls.__name__][key] = wrapped_class
71
+ else:
72
+ logger.warning(f'The {cls.__name__}/{key} builder is already registered.'
73
+ f' Replacing it')
74
+ return wrapped_class
75
+
76
+ return inner_wrapper
77
+
78
+ @classmethod
79
+ def create(cls, key, **kwargs) -> PlotterBase:
80
+ builder = cls._builders[cls.__name__].get(key)
81
+ if not builder:
82
+ raise ValueError(key)
83
+ return builder(**kwargs)
84
+
85
+ def get(self, backend: str, **kwargs):
86
+ return self.create(backend, **kwargs)
87
+
88
+ def backends(self) -> List[str]:
89
+ """Returns the list of plotter backends, main identifier of a given plotter"""
90
+ return sorted(list(self.builders[self.__class__.__name__].keys()))
91
+
File without changes