evefile 0.1.0rc1__py3-none-any.whl → 0.1.0rc2__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.
@@ -488,7 +488,7 @@ class EveFile(File):
488
488
  dataframe = pd.DataFrame(
489
489
  {item.metadata.name: item.data for item in joined_data}
490
490
  )
491
- dataframe.index.name = "PosRef"
491
+ dataframe.index.name = "position"
492
492
  return dataframe
493
493
 
494
494
  def show_info(self):
@@ -466,7 +466,6 @@ import logging
466
466
  from functools import reduce
467
467
 
468
468
  import numpy as np
469
- from numpy import ma
470
469
 
471
470
  import evefile.entities
472
471
  import evefile.entities.data
@@ -616,56 +615,16 @@ class Join:
616
615
  def _fill_axes(self):
617
616
  for axis in self._axes:
618
617
  if axis.metadata.id in self.evefile.snapshots:
619
- self.evefile.snapshots[axis.metadata.id].get_data()
620
- axis.position_counts = np.searchsorted(
621
- axis.position_counts,
622
- self.evefile.snapshots[axis.metadata.id].position_counts,
618
+ axis.join(
619
+ positions=self._result_positions,
620
+ snapshot=self.evefile.snapshots[axis.metadata.id],
623
621
  )
624
- axis.data = np.insert(
625
- axis.data,
626
- axis.position_counts,
627
- self.evefile.snapshots[axis.metadata.id].data,
628
- )
629
- positions = (
630
- np.searchsorted(
631
- axis.position_counts, self._result_positions, side="right"
632
- )
633
- - 1
634
- )
635
- axis.position_counts = self._result_positions
636
- axis.data = axis.data[positions]
637
- if np.any(np.where(positions < 0)):
638
- axis.data = ma.masked_array(axis.data)
639
- axis.data[np.where(positions < 0)] = ma.masked
622
+ else:
623
+ axis.join(positions=self._result_positions, fill=True)
640
624
 
641
625
  def _fill_channels(self):
642
626
  for channel in self._channels:
643
- if len(self._result_positions) > len(channel.position_counts):
644
- original_values = channel.data
645
- channel.data = ma.masked_array(
646
- np.zeros(len(self._result_positions))
647
- )
648
- channel.data = ma.masked_array(channel.data)
649
- positions = np.setdiff1d(
650
- self._result_positions, channel.position_counts
651
- )
652
- channel.data[
653
- np.searchsorted(
654
- self._result_positions, channel.position_counts
655
- ).astype(np.int64)
656
- ] = original_values
657
- channel.data[
658
- np.searchsorted(self._result_positions, positions).astype(
659
- np.int64
660
- )
661
- ] = ma.masked
662
- elif len(self._result_positions) < len(channel.position_counts):
663
- channel.data = channel.data[
664
- np.searchsorted(
665
- channel.position_counts, self._result_positions
666
- ).astype(np.int64)
667
- ]
668
- channel.position_counts = self._result_positions
627
+ channel.join(positions=self._result_positions)
669
628
 
670
629
  def _assign_result(self):
671
630
  result = [*self._axes]
evefile/entities/data.py CHANGED
@@ -10,15 +10,15 @@
10
10
 
11
11
  Data are organised in "datasets" within HDF5, and the
12
12
  :mod:`evefile.entities.data` module provides the relevant entities
13
- to describe these datasets. Although currently (as of 07/2024, eve version
14
- 2.1) neither average nor interval detector channels save the individual
15
- data points, at least the former is a clear need of the
16
- engineers/scientists. Hence, the data model already respects this use
17
- case. As per position (count) there can be a variable number of measured
18
- points, the resulting array is no longer rectangular, but a "ragged
19
- array". While storing such arrays is possible directly in HDF5,
20
- the implementation within evefile is entirely independent of the actual
21
- representation in the eveH5 file.
13
+ to describe these datasets.
14
+
15
+ Please note that in contrast to the `evedata
16
+ <https://evedata.docs.radiometry.de/>`_ package, the ``evefile`` package has
17
+ a somewhat reduced data model, *e.g.* not considering individual data points
18
+ for average and interval channels (that are currently not available from the
19
+ underlying data files, anyway). Whether the corresponding module in the
20
+ ``evedata`` package will become a true superset of this module remains to be
21
+ seen.
22
22
 
23
23
 
24
24
  Overview
@@ -127,6 +127,8 @@ import logging
127
127
 
128
128
  import h5py
129
129
  import numpy as np
130
+ import pandas as pd
131
+ from numpy import ma
130
132
 
131
133
  from evefile.entities import metadata
132
134
 
@@ -193,6 +195,8 @@ class Data:
193
195
  self.options = {}
194
196
  self.importer = []
195
197
  self._data = None
198
+ # List of attributes containing data
199
+ self._data_attributes = ["data"]
196
200
 
197
201
  def __str__(self):
198
202
  """
@@ -307,14 +311,102 @@ class Data:
307
311
  )
308
312
  self.metadata.copy_attributes_from(source.metadata)
309
313
 
314
+ def show_info(self):
315
+ """
316
+ Print basic information regarding the contents of a data object.
317
+
318
+ Often, it is convenient to get a brief overview of the contents of
319
+ a data object. The output of this method currently contains the
320
+ following sections:
321
+
322
+ * metadata
323
+ * options (if present)
324
+ * fields
325
+
326
+ The output could look similar to the following:
327
+
328
+ .. code-block:: none
329
+
330
+ METADATA
331
+ name: jane
332
+
333
+ OPTIONS
334
+ some_option: value
335
+
336
+ FIELDS
337
+ data
338
+
339
+ Here, the ``METADATA`` block simply outputs what you would get with
340
+
341
+ .. code-block::
342
+
343
+ print(data.metadata)
344
+
345
+ If options are present, then the keys of the :attr:`options` dict
346
+ are returned in the ``OPTIONS`` block. Finally, the ``FIELDS`` block
347
+ provides an overview of all the attributes containing some kind of
348
+ data. This will differ depending on the type of data you are looking
349
+ at.
350
+ """
351
+ print("METADATA")
352
+ print(self.metadata)
353
+ if self.options:
354
+ print("\nOPTIONS")
355
+ for key in self.options:
356
+ print(key)
357
+ print("\nFIELDS")
358
+ for item in dir(self):
359
+ if (
360
+ not item.startswith("_")
361
+ and not callable(getattr(self, item))
362
+ and item not in ["importer", "metadata", "options"]
363
+ ):
364
+ print(item)
365
+
366
+ def get_dataframe(self):
367
+ """
368
+ Retrieve Pandas DataFrame with data as column.
369
+
370
+ .. important::
371
+
372
+ While working with a Pandas DataFrame may seem convenient,
373
+ you're loosing basically all the relevant metadata of the
374
+ datasets. Hence, this method is rather a convenience method to
375
+ be backwards-compatible to older interfaces, but it is
376
+ explicitly *not* suggested for extensive use.
377
+
378
+ Returns
379
+ -------
380
+ dataframe : :class:`pandas.DataFrame`
381
+ Pandas DataFrame containing data as column.
382
+
383
+ """
384
+ if self.data is not None:
385
+ index = np.linspace(1, self.data.size, self.data.size)
386
+ else:
387
+ index = [0]
388
+ dataframe = pd.DataFrame(
389
+ {item: getattr(self, item) for item in self._data_attributes},
390
+ index=index,
391
+ )
392
+ return dataframe
393
+
310
394
 
311
395
  class MonitorData(Data):
312
396
  """
313
397
  Data from devices monitored, but not controlled by the eve engine.
314
398
 
399
+ Monitors are a concept stemming from the underlying `EPICS layer
400
+ <https://epics-controls.org/>`_ and are closely related to telemetry in
401
+ general. In short: You register a certain "device", record an initial
402
+ value, and from then on only changes to this value (together with a
403
+ timestamp). This allows you to record any relevant changes in your setup
404
+ with minimal overhead and data storage.
405
+
315
406
  In contrast to :class:`MeasureData`, :class:`MonitorData` do not have
316
407
  a position as primary axis, but a timestamp in milliseconds, *i.e.*,
317
- the :attr:`milliseconds` attribute.
408
+ the :attr:`milliseconds` attribute. This means that without further ado,
409
+ you cannot plot monitor data against other data.
318
410
 
319
411
 
320
412
  Attributes
@@ -356,6 +448,33 @@ class MonitorData(Data):
356
448
  f"<{type(self).__name__}>"
357
449
  )
