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.
- pymodaq_data/__init__.py +53 -0
- pymodaq_data/data.py +2901 -0
- pymodaq_data/h5modules/__init__.py +6 -0
- pymodaq_data/h5modules/backends.py +1031 -0
- pymodaq_data/h5modules/browsing.py +101 -0
- pymodaq_data/h5modules/data_saving.py +1107 -0
- pymodaq_data/h5modules/exporter.py +119 -0
- pymodaq_data/h5modules/exporters/__init__.py +0 -0
- pymodaq_data/h5modules/exporters/base.py +111 -0
- pymodaq_data/h5modules/exporters/flimj.py +63 -0
- pymodaq_data/h5modules/exporters/hyperspy.py +143 -0
- pymodaq_data/h5modules/saving.py +411 -0
- pymodaq_data/h5modules/utils.py +115 -0
- pymodaq_data/icon.ico +0 -0
- pymodaq_data/plotting/__init__.py +0 -0
- pymodaq_data/plotting/plotter/plotter.py +91 -0
- pymodaq_data/plotting/plotter/plotters/__init__.py +0 -0
- pymodaq_data/plotting/plotter/plotters/matplotlib_plotters.py +133 -0
- pymodaq_data/post_treatment/__init__.py +6 -0
- pymodaq_data/post_treatment/process_to_scalar.py +234 -0
- pymodaq_data/slicing.py +48 -0
- pymodaq_data/splash.png +0 -0
- pymodaq_data-0.0.1.dist-info/METADATA +160 -0
- pymodaq_data-0.0.1.dist-info/RECORD +26 -0
- pymodaq_data-0.0.1.dist-info/WHEEL +4 -0
- pymodaq_data-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -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
|