evefile 0.1.0rc1__tar.gz → 0.2.0__tar.gz

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.
Files changed (58) hide show
  1. {evefile-0.1.0rc1/evefile.egg-info → evefile-0.2.0}/PKG-INFO +22 -3
  2. {evefile-0.1.0rc1 → evefile-0.2.0}/README.rst +21 -2
  3. evefile-0.2.0/VERSION +1 -0
  4. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/boundaries/__pycache__/evefile.cpython-311.pyc +0 -0
  5. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/boundaries/evefile.py +184 -9
  6. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/controllers/__pycache__/joining.cpython-311.pyc +0 -0
  7. evefile-0.2.0/evefile/controllers/__pycache__/timestamp_mapping.cpython-311.pyc +0 -0
  8. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/controllers/__pycache__/version_mapping.cpython-311.pyc +0 -0
  9. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/controllers/joining.py +68 -111
  10. evefile-0.2.0/evefile/controllers/timestamp_mapping.py +187 -0
  11. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/controllers/version_mapping.py +181 -1
  12. evefile-0.2.0/evefile/entities/__pycache__/data.cpython-311.pyc +0 -0
  13. evefile-0.2.0/evefile/entities/__pycache__/metadata.cpython-311.pyc +0 -0
  14. evefile-0.2.0/evefile/entities/data.py +2398 -0
  15. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/entities/metadata.py +264 -0
  16. {evefile-0.1.0rc1 → evefile-0.2.0/evefile.egg-info}/PKG-INFO +22 -3
  17. {evefile-0.1.0rc1 → evefile-0.2.0}/tests/boundaries/test_evefile.py +169 -2
  18. {evefile-0.1.0rc1 → evefile-0.2.0}/tests/controllers/test_joining.py +125 -120
  19. evefile-0.2.0/tests/controllers/test_timestamp_mapping.py +109 -0
  20. {evefile-0.1.0rc1 → evefile-0.2.0}/tests/controllers/test_version_mapping.py +308 -0
  21. evefile-0.2.0/tests/entities/test_data.py +1652 -0
  22. evefile-0.2.0/tests/entities/test_metadata.py +672 -0
  23. evefile-0.1.0rc1/VERSION +0 -1
  24. evefile-0.1.0rc1/evefile/controllers/__pycache__/timestamp_mapping.cpython-311.pyc +0 -0
  25. evefile-0.1.0rc1/evefile/controllers/timestamp_mapping.py +0 -101
  26. evefile-0.1.0rc1/evefile/entities/__pycache__/data.cpython-311.pyc +0 -0
  27. evefile-0.1.0rc1/evefile/entities/__pycache__/metadata.cpython-311.pyc +0 -0
  28. evefile-0.1.0rc1/evefile/entities/data.py +0 -1311
  29. evefile-0.1.0rc1/tests/controllers/test_timestamp_mapping.py +0 -3
  30. evefile-0.1.0rc1/tests/entities/test_data.py +0 -800
  31. evefile-0.1.0rc1/tests/entities/test_metadata.py +0 -351
  32. {evefile-0.1.0rc1 → evefile-0.2.0}/COPYING +0 -0
  33. {evefile-0.1.0rc1 → evefile-0.2.0}/LICENSE +0 -0
  34. {evefile-0.1.0rc1 → evefile-0.2.0}/MANIFEST.in +0 -0
  35. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/__init__.py +0 -0
  36. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/__pycache__/__init__.cpython-311.pyc +0 -0
  37. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/boundaries/__init__.py +0 -0
  38. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/boundaries/__pycache__/__init__.cpython-311.pyc +0 -0
  39. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/boundaries/__pycache__/eveh5.cpython-311.pyc +0 -0
  40. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/boundaries/eveh5.py +0 -0
  41. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/controllers/__init__.py +0 -0
  42. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/controllers/__pycache__/__init__.cpython-311.pyc +0 -0
  43. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/entities/__init__.py +0 -0
  44. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/entities/__pycache__/__init__.cpython-311.pyc +0 -0
  45. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/entities/__pycache__/file.cpython-311.pyc +0 -0
  46. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile/entities/file.py +0 -0
  47. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile.egg-info/SOURCES.txt +0 -0
  48. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile.egg-info/dependency_links.txt +0 -0
  49. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile.egg-info/requires.txt +0 -0
  50. {evefile-0.1.0rc1 → evefile-0.2.0}/evefile.egg-info/top_level.txt +0 -0
  51. {evefile-0.1.0rc1 → evefile-0.2.0}/pyproject.toml +0 -0
  52. {evefile-0.1.0rc1 → evefile-0.2.0}/setup.cfg +0 -0
  53. {evefile-0.1.0rc1 → evefile-0.2.0}/setup.py +0 -0
  54. {evefile-0.1.0rc1 → evefile-0.2.0}/tests/boundaries/__init__.py +0 -0
  55. {evefile-0.1.0rc1 → evefile-0.2.0}/tests/boundaries/test_eveh5.py +0 -0
  56. {evefile-0.1.0rc1 → evefile-0.2.0}/tests/controllers/__init__.py +0 -0
  57. {evefile-0.1.0rc1 → evefile-0.2.0}/tests/entities/__init__.py +0 -0
  58. {evefile-0.1.0rc1 → evefile-0.2.0}/tests/entities/test_file.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evefile