358
450
 
451
+ def get_dataframe(self):
452
+ """
453
+ Retrieve Pandas DataFrame with data as column.
454
+
455
+ The index is named "milliseconds" and contains the values of the
456
+ :attr:`milliseconds` attribute of the data object.
457
+
458
+ .. important::
459
+
460
+ While working with a Pandas DataFrame may seem convenient,
461
+ you're loosing basically all the relevant metadata of the
462
+ datasets. Hence, this method is rather a convenience method to
463
+ be backwards-compatible to older interfaces, but it is
464
+ explicitly *not* suggested for extensive use.
465
+
466
+ Returns
467
+ -------
468
+ dataframe : :class:`pandas.DataFrame`
469
+ Pandas DataFrame containing data as column.
470
+
471
+ """
472
+ dataframe = super().get_dataframe()
473
+ if self.milliseconds.ndim:
474
+ dataframe.index = self.milliseconds
475
+ dataframe.index.name = "milliseconds"
476
+ return dataframe
477
+
359
478
 
360
479
  class MeasureData(Data):
361
480
  """
@@ -439,6 +558,99 @@ class MeasureData(Data):
439
558
  def position_counts(self, positions=None):
440
559
  self._position_counts = positions
441
560
 
561
+ def get_dataframe(self):
562
+ """
563
+ Retrieve Pandas DataFrame with data as column.
564
+
565
+ The index is named "positions" and contains the values of the
566
+ :attr:`position_counts` attribute of the data object.
567
+
568
+ .. important::
569
+
570
+ While working with a Pandas DataFrame may seem convenient,
571
+ you're loosing basically all the relevant metadata of the
572
+ datasets. Hence, this method is rather a convenience method to
573
+ be backwards-compatible to older interfaces, but it is
574
+ explicitly *not* suggested for extensive use.
575
+
576
+ Returns
577
+ -------
578
+ dataframe : :class:`pandas.DataFrame`
579
+ Pandas DataFrame containing data as column.
580
+
581
+ """
582
+ dataframe = super().get_dataframe()
583
+ if self.position_counts is not None and self.position_counts.ndim:
584
+ dataframe.index = self.position_counts
585
+ dataframe.index.name = "position"
586
+ return dataframe
587
+
588
+ def join(self, positions=None):
589
+ """
590
+ Perform a left join of the data on the provided list of positions.
591
+
592
+ The main "quantisation" axis of the values for a device and the
593
+ common reference is the list of positions. To sensibly compare the
594
+ data of different devices or plot different device data against each
595
+ other, the data need to be harmonised, *i.e.* share a common set of
596
+ positions as indices.
597
+
598
+ If positions are not present in the original data, by default,
599
+ the corresponding entries will be masked and the :attr:`data`
600
+ attribute converted into a :class:`numpy.ma.MaskedArray`.
601
+
602
+ The reason for not using "NaN" (not a number) is, in short,
603
+ that "NaN" is only defined for floating point numbers, but neither
604
+ integers nor non-numeric values. Data, however, could generally
605
+ contain values that are *not* floating point numbers. For a more
606
+ detailed discussion, see the :mod:`evefile.controllers.joining`
607
+ module.
608
+
609
+ .. note::
610
+
611
+ The method will *alter* the data and positions of the underlying
612
+ :obj:`MeasureData` object. Hence, make sure to make a copy if
613
+ this is not your intended use case.
614
+
615
+
616
+ Parameters
617
+ ----------
618
+ positions : :class:`numpy.ndarray`
619
+ Array with positions the data should be mapped to.
620
+
621
+ Raises
622
+ ------
623
+ ValueError
624
+ Raised if no positions are provided
625
+
626
+ """
627
+ if positions is None:
628
+ raise ValueError("No positions provided")
629
+ for item in self._data_attributes:
630
+ data_ = getattr(self, item)
631
+ if len(positions) < len(self.position_counts):
632
+ # pylint: disable=unsubscriptable-object
633
+ data_ = data_[
634
+ np.searchsorted(self.position_counts, positions).astype(
635
+ np.int64
636
+ )
637
+ ]
638
+ elif len(positions) > len(self.position_counts):
639
+ original_values = data_
640
+ data_ = ma.masked_array(np.zeros(len(positions)))
641
+ data_ = ma.masked_array(data_)
642
+ new_positions = np.setdiff1d(positions, self.position_counts)
643
+ data_[
644
+ np.searchsorted(positions, self.position_counts).astype(
645
+ np.int64
646
+ )
647
+ ] = original_values
648
+ data_[
649
+ np.searchsorted(positions, new_positions).astype(np.int64)
650
+ ] = ma.masked
651
+ setattr(self, item, data_)
652
+ self.position_counts = positions
653
+
442
654
  def _import_from_hdf5dataimporter(self, importer=None):
443
655
  """
