evefile 0.1.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.
@@ -0,0 +1,592 @@
1
+ """
2
+
3
+ *Metadata classes corresponding to the data entities.*
4
+
5
+ Data without context (*i.e.* metadata) are mostly useless. Hence, to every
6
+ class (type) of data in the :mod:`evefile.entities.data` module,
7
+ there exists a corresponding metadata class in this module.
8
+
9
+
10
+ Overview
11
+ ========
12
+
13
+ A first overview of the classes implemented in this module and their
14
+ hierarchy is given in the UML diagram below.
15
+
16
+ .. figure:: /uml/evefile.entities.metadata.*
17
+ :align: center
18
+ :width: 750px
19
+
20
+ Class hierarchy of the :mod:`evefile.entities.metadata` module.
21
+ Each concrete class in the :mod:`evefile.entities.data` module
22
+ has a corresponding metadata class in this module.
23
+ You may click on the image for a larger view.
24
+
25
+
26
+ A note on the :class:`AbstractDeviceMetadata` interface class: The eveH5
27
+ dataset corresponding to the :class:`TimestampMetadata` class is special in
28
+ sense of having no PV and transport type nor an id. Several options have been
29
+ considered to address this problem:
30
+
31
+ #. Moving these three attributes down the line and copying them multiple
32
+ times (feels bad).
33
+ #. Leaving the attributes blank for the "special" dataset (feels bad, too).
34
+ #. Introduce another class in the hierarchy, breaking the parallel to the
35
+ Data class hierarchy (potentially confusing).
36
+ #. Create a mixin class (abstract interface) with the three attributes and
37
+ use multiple inheritance/implements.
38
+
39
+ As obvious from the UML diagram, the last option has been chosen. The name
40
+ "DeviceMetadata" clearly distinguishes actual devices from datasets not
41
+ containing data read from some instrument.
42
+
43
+
44
+ The following is not a strict inheritance hierarchy, but rather a grouped
45
+ hierarchical list of classes for quick access to their individual API
46
+ documentation:
47
+
48
+ * :class:`Metadata`
49
+
50
+ * :class:`MonitorMetadata`
51
+ * :class:`MeasureMetadata`
52
+
53
+ * :class:`DeviceMetadata`
54
+ * :class:`TimestampMetadata`
55
+ * :class:`AxisMetadata`
56
+
57
+ * :class:`ChannelMetadata`
58
+
59
+ * :class:`SinglePointChannelMetadata`
60
+
61
+ * :class:`SinglePointNormalizedChannelMetadata`
62
+
63
+ * :class:`AverageChannelMetadata`
64
+
65
+ * :class:`AverageNormalizedChannelMetadata`
66
+
67
+ * :class:`IntervalChannelMetadata`
68
+
69
+ * :class:`IntervalNormalizedChannelMetadata`
70
+
71
+
72
+ Module documentation
73
+ ====================
74
+
75
+ """
76
+
77
+ import copy
78
+ import logging
79
+
80
+ logger = logging.getLogger(__name__)
81
+
82
+
83
+ class Metadata:
84
+ """
85
+ Metadata for the devices involved in a measurement.
86
+
87
+ This is the base class for all data(sets) and not meant to be used
88
+ directly. Rather, one of the individual subclasses should actually be
89
+ used.
90
+
91
+ This class complements the class
92
+ :class:`evefile.entities.data.Data`.
93
+
94
+ Attributes
95
+ ----------
96
+ name : :class:`str`
97
+ Name of the device.
98
+
99
+ Devices are uniquely identified by an ID that usually corresponds
100
+ to the EPICS process variable (PV). However, most devices have
101
+ "given" names as well that provide a more human-readable alternative.
102
+
103
+ options : :class:`dict`
104
+ (Scalar) options of the device.
105
+
106
+ Devices can have options. Generally, there are two types of
107
+ options: those whose values are *not* changing within a given scan
108
+ module, and those whose values can potentially change for every
109
+ individual position (count). The former are stored here as
110
+ key--value pairs with the key corresponding to the option name.
111
+ The latter are stored in the
112
+ :attr:`evefile.entities.data.Data.options` attribute.
113
+
114
+ Examples
115
+ --------
116
+ The :class:`Metadata` class is not meant to be used directly, as any
117
+ entities, but rather indirectly by means of the respective facades in
118
+ the boundaries technical layer of the ``evefile`` package.
119
+ Hence, for the time being, there are no dedicated examples how to use
120
+ this class. Of course, you can instantiate an object as usual.
121
+
122
+ """
123
+
124
+ def __init__(self):
125
+ super().__init__()
126
+ self.name = ""
127
+ self.options = {}
128
+ # Note: Attributes are listed manually here for explicit ordering in
129
+ # string representation using self.__str__
130
+ # Use only append or extend in subclasses!
131
+ self._attributes = ["name"]
132
+
133
+ def __str__(self):
134
+ """
135
+ Human-readable representation of the metadata.
136
+
137
+ Returns
138
+ -------
139
+ output : :class:`str`
140
+ Multiline string with one attribute per line
141
+
142
+ """
143
+ output = []
144
+ attribute_name_length = max(
145
+ len(attribute) for attribute in self._attributes
146
+ )
147
+ for attribute in self._attributes:
148
+ output.append(
149
+ f"{attribute:>{attribute_name_length}}:"
150
+ f" {getattr(self, attribute)}"
151
+ )
152
+ if self.options:
153
+ key_name_length = max(len(key) for key in self.options)
154
+ output.append("")
155
+ output.append("SCALAR OPTIONS")
156
+ for key, value in self.options.items():
157
+ output.append(f"{key:>{key_name_length}}:" f" {value}")
158
+ return "\n".join(output)
159
+
160
+ def copy_attributes_from(self, source=None):
161
+ """
162
+ Obtain attributes from another :obj:`Metadata` object.
163
+
164
+ Sometimes, it is useful to obtain the (public) attributes from
165
+ another :obj:`Metadata` object. Note that only public attributes are
166
+ copied. Furthermore, a (true) copy of the attributes is obtained,
167
+ hence the properties of source and target are actually different
168
+ objects.
169
+
170
+ Parameters
171
+ ----------
172
+ source : :class:`Metadata`
173
+ Object to copy attributes from.
174
+
175
+ Should typically be of the same (super)type.
176
+
177
+ Raises
178
+ ------
179
+ ValueError
180
+ Raised if no source is provided to copy attributes from.
181
+
182
+ """
183
+ if not source:
184
+ raise ValueError("No source provided to copy attributes from.")
185
+ public_attributes = [
186
+ item
187
+ for item in self.__dict__
188
+ if not (item.startswith("_") or item == "metadata")
189
+ ]
190
+ for attribute in public_attributes:
191
+ try:
192
+ setattr(
193
+ self, attribute, copy.copy(getattr(source, attribute))
194
+ )
195
+ except AttributeError:
196
+ logger.debug(
197
+ "Cannot set non-existing attribute %s", attribute
198
+ )
199
+
200
+
201
+ class AbstractDeviceMetadata:
202
+ """
203
+ Mixin class (interface) for metadata of actual physical devices.
204
+
205
+ Each physical device has a unique ID and can be accessed by an EPICS
206
+ process variable (PV).
207
+
208
+
209
+ Attributes
210
+ ----------
211
+ id : :class:`str`
212
+ Unique ID of the device.
213
+
214
+ pv : :class:`str`
215
+ EPICS process variable (PV) used to access the physical device.
216
+
217
+ access_mode : :class:`str`
218
+ Method used to access the EPICS PV.
219
+
220
+ Examples
221
+ --------
222
+ The :class:`AbstractDeviceMetadata` class is not meant to be used
223
+ directly, as any entities, but rather indirectly by means of the
224
+ respective facades in the boundaries technical layer of the
225
+ ``evefile`` package. Hence, for the time being,
226
+ there are no dedicated examples how to use this class. Of course,
227
+ you can instantiate an object as usual.
228
+
229
+ """
230
+
231
+ def __init__(self):
232
+ super().__init__()
233
+ self.id = "" # pylint: disable=invalid-name
234
+ self.pv = "" # pylint: disable=invalid-name
235
+ self.access_mode = ""
236
+
237
+
238
+ class MonitorMetadata(Metadata, AbstractDeviceMetadata):
239
+ """
240
+ Metadata for monitor data.
241
+
242
+ This class complements the class
243
+ :class:`evefile.entities.data.MonitorData`.
244
+
245
+
246
+ Examples
247
+ --------
248
+ The :class:`MonitorMetadata` class is not meant to be used directly,
249
+ as any entities, but rather indirectly by means of the respective
250
+ facades in the boundaries technical layer of the
251
+ ``evefile`` package. Hence, for the time being,
252
+ there are no dedicated examples how to use this class. Of course,
253
+ you can instantiate an object as usual.
254
+
255
+ """
256
+
257
+ def __init__(self):
258
+ super().__init__()
259
+ self._attributes.extend(["id", "pv", "access_mode"])
260
+
261
+
262
+ class MeasureMetadata(Metadata):
263
+ """
264
+ Metadata for data that are actually measured.
265
+
266
+ This class complements the class
267
+ :class:`evefile.entities.data.MeasureData`.
268
+
269
+
270
+ Attributes
271
+ ----------
272
+ unit : :class:`string`
273
+ Name of the unit corresponding to the data.
274
+
275
+
276
+ Examples
277
+ --------
278
+ The :class:`MeasureMetadata` class is not meant to be used directly,
279
+ as any entities, but rather indirectly by means of the respective
280
+ facades in the boundaries technical layer of the
281
+ ``evefile`` package. Hence, for the time being,
282
+ there are no dedicated examples how to use this class. Of course,
283
+ you can instantiate an object as usual.
284
+
285
+ """
286
+
287
+ def __init__(self):
288
+ super().__init__()
289
+ self.unit = ""
290
+ self._attributes.append("unit")
291
+
292
+
293
+ class DeviceMetadata(MeasureMetadata, AbstractDeviceMetadata):
294
+ """
295
+ Metadata for device data.
296
+
297
+ This class complements the class
298
+ :class:`evefile.entities.data.DeviceData`.
299
+
300
+
301
+ Examples
302
+ --------
303
+ The :class:`DeviceMetadata` class is not meant to be used directly,
304
+ as any entities, but rather indirectly by means of the respective
305
+ facades in the boundaries technical layer of the
306
+ ``evefile`` package. Hence, for the time being,
307
+ there are no dedicated examples how to use this class. Of course,
308
+ you can instantiate an object as usual.
309
+
310
+ """
311
+
312
+ def __init__(self):
313
+ super().__init__()
314
+ self._attributes.extend(["id", "pv", "access_mode"])
315
+
316
+
317
+ class AxisMetadata(MeasureMetadata, AbstractDeviceMetadata):
318
+ """
319
+ Metadata for axis data.
320
+
321
+ This class complements the class
322
+ :class:`evefile.entities.data.AxisData`.
323
+
324
+
325
+ Examples
326
+ --------
327
+ The :class:`AxisMetadata` class is not meant to be used directly, as any
328
+ entities, but rather indirectly by means of the respective facades in
329
+ the boundaries technical layer of the ``evefile`` package.
330
+ Hence, for the time being, there are no dedicated examples how to use
331
+ this class. Of course, you can instantiate an object as usual.
332
+
333
+ """
334
+
335
+ def __init__(self):
336
+ super().__init__()
337
+ self.deadband = 0.0
338
+ self._attributes.extend(["id", "pv", "access_mode", "deadband"])
339
+
340
+
341
+ class ChannelMetadata(MeasureMetadata, AbstractDeviceMetadata):
342
+ """
343
+ Metadata for channel data.
344
+
345
+ This class complements the class
346
+ :class:`evefile.entities.data.ChannelData`.
347
+
348
+
349
+ Examples
350
+ --------
351
+ The :class:`ChannelMetadata` class is not meant to be used directly,
352
+ as any entities, but rather indirectly by means of the respective
353
+ facades in the boundaries technical layer of the
354
+ ``evefile`` package. Hence, for the time being,
355
+ there are no dedicated examples how to use this class. Of course,
356
+ you can instantiate an object as usual.
357
+
358
+ """
359
+
360
+ def __init__(self):
361
+ super().__init__()
362
+ self._attributes.extend(["id", "pv", "access_mode"])
363
+
364
+
365
+ class TimestampMetadata(MeasureMetadata):
366
+ """
367
+ Metadata for the special dataset mapping timestamps to positions.
368
+
369
+ This class complements the class
370
+ :class:`evefile.entities.data.TimestampData`.
371
+
372
+
373
+ Examples
374
+ --------
375
+ The :class:`TimestampMetadata` class is not meant to be used directly,
376
+ as any entities, but rather indirectly by means of the respective
377
+ facades in the boundaries technical layer of the
378
+ ``evefile`` package. Hence, for the time being,
379
+ there are no dedicated examples how to use this class. Of course,
380
+ you can instantiate an object as usual.
381
+
382
+ """
383
+
384
+
385
+ class SinglePointChannelMetadata(ChannelMetadata):
386
+ """
387
+ Metadata for channels with numeric 0D data.
388
+
389
+ This class complements the class
390
+ :class:`evefile.entities.data.SinglePointChannelData`.
391
+
392
+
393
+ Examples
394
+ --------
395
+ The :class:`SinglePointChannelMetadata` class is not meant to be used
396
+ directly, as any entities, but rather indirectly by means of the
397
+ respective facades in the boundaries technical layer of the
398
+ ``evefile`` package. Hence, for the time being,
399
+ there are no dedicated examples how to use this class. Of course,
400
+ you can instantiate an object as usual.
401
+
402
+ """
403
+
404
+
405
+ class AverageChannelMetadata(ChannelMetadata):
406
+ """
407
+ Metadata for channels with averaged numeric 0D data.
408
+
409
+ This class complements the class
410
+ :class:`evefile.entities.data.AverageChannelData`.
411
+
412
+
413
+ Attributes
414
+ ----------
415
+ n_averages : :class:`int`
416
+ Number of averages
417
+
418
+ low_limit : :class:`float`
419
+ Minimum value for first reading of the channel
420
+
421
+ If set, the value of the channel is read and needs to be larger
422
+ than this minimum value to start the comparison phase.
423
+
424
+ max_attempts : :class:`float`
425
+ Maximum number of attempts for reading the channel data.
426
+
427
+ max_deviation : :class:`float`
428
+ Maximum deviation allowed between two values in the comparison phase.
429
+
430
+ If the :attr:`low_limit` is set, as soon as the value of the
431
+ channel is larger than the low limit, the comparison phase starts.
432
+ Here, two subsequent channel readouts need to be within the
433
+ boundary set by :attr:`max_deviation`.
434
+
435
+ However, no more than :attr:`max_attempts` channel readouts are done.
436
+
437
+
438
+ Examples
439
+ --------
440
+ The :class:`AverageChannelMetadata` class is not meant to be used
441
+ directly, as any entities, but rather indirectly by means of the
442
+ respective facades in the boundaries technical layer of the
443
+ ``evefile`` package. Hence, for the time being,
444
+ there are no dedicated examples how to use this class. Of course,
445
+ you can instantiate an object as usual.
446
+
447
+ """
448
+
449
+ def __init__(self):
450
+ super().__init__()
451
+ self.n_averages = 0
452
+ self.low_limit = 0.0
453
+ self.max_attempts = 0
454
+ self.max_deviation = 0.0
455
+ self._attributes.extend(
456
+ ["n_averages", "low_limit", "max_attempts", "max_deviation"]
457
+ )
458
+
459
+
460
+ class IntervalChannelMetadata(ChannelMetadata):
461
+ """
462
+ Metadata for channels with numeric 0D data measured in a time interval.
463
+
464
+ This class complements the class
465
+ :class:`evefile.entities.data.IntervalChannelData`.
466
+
467
+
468
+ Attributes
469
+ ----------
470
+ trigger_interval : :class:`float`
471
+ The interval/rate measurements are taken in seconds
472
+
473
+
474
+ Examples
475
+ --------
476
+ The :class:`IntervalChannelMetadata` class is not meant to be used
477
+ directly, as any entities, but rather indirectly by means of the
478
+ respective facades in the boundaries technical layer of the
479
+ ``evefile`` package. Hence, for the time being,
480
+ there are no dedicated examples how to use this class. Of course,
481
+ you can instantiate an object as usual.
482
+
483
+ """
484
+
485
+ def __init__(self):
486
+ super().__init__()
487
+ self.trigger_interval = 0.0
488
+ self._attributes.append("trigger_interval")
489
+
490
+
491
+ class NormalizedChannelMetadata:
492
+ """
493
+ Mixin class (interface) for metadata of normalized channel data.
494
+
495
+ Attributes
496
+ ----------
497
+ normalize_id : :class:`str`
498
+ Unique ID of the channel used to normalize the data
499
+
500
+
501
+ Examples
502
+ --------
503
+ The :class:`NormalizedChannelMetadata` class is not meant to be used
504
+ directly, as any entities, but rather indirectly by means of the
505
+ respective facades in the boundaries technical layer of the
506
+ ``evefile`` package. Hence, for the time being,
507
+ there are no dedicated examples how to use this class. Of course,
508
+ you can instantiate an object as usual.
509
+
510
+ """
511
+
512
+ def __init__(self):
513
+ super().__init__()
514
+ self.normalize_id = ""
515
+
516
+
517
+ class SinglePointNormalizedChannelMetadata(
518
+ ChannelMetadata, NormalizedChannelMetadata
519
+ ):
520
+ """
521
+ Metadata for channels with normalized numeric 0D data.
522
+
523
+ This class complements the class
524
+ :class:`evefile.entities.data.SinglePointNormalizedChannelData`.
525
+
526
+
527
+ Examples
528
+ --------
529
+ The :class:`SinglePointNormalizedChannelMetadata` class is not meant
530
+ to be used directly, as any entities, but rather indirectly by means
531
+ of the respective facades in the boundaries technical layer of the
532
+ ``evefile`` package. Hence, for the time being,
533
+ there are no dedicated examples how to use this class. Of course,
534
+ you can instantiate an object as usual.
535
+
536
+ """
537
+
538
+ def __init__(self):
539
+ super().__init__()
540
+ self._attributes.extend(["normalize_id"])
541
+
542
+
543
+ class AverageNormalizedChannelMetadata(
544
+ ChannelMetadata, NormalizedChannelMetadata
545
+ ):
546
+ """
547
+ Metadata for channels with normalized averaged numeric 0D data.
548
+
549
+ This class complements the class
550
+ :class:`evefile.entities.data.AverageNormalizedChannelData`.
551
+
552
+
553
+ Examples
554
+ --------
555
+ The :class:`AverageNormalizedChannelMetadata` class is not meant
556
+ to be used directly, as any entities, but rather indirectly by means
557
+ of the respective facades in the boundaries technical layer of the
558
+ ``evefile`` package. Hence, for the time being,
559
+ there are no dedicated examples how to use this class. Of course,
560
+ you can instantiate an object as usual.
561
+
562
+ """
563
+
564
+ def __init__(self):
565
+ super().__init__()
566
+ self._attributes.extend(["normalize_id"])
567
+
568
+
569
+ class IntervalNormalizedChannelMetadata(
570
+ ChannelMetadata, NormalizedChannelMetadata
571
+ ):
572
+ """
573
+ Metadata for channels with normalized interval-measured numeric 0D data.
574
+
575
+ This class complements the class
576
+ :class:`evefile.entities.data.IntervalNormalizedChannelData`.
577
+
578
+
579
+ Examples
580
+ --------
581
+ The :class:`IntervalNormalizedChannelMetadata` class is not meant
582
+ to be used directly, as any entities, but rather indirectly by means
583
+ of the respective facades in the boundaries technical layer of the
584
+ ``evefile`` package. Hence, for the time being,
585
+ there are no dedicated examples how to use this class. Of course,
586
+ you can instantiate an object as usual.
587
+
588
+ """
589
+
590
+ def __init__(self):
591
+ super().__init__()
592
+ self._attributes.extend(["normalize_id"])