3
- Version: 0.1.0rc1
3
+ Version: 0.2.0
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,24 +53,43 @@ 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
  =======
59
64
 
60
65
  *Transitional package to read eveH5 files containing synchrotron radiometry data recorded at BESSY/MLS in Berlin.*
61
66
 
62
- Welcome! This is evefile, a Python package for **importing (synchrotron) radiometry data** obtained at one of the beamlines at **BESSY-II or MLS in Berlin**, mostly operated by the German National Metrology Institute, the `Physikalisch-Technische Bundesanstalt (PTB) <https://www.ptb.de/>`_. This package acts as transitional interface between the (eveH5) data files and the processing and analysis code. For related packages for importing, viewing, and analysing those data, have a look at the "related projects" section below.
67
+ Welcome! This is evefile, a Python package for **importing (synchrotron) radiometry data** obtained at one of the beamlines at **BESSY-II or MLS in Berlin**, mostly operated by the German National Metrology Institute, the `Physikalisch-Technische Bundesanstalt (PTB) <https://www.ptb.de/>`_. This package acts as *transitional* interface between the (eveH5) data files and the processing and analysis code. For related packages for importing, viewing, and analysing those data, have a look at the "related projects" section below.
68
+
69
+ Loading the contents of a data file of a measurement is as simple as::
70
+
71
+ import evefile
72
+
73
+ file = evefile.EveFile(filename="my_measurement_file.h5")
74
+
75
+ Here, ``file`` contains all the information contained in the data file as a hierarchy of Python objects.
63
76
 
64
77
 
65
78
  Features
66
79
  ========
67
80
 
68
- A list of (planned) features:
81
+ A list of features:
69
82
 
70
83
  * Importer for eve HDF5 files (used at PTB in Berlin, Germany)
71
84
 
72
85
  * Fully backwards-compatible to older eveH5 versions
73
86
 
87
+ * Complete information available that is contained in an eveH5 file
88
+
89
+ * Data are (only) loaded on demand, not when loading the file
90
+
91
+ * Powerful and intuitive abstractions, allowing for associative access to data and information – beyond a purely tabular view of the data
92
+
74
93
 
75
94
  And to make it even more convenient for users and future-proof:
76
95
 
@@ -1,21 +1,40 @@
1
+
2
+ .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.16815768.svg
3
+ :target: https://doi.org/10.5281/zenodo.16815768
4
+ :align: right
5
+
1
6
  =======
2
7
  evefile
3
8
  =======
4
9
 
5
10
  *Transitional package to read eveH5 files containing synchrotron radiometry data recorded at BESSY/MLS in Berlin.*
6
11
 