444
656
  Import data from HDF5 using data importer.
@@ -555,6 +767,87 @@ class AxisData(MeasureData):
555
767
  for attribute in importer.mapping.values():
556
768
  setattr(self, attribute, getattr(self, attribute)[indices])
557
769
 
770
+ def join(self, positions=None, fill=False, snapshot=None):
771
+ """
772
+ Perform a left join of the data on the provided list of positions.
773
+
774
+ The main "quantisation" axis of the values for a device and the
775
+ common reference is the list of positions. To sensibly compare the
776
+ data of different devices or plot different device data against each
777
+ other, the data need to be harmonised, *i.e.* share a common set of
778
+ positions as indices.
779
+
780
+ If positions are not present in the original data, by default,
781
+ the corresponding entries will be masked and the :attr:`data`
782
+ attribute converted into a :class:`numpy.ma.MaskedArray`.
783
+
784
+ The reason for not using "NaN" (not a number) is, in short,
785
+ that "NaN" is only defined for floating point numbers, but neither
786
+ integers nor non-numeric values. Data, however, could generally
787
+ contain values that are *not* floating point numbers. For a more
788
+ detailed discussion, see the :mod:`evefile.controllers.joining`
789
+ module.
790
+
791
+ .. note::
792
+
793
+ The method will *alter* the data and positions of the underlying
794
+ :obj:`MeasureData` object. Hence, make sure to make a copy if
795
+ this is not your intended use case.
796
+
797
+
798
+ Parameters
799
+ ----------
800
+ positions : :class:`numpy.ndarray`
801
+ Array with positions the data should be mapped to.
802
+
803
+ fill : :class:`bool`
804
+ Whether to fill missing positions with previous values.
805
+
806
+ Only in case a previous value exists for a given position (or a
807
+ snapshot containing a previous value is provided as additional
808
+ parameter), filling for the position will be performed.
809
+ Otherwise, the position is masked.
810
+
811
+ snapshot : :class:`AxisData`
812
+ Snapshot data corresponding to the original :obj:`AxisData` object.
813
+
814
+ Raises
815
+ ------
816
+ ValueError
817
+ Raised if no positions are provided
818
+
819
+ """
820
+ if snapshot is not None:
821
+ fill = True
822
+ if not fill:
823
+ super().join(positions=positions)
824
+ else:
825
+ if snapshot:
826
+ snapshot.get_data()
827
+ insert_positions = np.searchsorted(
828
+ self.position_counts,
829
+ snapshot.position_counts,
830
+ )
831
+ self.data = np.insert(
832
+ self.data,
833
+ insert_positions,
834
+ snapshot.data,
835
+ )
836
+ self.position_counts = np.insert(
837
+ self.position_counts,
838
+ insert_positions,
839
+ snapshot.position_counts,
840
+ )
841
+ new_positions = (
842
+ np.searchsorted(self.position_counts, positions, side="right")
843
+ - 1
844
+ )
845
+ self.position_counts = positions
846
+ self.data = self.data[new_positions]
847
+ if np.where(new_positions < 0)[0].size:
848
+ self.data = ma.masked_array(self.data)
849
+ self.data[np.where(new_positions < 0)] = ma.masked
850
+
558
851
 
559
852
  class ChannelData(MeasureData):
560
853
  """
@@ -743,12 +1036,6 @@ class AverageChannelData(ChannelData):
743
1036
  metadata : :class:`evefile.entities.metadata.AverageChannelMetadata`
744
1037
  Relevant metadata for the individual device.
745
1038
 
746
- raw_data : Any
747
- The raw individual values measured.
748
-
749
- attempts : numpy.ndarray
750
- Short description
751
-
752
1039
 
753
1040
  Examples
754
1041
  --------
@@ -764,56 +1051,44 @@ class AverageChannelData(ChannelData):
764
1051
  def __init__(self):
765
1052
  super().__init__()
766
1053
  self.metadata = metadata.AverageChannelMetadata()
767
- self.raw_data = None
768
- self.attempts = np.ndarray(shape=[], dtype=int)
769
- self._mean = None
770
- self._std = None
1054
+ self._attempts = None
1055
+ self._data_attributes = ["data", "attempts"]
771
1056
 
772
1057
  @property
773
- def mean(self):
1058
+ def attempts(self):
774
1059
  """
775
- Mean values for channel data.
1060
+ Number of attempts needed until final data were recorded.
776
1061
 
777
1062
  Returns
778
1063
  -------
779
- mean : :class:`numpy.ndarray`
780
- The mean of the values recorded.
781
-
782
- If more values have been recorded than should be averaged
783
- over, only the number of values to average over are taken from
784
- the end of the individual :attr:`raw_data` row.
1064
+ attempts : :class:`numpy.ndarray`
1065
+ Number of attempts
785
1066
 
786
1067
  """
787
- if self._mean is None:
788
- if self.raw_data is not None:
789
- self._mean = self.raw_data.mean(axis=1)
790
- else:
791
- self._mean = self.data
792
- return self._mean
1068
+ if self._attempts is None:
1069
+ self.get_data()
1070
+ return self._attempts
1071
+
1072
+ @attempts.setter
1073
+ def attempts(self, attempts=None):
1074
+ self._attempts = attempts
793
1075
 
794
1076
  @property
795
- def std(self):
1077
+ def mean(self):
796
1078
  """
797
- Standard deviation values for channel data.
1079
+ Mean values for channel data.
798
1080
 
799
1081
  Returns
800
1082
  -------
801
1083
  mean : :class:`numpy.ndarray`
802
- The standard deviation of the values recorded.
1084
+ The mean of the values recorded.
803
1085
 
804
- If more values have been recorded than should be averaged
805
- over, only the number of values to average over are taken from
806
- the end of the individual :attr:`raw_data` row to calculate
807
- the standard deviation.
1086
+ As at least up to eveH5 v7.x only the averaged values are stored
1087
+ in the HDF5 file, this simply returns the values stored
1088
+ in :attr:`data`.
808
1089
 
809
1090
  """
810
- if self._std is None and self.raw_data is not None:
811
- self._std = self.raw_data.std(axis=1)
812
- return self._std
813
-
814
- @std.setter
815
- def std(self, std=None):
816
- self._std = std
1091
+ return self.data
817
1092
 
818
1093
 
819
1094
  class IntervalChannelData(ChannelData):
@@ -838,14 +1113,6 @@ class IntervalChannelData(ChannelData):
838
1113
  metadata : :class:`evefile.entities.metadata.IntervalChannelMetadata`
839
1114
  Relevant metadata for the individual device.
840
1115
 
841
- raw_data : Any
842
- The raw individual values measured in the given time interval.
843
-
844
- counts : numpy.ndarray
845
- The number of values measured in the given time interval.
846
-
847
- Note that this value may change for each individual position.
848
-
849
1116
 
850
1117
  Examples
851
1118
  --------
@@ -861,28 +1128,28 @@ class IntervalChannelData(ChannelData):
861
1128
  def __init__(self):
862
1129
  super().__init__()
863
1130
  self.metadata = metadata.IntervalChannelMetadata()
864
- self.raw_data = None
865
- self.counts = np.ndarray(shape=[], dtype=int)
866
- self._mean = None
1131
+ self._counts = None
867
1132
  self._std = None
1133
+ self._data_attributes = ["data", "counts", "std"]
868
1134
 
869
1135
  @property
870
- def mean(self):
1136
+ def counts(self):
871
1137
  """
872
- Mean values for channel data.
1138
+ The number of values measured in the given time interval.
873
1139
 
874
1140
  Returns
875
1141
  -------
876
- mean : :class:`numpy.ndarray`
877
- The mean of the values measured in the given time interval.
1142
+ counts : :class:`numpy.ndarray`
1143
+ Number of values measured in the given time interval
878
1144
 