7
- Welcome! This is evefile, a Python package for **importing (synchrotron) radiometry data** obtained at one of the beamlines at **BESSY-II or MLS in Berlin**, mostly operated by the German National Metrology Institute, the `Physikalisch-Technische Bundesanstalt (PTB) <https://www.ptb.de/>`_. This package acts as transitional interface between the (eveH5) data files and the processing and analysis code. For related packages for importing, viewing, and analysing those data, have a look at the "related projects" section below.
12
+ Welcome! This is evefile, a Python package for **importing (synchrotron) radiometry data** obtained at one of the beamlines at **BESSY-II or MLS in Berlin**, mostly operated by the German National Metrology Institute, the `Physikalisch-Technische Bundesanstalt (PTB) <https://www.ptb.de/>`_. This package acts as *transitional* interface between the (eveH5) data files and the processing and analysis code. For related packages for importing, viewing, and analysing those data, have a look at the "related projects" section below.
13
+
14
+ Loading the contents of a data file of a measurement is as simple as::
15
+
16
+ import evefile
17
+
18
+ file = evefile.EveFile(filename="my_measurement_file.h5")
19
+
20
+ Here, ``file`` contains all the information contained in the data file as a hierarchy of Python objects.
8
21
 
9
22
 
10
23
  Features
11
24
  ========
12
25
 
13
- A list of (planned) features:
26
+ A list of features:
14
27
 
15
28
  * Importer for eve HDF5 files (used at PTB in Berlin, Germany)
16
29
 
17
30
  * Fully backwards-compatible to older eveH5 versions
18
31
 
32
+ * Complete information available that is contained in an eveH5 file
33
+
34
+ * Data are (only) loaded on demand, not when loading the file
35
+
36
+ * Powerful and intuitive abstractions, allowing for associative access to data and information – beyond a purely tabular view of the data
37
+
19
38
 
20
39
  And to make it even more convenient for users and future-proof:
21
40
 
evefile-0.2.0/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -146,9 +146,10 @@ import os
146
146
 
147
147
  import pandas as pd
148
148
 
149
+ import evefile.entities.data
149
150
  from evefile.entities.file import File
150
151
  from evefile.boundaries.eveh5 import HDF5File
151
- from evefile.controllers import version_mapping, joining
152
+ from evefile.controllers import version_mapping, joining, timestamp_mapping
152
153
 
153
154
 
154
155
  logger = logging.getLogger(__name__)
@@ -221,6 +222,11 @@ class EveFile(File):
221
222
  The keys of the dictionary are the (guaranteed to be unique) HDF
222
223
  dataset names, not the "given" names usually familiar to the users.
223
224
 
225
+ **Please note:** For monitors, in contrast to data stored in the
226
+ :attr:`data` attribute, names are *not unique*. Hence, the only way to
227
+ address an individual monitor unequivocally is by its ID that is
228
+ identical to the HDF dataset name.
229
+
224
230
  Each item is an instance of
225
231
  :class:`evefile.entities.data.MonitorData`.
226
232
 
@@ -261,7 +267,8 @@ class EveFile(File):
261
267
  def __init__(self, filename="", load=True):
262
268
  super().__init__()
263
269
  self.filename = filename
264
- self._join_factory = joining.JoinFactory(evefile=self)
270
+ self._join_factory = joining.JoinFactory(file=self)
271
+ self._monitor_mapper = timestamp_mapping.Mapper(file=self)
265
272
  if load:
266
273
  if not filename:
267
274
  raise ValueError("No filename given")
@@ -387,14 +394,24 @@ class EveFile(File):
387
394
  if self.metadata.preferred_axis:
388
395
  output[0] = self.data[self.metadata.preferred_axis]
389
396
  if self.metadata.preferred_channel:
390
- output[1] = self.data[self.metadata.preferred_channel]
397
+ if self.metadata.preferred_channel in self.data:
398
+ output[1] = self.data[self.metadata.preferred_channel]
399
+ else:
400
+ logger.warning(
401
+ "Dataset {} not found, possibly an "
402
+ "attribute or option?".format(
403
+ self.metadata.preferred_channel
404
+ )
405
+ )
391
406
  if self.metadata.preferred_normalisation_channel:
392
407
  output[2] = self.data[
393
408
  self.metadata.preferred_normalisation_channel
394
409
  ]
395
410
  return output
396
411
 
397
- def get_joined_data(self, data=None, mode="AxisOrChannelPositions"):
412
+ def get_joined_data(
413
+ self, data=None, mode="AxisOrChannelPositions", include_monitors=False
414
+ ):
398
415
  """
399
416
  Retrieve data objects with commensurate dimensions.
400
417
 
@@ -420,6 +437,15 @@ class EveFile(File):
420
437
 
421
438
  Default: "AxisOrChannelPositions"
422
439
 
440
+ include_monitors : :class:`bool`
441
+ Whether to include (all) monitors in joined data.
442
+
443
+ If set to :obj:`True`, all monitors available will automatically
444
+ be mapped to :obj:`DeviceData <evefile.entities.data.DeviceData>`
445
+ objects and included in the list of joined data.
446
+
447
+ Default: :obj:`False`
448
+
423
449
  Returns
424
450
  -------
425
451
  data : :class:`list`
@@ -431,10 +457,37 @@ class EveFile(File):
431
457
  """