879
1145
  """
880
- if self._mean is None:
881
- if self.raw_data is not None:
882
- self._mean = self.raw_data.mean(axis=1)
883
- else:
884
- self._mean = self.data
885
- return self._mean
1146
+ if self._counts is None:
1147
+ self.get_data()
1148
+ return self._counts
1149
+
1150
+ @counts.setter
1151
+ def counts(self, counts=None):
1152
+ self._counts = counts
886
1153
 
887
1154
  @property
888
1155
  def std(self):
@@ -891,19 +1158,35 @@ class IntervalChannelData(ChannelData):
891
1158
 
892
1159
  Returns
893
1160
  -------
894
- mean : :class:`numpy.ndarray`
895
- The standard deviation of the values measured in the given
896
- time interval.
1161
+ std : :class:`numpy.ndarray`
1162
+ Standard deviation values for channel data.
897
1163
 
898
1164
  """
899
- if self._std is None and self.raw_data is not None:
900
- self._std = self.raw_data.std(axis=1)
1165
+ if self._std is None:
1166
+ self.get_data()
901
1167
  return self._std
902
1168
 
903
1169
  @std.setter
904
1170
  def std(self, std=None):
905
1171
  self._std = std
906
1172
 
1173
+ @property
1174
+ def mean(self):
1175
+ """
1176
+ Mean values for channel data.
1177
+
1178
+ Returns
1179
+ -------
1180
+ mean : :class:`numpy.ndarray`
1181
+ The mean of the values measured in the given time interval.
1182
+
1183
+ As at least up to eveH5 v7.x only the averaged values are stored
1184
+ in the HDF5 file, this simply returns the values stored
1185
+ in :attr:`data`.
1186
+
1187
+ """
1188
+ return self.data
1189
+
907
1190
 
908
1191
  class NormalizedChannelData:
909
1192
  """
@@ -918,14 +1201,6 @@ class NormalizedChannelData:
918
1201
  metadata : :class:`evefile.entities.metadata.AreaChannelMetadata`
919
1202
  Relevant metadata for normalization.
920
1203
 
921
- normalized_data : Any
922
- Data that have been normalized.
923
-
924
- Normalization takes place by dividing by the values of the
925
- normalizing channel.
926
-
927
- normalizing_data : Any
928
- Data used for normalization.
929
1204
 
930
1205
  Raises
931
1206
  ------
@@ -947,8 +1222,45 @@ class NormalizedChannelData:
947
1222
  def __init__(self):
948
1223
  super().__init__()
949
1224
  self.metadata = metadata.NormalizedChannelMetadata()
950
- self.normalized_data = None
951
- self.normalizing_data = None
1225
+ self._normalized_data = None
1226
+ self._normalizing_data = None
1227
+
1228
+ @property
1229
+ def normalized_data(self):
1230
+ """
1231
+ Data that have been normalized.
1232
+
1233
+ Normalization takes place by dividing by the values of the
1234
+ normalizing channel.
1235
+
1236
+ Returns
1237
+ -------
1238
+ normalized_data : Any
1239
+ Data that have been normalized.
1240
+
1241
+ """
1242
+ return self._normalized_data
1243
+
1244
+ @normalized_data.setter
1245
+ def normalized_data(self, normalized_data=None):
1246
+ self._normalized_data = normalized_data
1247
+
1248
+ @property
1249
+ def normalizing_data(self):
1250
+ """
1251
+ Data used for normalization.
1252
+
1253
+ Returns
1254
+ -------
1255
+ normalized_data : Any
1256
+ Data used for normalization.
1257
+
1258
+ """
1259
+ return self._normalizing_data
1260
+
1261
+ @normalizing_data.setter
1262
+ def normalizing_data(self, normalizing_data=None):
1263
+ self._normalizing_data = normalizing_data
952
1264
 
953
1265
 
954
1266
  class SinglePointNormalizedChannelData(
@@ -990,6 +1302,52 @@ class SinglePointNormalizedChannelData(
990
1302
  def __init__(self):
991
1303
  super().__init__()
992
1304
  self.metadata = metadata.SinglePointNormalizedChannelMetadata()
1305
+ self._data_attributes = [
1306
+ "data",
1307
+ "normalized_data",
1308
+ "normalizing_data",
1309
+ ]
1310
+
1311
+ @property
1312
+ def normalized_data(self):
1313
+ """
1314
+ Data that have been normalized.
1315
+
1316
+ Normalization takes place by dividing by the values of the
1317
+ normalizing channel.
1318
+
1319
+ Returns
1320
+ -------
1321
+ normalized_data : Any
1322
+ Data that have been normalized.
1323
+
1324
+ """
1325
+ if self._normalized_data is None:
1326
+ self.get_data()
1327
+ return self._normalized_data
1328
+
1329
+ @normalized_data.setter
1330
+ def normalized_data(self, normalized_data=None):
1331
+ self._normalized_data = normalized_data
1332
+
1333
+ @property
1334
+ def normalizing_data(self):
1335
+ """
1336
+ Data used for normalization.
1337
+
1338
+ Returns
1339
+ -------
1340
+ normalized_data : Any
1341
+ Data used for normalization.
1342
+
1343
+ """
1344
+ if self._normalizing_data is None:
1345
+ self.get_data()
1346
+ return self._normalizing_data
1347
+
1348
+ @normalizing_data.setter
1349
+ def normalizing_data(self, normalizing_data=None):
1350
+ self._normalizing_data = normalizing_data
993
1351
 
994
1352
 
995
1353
  class AverageNormalizedChannelData(AverageChannelData, NormalizedChannelData):
@@ -1029,6 +1387,53 @@ class AverageNormalizedChannelData(AverageChannelData, NormalizedChannelData):
1029
1387
  def __init__(self):
1030
1388
  super().__init__()
1031
1389
  self.metadata = metadata.AverageNormalizedChannelMetadata()
1390
+ self._data_attributes = [
1391
+ "data",
1392
+ "attempts",
1393
+ "normalized_data",
1394
+ "normalizing_data",
1395
+ ]
1396
+
1397
+ @property
1398
+ def normalized_data(self):
1399
+ """
1400
+ Data that have been normalized.
1401
+
1402
+ Normalization takes place by dividing by the values of the
1403
+ normalizing channel.
1404
+
1405
+ Returns
1406
+ -------
1407
+ normalized_data : Any
1408
+ Data that have been normalized.
1409
+
1410
+ """
1411
+ if self._normalized_data is None:
1412
+ self.get_data()
1413
+ return self._normalized_data
1414
+
1415
+ @normalized_data.setter
1416
+ def normalized_data(self, normalized_data=None):
1417
+ self._normalized_data = normalized_data
1418
+
1419
+ @property
1420
+ def normalizing_data(self):
1421
+ """
1422
+ Data used for normalization.
1423
+
1424
+ Returns
1425
+ -------
1426
+ normalized_data : Any
1427
+ Data used for normalization.
1428
+
1429
+ """
1430
+ if self._normalizing_data is None:
1431
+ self.get_data()
1432
+ return self._normalizing_data
1433
+
1434
+ @normalizing_data.setter
1435
+ def normalizing_data(self, normalizing_data=None):
1436
+ self._normalizing_data = normalizing_data
1032
1437
 
1033
1438
 
1034
1439
  class IntervalNormalizedChannelData(
@@ -1069,6 +1474,54 @@ class IntervalNormalizedChannelData(
1069
1474
  def __init__(self):
1070
1475
  super().__init__()
1071
1476
  self.metadata = metadata.IntervalNormalizedChannelMetadata()
1477
+ self._data_attributes = [
1478
+ "data",
1479
+ "counts",
1480
+ "std",
1481
+ "normalized_data",
1482
+ "normalizing_data",
1483
+ ]
1484
+
1485
+ @property
1486
+ def normalized_data(self):
1487
+ """
1488
+ Data that have been normalized.
1489
+
1490
+ Normalization takes place by dividing by the values of the
1491
+ normalizing channel.
1492
+
1493
+ Returns
1494
+ -------
1495
+ normalized_data : Any
1496
+ Data that have been normalized.
1497
+
1498
+ """
1499
+ if self._normalized_data is None:
1500
+ self.get_data()
1501
+ return self._normalized_data
1502
+
1503
+ @normalized_data.setter
1504
+ def normalized_data(self, normalized_data=None):
1505
+ self._normalized_data = normalized_data
1506
+
1507
+ @property
1508
+ def normalizing_data(self):
1509
+ """
1510
+ Data used for normalization.
1511
+
1512
+ Returns
1513
+ -------
1514
+ normalized_data : Any
1515
+ Data used for normalization.
1516
+
1517
+ """
1518
+ if self._normalizing_data is None:
1519
+ self.get_data()
1520
+ return self._normalizing_data
1521
+
1522
+ @normalizing_data.setter
1523
+ def normalizing_data(self, normalizing_data=None):
1524
+ self._normalizing_data = normalizing_data
1072
1525
 
1073
1526
 
1074
1527
  class DataImporter:
@@ -125,6 +125,37 @@ class Metadata:
125
125
  super().__init__()
126
126
  self.name = ""
127
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)
128
159
 
129
160
  def copy_attributes_from(self, source=None):
130
161
  """
@@ -223,6 +254,10 @@ class MonitorMetadata(Metadata, AbstractDeviceMetadata):
223
254
 
224
255
  """
225
256
 
257
+ def __init__(self):
258
+ super().__init__()
259
+ self._attributes.extend(["id", "pv", "access_mode"])
260
+
226
261
 
227
262
  class MeasureMetadata(Metadata):
228
263
  """
@@ -252,6 +287,7 @@ class MeasureMetadata(Metadata):
252
287
  def __init__(self):
253
288
  super().__init__()
254
289
  self.unit = ""
290
+ self._attributes.append("unit")
255
291
 
256
292
 
257
293
  class DeviceMetadata(MeasureMetadata, AbstractDeviceMetadata):
@@ -273,6 +309,10 @@ class DeviceMetadata(MeasureMetadata, AbstractDeviceMetadata):
273
309
 
274
310
  """
275
311
 
312
+ def __init__(self):
313
+ super().__init__()
314
+ self._attributes.extend(["id", "pv", "access_mode"])
315
+
276
316
 
277
317
  class AxisMetadata(MeasureMetadata, AbstractDeviceMetadata):
278
318
  """
@@ -295,6 +335,7 @@ class AxisMetadata(MeasureMetadata, AbstractDeviceMetadata):
295
335
  def __init__(self):
296
336
  super().__init__()
297
337
  self.deadband = 0.0