432
458
  if not data:
433
459
  data = list(self.data.values())
460
+ data = [
461
+ (
462
+ self._convert_str_to_data_object(item)
463
+ if isinstance(item, str)
464
+ else item
465
+ )
466
+ for item in data
467
+ ]
468
+ if include_monitors:
469
+ monitors = self.get_monitors()
470
+ if isinstance(monitors, list):
471
+ data.extend(monitors)
472
+ else:
473
+ data.append(monitors)
434
474
  joiner = self._join_factory.get_join(mode=mode)
435
475
  return joiner.join(data)
436
476
 
437
- def get_dataframe(self, data=None, mode="AxisOrChannelPositions"):
477
+ def _convert_str_to_data_object(self, name_or_id=""):
478
+ try:
479
+ result = self.data[name_or_id]
480
+ except KeyError:
481
+ try:
482
+ result = self.get_data(name_or_id)
483
+ except KeyError:
484
+ # Valid situation: monitor
485
+ result = self.get_monitors(name_or_id)
486
+ return result
487
+
488
+ def get_dataframe(
489
+ self, data=None, mode="AxisOrChannelPositions", include_monitors=False
490
+ ):
438
491
  """
439
492
  Retrieve Pandas DataFrame with given data objects as columns.
440
493
 
@@ -445,6 +498,18 @@ class EveFile(File):
445
498
  The names of the columns of the returned DataFrame are the names (not
446
499
  IDs) of the respective datasets.
447
500
 
501
+ .. note::
502
+
503
+ At least for the time being, for each data object involved only
504
+ the ``data`` attribute will be contained in the returned
505
+ DataFrame as a column. This is of particular importance for more
506
+ complicated data types, such as :class:`NormalizedChannelData
507
+ <evefile.entities.data.NormalizedChannelData>`,
508
+ :class:`AverageChannelData
509
+ <evefile.entities.data.AverageChannelData>`,
510
+ and :class:`IntervalChannelData
511
+ <evefile.entities.data.IntervalChannelData>`.
512
+
448
513
  .. important::
449
514
 
450
515
  While working with a Pandas DataFrame may seem convenient,
@@ -457,12 +522,12 @@ class EveFile(File):
457
522
  Parameters
458
523
  ----------
459
524
  data : :class:`list`
460
- (Names/IDs of) data objects whose data should be joined.
525
+ (Names/IDs of) data objects whose data should be included.
461
526
 
462
527
  You can provide either names or IDs or the actual data objects.
463
528
 
464
529
  If no data are given, by default all data available will be
465
- joined.
530
+ included.
466
531
 
467
532
  Default: :obj:`None`
468
533
 
@@ -473,6 +538,19 @@ class EveFile(File):
473
538
 
474
539
  Default: "AxisOrChannelPositions"
475
540
 
541
+ include_monitors : :class:`bool`
542
+ Whether to include (all) monitors in the dataframe.
543
+
544
+ If set to :obj:`True`, all monitors available will automatically
545
+ be mapped to :obj:`DeviceData <evefile.entities.data.DeviceData>`
546
+ objects and included in the dataframe.
547
+
548
+ Note that for mapped monitors, their IDs are used as column
549
+ names rather than the "given" names, as for monitors, names are
550
+ *not unique*.
551
+
552
+ Default: :obj:`False`
553
+
476
554
  Returns
477
555
  -------
478
556
  dataframe : :class:`pandas.DataFrame`
@@ -484,11 +562,18 @@ class EveFile(File):
484
562
  """
485
563
  if not data:
486
564
  data = list(self.data.values())