338
+ self._attributes.extend(["id", "pv", "access_mode", "deadband"])
298
339
 
299
340
 
300
341
  class ChannelMetadata(MeasureMetadata, AbstractDeviceMetadata):
@@ -316,6 +357,10 @@ class ChannelMetadata(MeasureMetadata, AbstractDeviceMetadata):
316
357
 
317
358
  """
318
359
 
360
+ def __init__(self):
361
+ super().__init__()
362
+ self._attributes.extend(["id", "pv", "access_mode"])
363
+
319
364
 
320
365
  class TimestampMetadata(MeasureMetadata):
321
366
  """
@@ -407,6 +452,9 @@ class AverageChannelMetadata(ChannelMetadata):
407
452
  self.low_limit = 0.0
408
453
  self.max_attempts = 0
409
454
  self.max_deviation = 0.0
455
+ self._attributes.extend(
456
+ ["n_averages", "low_limit", "max_attempts", "max_deviation"]
457
+ )
410
458
 
411
459
 
412
460
  class IntervalChannelMetadata(ChannelMetadata):
@@ -437,6 +485,7 @@ class IntervalChannelMetadata(ChannelMetadata):
437
485
  def __init__(self):
438
486
  super().__init__()
439
487
  self.trigger_interval = 0.0
488
+ self._attributes.append("trigger_interval")
440
489
 
441
490
 
442
491
  class NormalizedChannelMetadata:
@@ -486,6 +535,10 @@ class SinglePointNormalizedChannelMetadata(
486
535
 
487
536
  """
488
537
 
538
+ def __init__(self):
539
+ super().__init__()
540
+ self._attributes.extend(["normalize_id"])
541
+
489
542
 
490
543
  class AverageNormalizedChannelMetadata(
491
544
  ChannelMetadata, NormalizedChannelMetadata
@@ -508,6 +561,10 @@ class AverageNormalizedChannelMetadata(
508
561
 
509
562
  """
510
563
 
564
+ def __init__(self):
565
+ super().__init__()
566
+ self._attributes.extend(["normalize_id"])
567
+
511
568
 
512
569
  class IntervalNormalizedChannelMetadata(
513
570
  ChannelMetadata, NormalizedChannelMetadata
@@ -529,3 +586,7 @@ class IntervalNormalizedChannelMetadata(
529
586
  you can instantiate an object as usual.
530
587
 
531
588
  """
589
+
590
+ def __init__(self):
591
+ super().__init__()
592
+ self._attributes.extend(["normalize_id"])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evefile
3
- Version: 0.1.0rc1
3
+ Version: 0.1.0rc2
4
4
  Summary: Transitional package to read eveH5 files containing synchrotron radiometry data recorded at BESSY/MLS in Berlin
5
5
  Home-page: https://www.ptb.de/cms/en/ptb/fachabteilungen/abt7/ptb-sr.html
6
6
  Author: Till Biskup
@@ -53,6 +53,11 @@ Dynamic: requires-dist
53
53
  Dynamic: requires-python
54
54
  Dynamic: summary
55
55
 
56
+
57
+ .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.16815768.svg
58
+ :target: https://doi.org/10.5281/zenodo.16815768
59
+ :align: right
60
+
56
61
  =======
57
62
  evefile
58
63
  =======
@@ -1,30 +1,30 @@
1
1
  evefile/__init__.py,sha256=DtUfummsLsWO7A-xiqL1hUEBll8Qc1z7829P9Sb0kOk,215
2
2
  evefile/__pycache__/__init__.cpython-311.pyc,sha256=RkdWBYZ3wcT04KJ8H7KPKwhPhMc-GV9vbGSuOIOtqjE,398
3
3
  evefile/boundaries/__init__.py,sha256=OKIxX1YeMY3Bda2g27xm-uQwH191EL6qLIHoPIoz_88,64
4
- evefile/boundaries/evefile.py,sha256=4ZfSrpSx6Bb_1d5Y-nQ4sOOk0MJ0tJSHnj1d_gPzr2E,18184
4
+ evefile/boundaries/evefile.py,sha256=uVptvR2-1bvCjByxi4JNO45VkMyt7t6pYBwRa5vjO9w,18186
5
5
  evefile/boundaries/eveh5.py,sha256=u-aEXXBoQYCfKb7l7w1x2VVKlQ2mT5S8gMdLiHZteYU,29750
6
6
  evefile/boundaries/__pycache__/__init__.cpython-311.pyc,sha256=GnLdTDzJRRVDQg_l9nctkGQs0GU8oILd3Lg1XY7kKhg,251
7
- evefile/boundaries/__pycache__/evefile.cpython-311.pyc,sha256=YKFIUnzmKCmT-7bAxK2wq7DfP9LZ0kAXHF6JdNuXIJg,22018
7
+ evefile/boundaries/__pycache__/evefile.cpython-311.pyc,sha256=n0oPx3nUc5SMzKGNektf1o4YBIuWllxQma9jd0bLKlE,22020
8
8
  evefile/boundaries/__pycache__/eveh5.cpython-311.pyc,sha256=IpaPRhZkmdR-z6x257TujkwRgAeyuhPwAn7NnvslNJM,35831
9
9
  evefile/controllers/__init__.py,sha256=K4ysYHyf7p5dFDIppJyx8-ydZpLfwvjoikhUcOapKuQ,147
10
- evefile/controllers/joining.py,sha256=Qsvv9o3l6l1PFsgroJKiUU24gMvJeRn1RT05tsflsYs,43986
10
+ evefile/controllers/joining.py,sha256=o0GI6bBxtAU5uZkXoZ6JhgerZ_fgD3Y6IaoYt4UjSJI,42173
11
11
  evefile/controllers/timestamp_mapping.py,sha256=_plmzVtY_CaVp3wYy1VhfGenRwWLRJiIA6yw398r_Ec,4686
12
12
  evefile/controllers/version_mapping.py,sha256=zplXv7lKvvzd0NI1pMpavVFCdrkauXBnT_uvXTJu8PQ,41958
13
13
  evefile/controllers/__pycache__/__init__.cpython-311.pyc,sha256=OAlQR88n3ns13ecANQdSyPZ4w0F350NhCicFxjMAJmw,356
14
- evefile/controllers/__pycache__/joining.cpython-311.pyc,sha256=QLLjKLNI7k5zjtHY0QWcCOVSOHIht24jVbV1sP2wH1g,50194
14
+ evefile/controllers/__pycache__/joining.cpython-311.pyc,sha256=si4e2UAuNQrQ6fGp6vXf_a7MUimLLeKDDh148OUzwPo,48080
15
15
  evefile/controllers/__pycache__/timestamp_mapping.cpython-311.pyc,sha256=yDqvWtdJg9FfAoT3vSQ2jI9tuIslMZxGTNjCOXtPpRs,4898
16
16
  evefile/controllers/__pycache__/version_mapping.cpython-311.pyc,sha256=rQ-M_O5po_E3bwjbG200AdZBT_g8N0WShDX-BFpYKl4,52416
17
17
  evefile/entities/__init__.py,sha256=VfS-6fdCiqlYobw7gcCMcfdm8Fct_6AnSPw99JHV2bo,170
18
- evefile/entities/data.py,sha256=UAW-N8naWSMC_HMBVEESq120ZcDiHVWFTj3ucY0RwNY,40761
18
+ evefile/entities/data.py,sha256=w6XMPqCtYqPGcRGmmZsWbOfT2WHsevc82Ws5uMB3tjc,54930
19
19
  evefile/entities/file.py,sha256=grOHe2ONwieh58imbEq1ahLZyXooardctNanvcuWOJs,9816
20
- evefile/entities/metadata.py,sha256=tXMG8vyzDT-0Zk1UQJPRXIqzeTa984pM7zOUo7UP8HU,16100
20
+ evefile/entities/metadata.py,sha256=DVgLWM3bob2JWh_wpf6jGJ6LnIRsErB_AJmOx-u_P3E,18111
21
21
  evefile/entities/__pycache__/__init__.cpython-311.pyc,sha256=Rc8OF9IeRYpJbN7Qw5AFn5qPxm7g_carXQKqnuZUHz4,380
22
- evefile/entities/__pycache__/data.cpython-311.pyc,sha256=xKsTEBQ9K7RfeKkaqp71ShmYMXJFem_mVQB7ky6MPM8,52458
22
+ evefile/entities/__pycache__/data.cpython-311.pyc,sha256=B_mzzKZ6uqGufF52z9I4jM8eIIMEEUF-zIVdSDEzS24,68181
23
23
  evefile/entities/__pycache__/file.cpython-311.pyc,sha256=scDx1m5UN-HcCqAArH3Gzw-tSjE0fWSiiTzR8xTSlfg,11764
24
- evefile/entities/__pycache__/metadata.cpython-311.pyc,sha256=6opdeMMvA3Jll8NhfBwMAj7uvc8nj7PVahYVQjynrYk,20736
25
- evefile-0.1.0rc1.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
26
- evefile-0.1.0rc1.dist-info/licenses/LICENSE,sha256=VYLxbjlmThdMWJAgeCXSLlcHUDR0kk0C40P92FH3o3M,766
27
- evefile-0.1.0rc1.dist-info/METADATA,sha256=NIfIY61ON08Ehm5NtyfjP383yGGvONvrw0GQyItkono,4936
28
- evefile-0.1.0rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
- evefile-0.1.0rc1.dist-info/top_level.txt,sha256=AiXT2Ch6ZWznVmfIXckmjoPXMfucyXMj4SP_46hKN7s,8
30
- evefile-0.1.0rc1.dist-info/RECORD,,
24
+ evefile/entities/__pycache__/metadata.cpython-311.pyc,sha256=AMbmJ_dJbM6fzzMw124sH6CYvUEaYNeV0vTn1CHEJEQ,24716
25
+ evefile-0.1.0rc2.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
26
+ evefile-0.1.0rc2.dist-info/licenses/LICENSE,sha256=VYLxbjlmThdMWJAgeCXSLlcHUDR0kk0C40P92FH3o3M,766
27
+ evefile-0.1.0rc2.dist-info/METADATA,sha256=SLm_dKzLTvD7OxUUzKT9yzUOLxOr1_o9RaN1Wyfs5qQ,5075
28
+ evefile-0.1.0rc2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ evefile-0.1.0rc2.dist-info/top_level.txt,sha256=AiXT2Ch6ZWznVmfIXckmjoPXMfucyXMj4SP_46hKN7s,8
30
+ evefile-0.1.0rc2.dist-info/RECORD,,