487
- joined_data = self.get_joined_data(data=data, mode=mode)
565
+ joined_data = self.get_joined_data(
566
+ data=data, mode=mode, include_monitors=include_monitors
567
+ )
568
+ for item in joined_data:
569
+ # Ensure IDs are used for monitors, as names are not unique
570
+ if isinstance(item, evefile.entities.data.DeviceData):
571
+ item.metadata.name = item.metadata.id
488
572
  dataframe = pd.DataFrame(
489
573
  {item.metadata.name: item.data for item in joined_data}
490
574
  )
491
- dataframe.index.name = "PosRef"
575
+ dataframe.index = joined_data[0].position_counts
576
+ dataframe.index.name = "position"
492
577
  return dataframe
493
578
 
494
579
  def show_info(self):
@@ -552,3 +637,93 @@ class EveFile(File):
552
637
  print("\nMONITORS")
553
638
  for item in self.monitors.values():
554
639
  print(item)
640
+
641
+ def get_monitors(self, monitors=None):
642
+ """
643
+ Retrieve monitors as mapped device data objects by ID.
644
+
645
+ While you can get the original monitor data objects by accessing the
646
+ :attr:`monitors <evefile.entities.file.File.monitors>` attribute
647
+ directly, there, they are stored as
648
+ :obj:`MonitorData <evefile.entities.data.MonitorData>` objects with
649
+ timestamps instead of position counts. To relate monitors to the
650
+ measured data, the former need to be mapped to position counts.
651
+
652
+ .. note::
653
+
654
+ Monitors, in contrast to data objects stored in the
655
+ :attr:`data <evefile.entities.file.File.data>` attribute,
656
+ have *no unique names*. Hence, the only way to unequivocally
657
+ access a given monitor (or the respective, mapped
658
+ :obj:`DeviceData <evefile.entities.data.DeviceData>` object) is
659
+ by means of its unique ID.
660
+
661
+
662
+ Parameters
663
+ ----------
664
+ monitors : :class:`str` | :class:`list`
665
+ Name or list of names of monitors to retrieve
666
+
667
+ Returns
668
+ -------
669
+ device_data : :class:`evefile.entities.data.Data` | :class:`list`
670
+ Device data object(s) corresponding to the ID(s).
671
+
672
+ In case of a list of data objects, each object is of type
673
+ :class:`evefile.entities.data.DeviceData`.
674
+
675
+ """
676
+ device_data = []
677
+ if not monitors:
678
+ monitors = list(self.monitors)
679
+ if isinstance(monitors, (list, tuple)):
680
+ for item in monitors:
681
+ device_data.append(self._monitor_mapper.map(item))
682
+ else:
683
+ device_data.append(self._monitor_mapper.map(monitors))
684
+ if len(device_data) == 1:
685
+ device_data = device_data[0]
686
+ return device_data
687
+
688
+ def get_snapshots(self):
689
+ """
690
+ Retrieve Pandas DataFrame with snapshots as rows.
691
+
692
+ Snapshots serve generally two functions:
693
+
694
+ #. Provide base values for axes.
695
+
696
+ In case of joining data using :meth:`get_joined_data`, for axes,
697
+ typically the previous values are used for positions no axes
698
+ values have been recorded. Snapshots are used if available.
699
+
700
+ #. Provide telemetry data for the setup the data were recorded with.
701
+
702
+ Snapshots regularly contain many more parameters than motor axes
703
+ used and detector channels recorded. Generally, this provides a
704
+ lot of telemetry data regarding the setup used for recording the
705
+ data.
706
+
707
+ The first function is served by the :meth:`get_joined_data` method
708
+ automatically. The second function can be served by having a look at
709
+ a summary containing all snapshot data. This is the aim of this
710
+ method: returning a Pandas DataFrame containing all snapshots as
711
+ rows and the position counts as columns.
712
+
713
+
714
+ Returns
715
+ -------
716
+ snapshot_dataframe : :class:`pandas.DataFrame`
717
+ Pandas DataFrame containing all snapshots as rows.
718
+
719
+ The indices (names of the rows) are the names (not IDs) of the
720
+ respective snapshot datasets.
721
+
722
+ """
723
+ snapshot_dataframes = []
724
+ for snapshot in self.snapshots.values():
725
+ data = dict(zip(snapshot.position_counts, snapshot.data))
726
+ snapshot_dataframes.append(
727
+ pd.DataFrame(data=data, index=[snapshot.metadata.name])
728
+ )
729
+ return pd.concat(snapshot_dataframes)