backscattermeter_analyzer 2.0.0__tar.gz → 2.1.1__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 (71) hide show
  1. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/PKG-INFO +1 -1
  2. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/pyproject.toml +1 -1
  3. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/__init__.py +25 -0
  4. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/characterize/__init__.py +9 -0
  5. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/__init__.py +15 -0
  6. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/hdf5/__init__.py +7 -0
  7. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/hdf5/measurement.py +156 -0
  8. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/hdf5/utils.py +322 -0
  9. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/registry/__init__.py +16 -0
  10. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/registry/default.py +5 -0
  11. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/registry/interface/__init__.py +9 -0
  12. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/registry/interface/unified_read_write.py +108 -0
  13. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/registry/interface/unified_read_write_method.py +27 -0
  14. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/registry/unified_input_registry.py +226 -0
  15. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/registry/unified_io_registry.py +43 -0
  16. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/registry/unified_io_registry_base.py +586 -0
  17. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/io/registry/unified_output_registry.py +183 -0
  18. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/scale.py +10 -0
  19. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/__init__.py +4 -0
  20. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/utils/measurement/connect.py +50 -0
  21. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/utils/measurement/measurement.py +137 -0
  22. backscattermeter_analyzer-2.1.1/src/backscattermeter_analyzer/utils/utils.py +124 -0
  23. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/__init__.py +0 -9
  24. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/characterize/__init__.py +0 -1
  25. backscattermeter_analyzer-2.0.0/src/backscattermeter_analyzer/utils/measurement/measurement.py +0 -34
  26. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/LICENSE +0 -0
  27. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/README.md +0 -0
  28. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/display.py +0 -0
  29. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/justfile +0 -0
  30. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/mise.toml +0 -0
  31. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/axis.py +0 -0
  32. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/characterize/angle/__init__.py +0 -0
  33. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/characterize/angle/result.py +0 -0
  34. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/characterize/distance/__init__.py +0 -0
  35. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/characterize/distance/distance.py +0 -0
  36. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/characterize/distance/measurement.py +0 -0
  37. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/characterize/distance/ratio.py +0 -0
  38. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/characterize/distance/result.py +0 -0
  39. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/characterize/distance/utils.py +0 -0
  40. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/characterize/distance/vector.py +0 -0
  41. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/direction.py +0 -0
  42. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/hardware/__init__.py +0 -0
  43. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/hardware/backscattermeter.py +0 -0
  44. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/hardware/camera/__init__.py +0 -0
  45. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/hardware/motor/__init__.py +0 -0
  46. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/hardware/motor/jump.py +0 -0
  47. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/hardware/motor/path.py +0 -0
  48. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/hardware/motor/step_numbers.py +0 -0
  49. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/hardware/optic/__init__.py +0 -0
  50. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/distance/__init__.py +0 -0
  51. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/distance/distance.py +0 -0
  52. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/distance/motor/__init__.py +0 -0
  53. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/distance/motor/no.py +0 -0
  54. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/distance/motor/state.py +0 -0
  55. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/distance/motor/yes.py +0 -0
  56. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/distance/state.py +0 -0
  57. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/__init__.py +0 -0
  58. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/angle/__init__.py +0 -0
  59. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/angle/no.py +0 -0
  60. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/angle/state.py +0 -0
  61. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/angle/yes.py +0 -0
  62. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/camera/__init__.py +0 -0
  63. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/camera/no.py +0 -0
  64. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/camera/state.py +0 -0
  65. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/camera/yes.py +0 -0
  66. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/motor/__init__.py +0 -0
  67. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/motor/no.py +0 -0
  68. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/motor/state.py +0 -0
  69. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/motor/yes.py +0 -0
  70. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/src/backscattermeter_analyzer/utils/measurement/state.py +0 -0
  71. {backscattermeter_analyzer-2.0.0 → backscattermeter_analyzer-2.1.1}/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: backscattermeter_analyzer
3
- Version: 2.0.0
3
+ Version: 2.1.1
4
4
  Summary: Tool to work on the backscattermeter at LAPP
5
5
  Project-URL: Documentation, https://git.linarphy.net/linarphy/backscattermeter_analyzer/wiki
6
6
  Project-URL: Homepage, https://git.linarphy.net/linarphy/backscattermeter_analyzer
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "backscattermeter_analyzer"
3
- version = "2.0.0"
3
+ version = "2.1.1"
4
4
  description = "Tool to work on the backscattermeter at LAPP"
5
5
  authors = [{ name = "linarphy", email = "linarphy@linarphy.net" }]
6
6
  readme = "README.md"
@@ -0,0 +1,25 @@
1
+ """Entry-point package."""
2
+
3
+ from .axis import Axis # noqa: I001
4
+ from .direction import Direction
5
+
6
+ from .hardware import Backscattermeter, Camera, MotorJump, MotorPath
7
+ from .io import register_measurement_hdf5
8
+
9
+ from .characterize import DistanceRatio, DistanceResult
10
+ from .utils import Distance, Measurement
11
+
12
+ register_measurement_hdf5()
13
+
14
+ __all__ = [
15
+ "Axis",
16
+ "Backscattermeter",
17
+ "Camera",
18
+ "Direction",
19
+ "Distance",
20
+ "DistanceRatio",
21
+ "DistanceResult",
22
+ "Measurement",
23
+ "MotorJump",
24
+ "MotorPath",
25
+ ]
@@ -0,0 +1,9 @@
1
+ """Sub-package focused on setup chacterization."""
2
+
3
+ from .distance import Ratio as DistanceRatio
4
+ from .distance import Result as DistanceResult
5
+
6
+ __all__ = [
7
+ "DistanceRatio",
8
+ "DistanceResult",
9
+ ]
@@ -0,0 +1,15 @@
1
+ """I/O package."""
2
+
3
+ from .hdf5 import register_measurement_hdf5
4
+ from .registry import (
5
+ UnifiedIORegistry,
6
+ UnifiedReadWrite,
7
+ UnifiedReadWriteMethod,
8
+ )
9
+
10
+ __all__ = [
11
+ "UnifiedIORegistry",
12
+ "UnifiedReadWrite",
13
+ "UnifiedReadWriteMethod",
14
+ "register_measurement_hdf5",
15
+ ]
@@ -0,0 +1,7 @@
1
+ """Sub-package for writing and parsing hdf5 files."""
2
+
3
+ from .measurement import (
4
+ register_measurement as register_measurement_hdf5,
5
+ )
6
+
7
+ __all__ = ["register_measurement_hdf5"]
@@ -0,0 +1,156 @@
1
+ """Hdf5 conversion for Measurement."""
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING
5
+
6
+ from h5py import File, Group
7
+
8
+ from backscattermeter_analyzer.hardware import Backscattermeter
9
+ from backscattermeter_analyzer.io.registry.default import (
10
+ default_registry,
11
+ )
12
+
13
+ from .utils import (
14
+ group_to_timeseriesdict,
15
+ is_hdf5,
16
+ timeseriesdict_to_group,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from backscattermeter_analyzer.utils.measurement.measurement import ( # noqa:E501,TC004
21
+ Measurement,
22
+ )
23
+
24
+
25
+ def read(input_: Path | Group, path: str | None = None) -> Measurement:
26
+ """
27
+ Read a Measurement object from an HDF5 component/file.
28
+
29
+ Parameters
30
+ ----------
31
+ input_: Path | Group
32
+ If a string or a Path, the filename to read the table from.
33
+ If an h5py object, either the file or the group object to read
34
+ the Measurement from.
35
+ path: str | None
36
+ The path from which to read the table inside the HDF5 file.
37
+ This should be relative to the input if the input is a Group.
38
+
39
+ """
40
+ from backscattermeter_analyzer.utils.measurement.measurement import ( # noqa: PLC0415,E501
41
+ Measurement,
42
+ )
43
+
44
+ if isinstance(input_, Path):
45
+ input_ = File(input_)
46
+
47
+ # input_ is now a Group (a File is a Group)
48
+ if path is not None:
49
+ item = input_.get(path, default=None)
50
+ if item is None:
51
+ message = f"{path} is not a valid path in {input_}."
52
+ raise ValueError(message)
53
+ if not isinstance(item, Group):
54
+ message = (
55
+ f"{path} from {input_} is a {type(item)}, not a Group."
56
+ )
57
+ raise TypeError(message)
58
+ else:
59
+ item = input_
60
+
61
+ group_data = item.get("data", default=None)
62
+ if group_data is None:
63
+ message = f"No 'data' group in {path} from {input_}."
64
+ raise ValueError(message)
65
+ if not isinstance(group_data, Group):
66
+ message = f"'data' must be a group in {path} from {input_}."
67
+ raise TypeError(message)
68
+
69
+ measurement = Measurement(
70
+ data=group_to_timeseriesdict(group_data),
71
+ backscattermeter=Backscattermeter(),
72
+ )
73
+
74
+ if isinstance(input_, File):
75
+ input_.close()
76
+
77
+ return measurement
78
+
79
+
80
+ def write(
81
+ measurement: Measurement,
82
+ output: Path | Group,
83
+ path: str | None = None,
84
+ *,
85
+ append: bool = False,
86
+ overwrite: bool = False,
87
+ ) -> None:
88
+ """
89
+ Write a Measurement object to an HDF5 file.
90
+
91
+ Parameters
92
+ ----------
93
+ measurement: Measurement
94
+ Measurement that is to be written to file.
95
+ output: Path | Group
96
+ If a Path, the path of the file to write the measurement to. If
97
+ a Group, the group object to write the measurement to, depending
98
+ on `path` value.
99
+ path: str | None
100
+ The path to which to write the table inside the HDF5 file.
101
+ This should be relative to the output Group (or the root Group
102
+ if output is a Path). Default to None.
103
+ append: bool
104
+ Whether to append the Measurement to an existing HDF5 file.
105
+ Default to False. This can delete a previous measurement.
106
+ overwrite: bool
107
+ Whether to overwrite any existing file without warning. Default
108
+ to False. If `append=True` and `overwrite=True`, then only the
109
+ group will be replaced.
110
+
111
+ """
112
+
113
+ if isinstance(output, Path):
114
+ if overwrite and not append:
115
+ output = File(output, mode="w")
116
+ elif append:
117
+ output = File(output, mode="r+")
118
+ else:
119
+ output = File(output, mode="x")
120
+
121
+ # output is now a Group (a File is a Group)
122
+ if path is not None:
123
+ item = output.get(path, default=None)
124
+ if item is None:
125
+ message = f"{path} is not a valid path in {output}."
126
+ raise ValueError(message)
127
+ if not isinstance(item, Group):
128
+ message = (
129
+ f"{path} from {output} is a {type(item)}, not a Group."
130
+ )
131
+ raise TypeError(message)
132
+ else:
133
+ item = output
134
+
135
+ element_number = len(item.keys())
136
+ if element_number != 0 and append:
137
+ item.clear()
138
+ elif element_number != 0 and not append:
139
+ message = f"{path} from {output} is not an empty group."
140
+ raise ValueError(message)
141
+
142
+ timeseriesdict_to_group(item, "data", measurement.data)
143
+
144
+ if isinstance(output, File):
145
+ output.close()
146
+
147
+
148
+ def register_measurement() -> None:
149
+ """Register Measurement hdf5 conversion in Unified I/O."""
150
+ from backscattermeter_analyzer.utils.measurement.measurement import ( # noqa: PLC0415,E501
151
+ Measurement,
152
+ )
153
+
154
+ default_registry.register_identifier("hdf5", Measurement, is_hdf5)
155
+ default_registry.register_reader("hdf5", Measurement, read)
156
+ default_registry.register_writer("hdf5", Measurement, write)
@@ -0,0 +1,322 @@
1
+ """Utils."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any, IO, Literal
5
+
6
+ from astropy.io.misc.hdf5 import HDF5_SIGNATURE
7
+ from astropy.units import Quantity
8
+ from gwpy.detector.channel import Channel
9
+ from gwpy.frequencyseries import FrequencySeries
10
+ from gwpy.timeseries import TimeSeries, TimeSeriesDict
11
+ from h5py import Dataset, Group
12
+ from numpy import array, geomspace
13
+
14
+ from backscattermeter_analyzer.io.registry.default import (
15
+ default_registry,
16
+ )
17
+ from backscattermeter_analyzer.scale import Scale
18
+
19
+
20
+ def dataset_to_frequencyseries(dataset: Dataset) -> FrequencySeries:
21
+ """
22
+ Convert a hdf5 dataset to a FrequencySeries.
23
+
24
+ Parameters
25
+ ----------
26
+ dataset: Dataset
27
+ Dataset to convert.
28
+
29
+ Returns
30
+ -------
31
+ FrequencySeries
32
+ FrequencySeries.
33
+
34
+ """
35
+ unit = None
36
+ scale = Scale.LINEAR
37
+ if "units" in dataset.attrs:
38
+ unit = dataset.attrs["units"]
39
+
40
+ if "scale" in dataset.attrs:
41
+ scale = Scale(dataset.attrs["scale"])
42
+
43
+ if scale is Scale.LINEAR:
44
+ if "f0" not in dataset.attrs:
45
+ message = f"f0 not defined in dataset {dataset.name}"
46
+ raise ValueError(message)
47
+ if "df" not in dataset.attrs:
48
+ message = f"df not defined in dataset {dataset.name}"
49
+ raise ValueError(message)
50
+
51
+ f0 = Quantity(dataset.attrs["f0"])
52
+ df = Quantity(dataset.attrs["df"])
53
+
54
+ return FrequencySeries(
55
+ data=array(dataset),
56
+ unit=unit,
57
+ f0=f0,
58
+ df=df,
59
+ )
60
+
61
+ if "start" not in dataset.attrs:
62
+ message = f"start not defined in dataset {dataset.name}"
63
+ raise ValueError(message)
64
+ if "stop" not in dataset.attrs:
65
+ message = f"stop not defined in dataset {dataset.name}"
66
+ raise ValueError(message)
67
+ if "num" not in dataset.attrs:
68
+ message = f"num not defined in dataset {dataset.name}"
69
+ raise ValueError(message)
70
+
71
+ start = Quantity(dataset.attrs["start"])
72
+ stop = Quantity(dataset.attrs["stop"])
73
+ num = int(dataset.attrs["num"])
74
+
75
+ return FrequencySeries(
76
+ data=array(dataset),
77
+ unit=unit,
78
+ frequencies=geomspace(start=start, stop=stop, num=num),
79
+ )
80
+
81
+
82
+ def dataset_to_timeseries(dataset: Dataset) -> TimeSeries:
83
+ """
84
+ Convert a hdf5 dataset to a TimeSeries.
85
+
86
+ Parameters
87
+ ----------
88
+ dataset: Dataset
89
+ Dataset to convert.
90
+
91
+ Returns
92
+ -------
93
+ TimeSeries
94
+ TimeSeries.
95
+
96
+ """
97
+ unit = None
98
+ scale = Scale.LINEAR
99
+ if "units" in dataset.attrs:
100
+ unit = dataset.attrs["units"]
101
+
102
+ if "scale" in dataset.attrs:
103
+ scale = Scale(dataset.attrs["scale"])
104
+
105
+ if scale is Scale.LINEAR:
106
+ if "t0" not in dataset.attrs:
107
+ message = f"t0 not defined in dataset {dataset.name}."
108
+ raise ValueError(message)
109
+
110
+ if "dt" not in dataset.attrs:
111
+ message = f"dt not defined in dataset {dataset.name}."
112
+ raise ValueError(message)
113
+
114
+ t0 = Quantity(dataset.attrs["t0"])
115
+ dt = Quantity(dataset.attrs["dt"])
116
+
117
+ return TimeSeries(
118
+ data=array(dataset),
119
+ unit=unit,
120
+ t0=t0,
121
+ dt=dt,
122
+ )
123
+
124
+ if "start" not in dataset.attrs:
125
+ message = f"start not defined in dataset {dataset.name}"
126
+ raise ValueError(message)
127
+ if "stop" not in dataset.attrs:
128
+ message = f"stop not defined in dataset {dataset.name}"
129
+ raise ValueError(message)
130
+ if "num" not in dataset.attrs:
131
+ message = f"num not defined in dataset {dataset.name}"
132
+ raise ValueError(message)
133
+
134
+ start = Quantity(dataset.attrs["start"])
135
+ stop = Quantity(dataset.attrs["stop"])
136
+ num = int(dataset.attrs["num"])
137
+
138
+ return TimeSeries(
139
+ data=array(dataset),
140
+ unit=unit,
141
+ times=Quantity(
142
+ geomspace(
143
+ start=start.to_value(unit="s"),
144
+ stop=stop.to_value(unit="s"),
145
+ num=num,
146
+ ),
147
+ "s",
148
+ ),
149
+ )
150
+
151
+
152
+ def group_to_timeseriesdict(group: Group) -> TimeSeriesDict: # type: ignore[ty:invalid-type-form]
153
+ """
154
+ Convert a hdf5 group to a TimeSeriesDict.
155
+
156
+ Parameters
157
+ ----------
158
+ group: Group
159
+ Group to convert.
160
+
161
+ Returns
162
+ -------
163
+ TimeSeriesDict
164
+ TimeSeriesDict.
165
+
166
+ """
167
+ dict_data = {}
168
+ for name, dataset in group.items():
169
+ if not isinstance(dataset, Dataset):
170
+ continue
171
+
172
+ dict_data[name] = dataset_to_timeseries(dataset)
173
+
174
+ return TimeSeriesDict(dict_data)
175
+
176
+
177
+ def frequencyseries_to_dataset(
178
+ store: Group, name: str, frequencyseries: FrequencySeries
179
+ ) -> Dataset:
180
+ """
181
+ Convert a FrequencySeries to an hdf5 dataset.
182
+
183
+ It adds the dataset to the `store`.
184
+
185
+ Parameters
186
+ ----------
187
+ store: Group
188
+ Hdf5 element which will store the dataset.
189
+ name: str
190
+ Name of the dataset.
191
+ frequencyseries: FrequencySeries
192
+ Data to store.
193
+
194
+ Returns
195
+ -------
196
+ Dataset
197
+ Created dataset.
198
+
199
+ """
200
+ dataset = store.create_dataset(name, data=frequencyseries.data)
201
+
202
+ if frequencyseries.unit is not None:
203
+ dataset.attrs["units"] = frequencyseries.unit.to_string()
204
+
205
+ try:
206
+ f0 = frequencyseries.f0
207
+ df = frequencyseries.df
208
+ dataset.attrs["scale"] = Scale.LINEAR
209
+ dataset.attrs["f0"] = f0.to_string()
210
+ dataset.attrs["df"] = df.to_string()
211
+ except AttributeError:
212
+ start = frequencyseries.f0
213
+ stop = frequencyseries.frequencies[-1]
214
+ num = len(frequencyseries.frequencies)
215
+ dataset.attrs["scale"] = Scale.LOG
216
+ dataset.attrs["start"] = start.to_string()
217
+ dataset.attrs["stop"] = stop.to_string()
218
+ dataset.attrs["num"] = num
219
+
220
+ return dataset
221
+
222
+
223
+ def timeseries_to_dataset(
224
+ store: Group, name: str, timeseries: TimeSeries
225
+ ) -> Dataset:
226
+ """
227
+ Convert a TimeSeries to an hdf5 dataset.
228
+
229
+ It adds the dataset to the `store`.
230
+
231
+ Parameters
232
+ ----------
233
+ store: Group
234
+ Hdf5 element which will store the dataset.
235
+ name: str
236
+ Name of the dataset.
237
+ timeseries: TimeSeries
238
+ Data to store.
239
+
240
+ Returns
241
+ -------
242
+ Dataset
243
+ Created dataset.
244
+
245
+ """
246
+ dataset = store.create_dataset(name, data=timeseries.data)
247
+
248
+ if timeseries.unit is not None:
249
+ dataset.attrs["units"] = timeseries.unit.to_string()
250
+
251
+ try:
252
+ t0 = timeseries.t0
253
+ dt = timeseries.dt
254
+ dataset.attrs["scale"] = Scale.LINEAR.value
255
+ dataset.attrs["t0"] = t0.to_string()
256
+ dataset.attrs["dt"] = dt.to_string()
257
+ except AttributeError:
258
+ start = timeseries.t0
259
+ stop = timeseries.times[-1]
260
+ num = len(timeseries.times)
261
+ dataset.attrs["scale"] = Scale.LOG.value
262
+ dataset.attrs["start"] = start.to_string()
263
+ dataset.attrs["stop"] = stop.to_string()
264
+ dataset.attrs["num"] = num
265
+
266
+ return dataset
267
+
268
+
269
+ def timeseriesdict_to_group(
270
+ store: Group, name: str, timeseriesdict: TimeSeriesDict # type: ignore[ty:invalid-type-form]
271
+ ) -> Group:
272
+ """
273
+ Convert a TimeSeriesDict to an hdf5 group.
274
+
275
+ It adds the group to the `store`.
276
+
277
+ Parameters
278
+ ----------
279
+ store: Group
280
+ Hdf5 element which will store the group.
281
+ name: str
282
+ Name of the group.
283
+ timeseriesdict: TimeSeriesDict
284
+ Data to store.
285
+
286
+ Returns
287
+ -------
288
+ Group
289
+ Created group.
290
+
291
+ """
292
+ group = store.create_group(name)
293
+
294
+ for key, timeseries in timeseriesdict.items():
295
+ channel = key
296
+ if isinstance(channel, Channel):
297
+ channel = str(key)
298
+ timeseries_to_dataset(group, channel, timeseries)
299
+
300
+ return group
301
+
302
+
303
+ def is_hdf5(
304
+ origin: Literal["read", "write"],
305
+ path: Path | None,
306
+ fileobj: IO | None,
307
+ *args: Any,
308
+ **kwargs: Any,
309
+ ) -> bool:
310
+ """Check if data is hdf5."""
311
+
312
+ if fileobj is not None:
313
+ loc = fileobj.tell()
314
+ try:
315
+ signature = fileobj.read(8)
316
+ finally:
317
+ fileobj.seek(loc)
318
+ return signature == HDF5_SIGNATURE
319
+ if path is not None:
320
+ return path.suffix in ["hdf5", "h5"]
321
+
322
+ return isinstance(args[0], (Group, Dataset)) # File is a Group
@@ -0,0 +1,16 @@
1
+ """Unified I/O Registry (see astropy)."""
2
+
3
+ from .interface import UnifiedReadWrite, UnifiedReadWriteMethod
4
+ from .unified_input_registry import UnifiedInputRegistry
5
+ from .unified_io_registry import UnifiedIORegistry
6
+ from .unified_io_registry_base import UnifiedIORegistryBase
7
+ from .unified_output_registry import UnifiedOutputRegistry
8
+
9
+ __all__ = [
10
+ "UnifiedIORegistry",
11
+ "UnifiedIORegistryBase",
12
+ "UnifiedInputRegistry",
13
+ "UnifiedOutputRegistry",
14
+ "UnifiedReadWrite",
15
+ "UnifiedReadWriteMethod",
16
+ ]
@@ -0,0 +1,5 @@
1
+ """Contains and store default states."""
2
+
3
+ from .unified_io_registry import UnifiedIORegistry
4
+
5
+ default_registry = UnifiedIORegistry()
@@ -0,0 +1,9 @@
1
+ """Define interfaces for Unified I/O Registry."""
2
+
3
+ from .unified_read_write import UnifiedReadWrite
4
+ from .unified_read_write_method import UnifiedReadWriteMethod
5
+
6
+ __all__ = [
7
+ "UnifiedReadWrite",
8
+ "UnifiedReadWriteMethod",
9
+ ]
@@ -0,0 +1,108 @@
1
+ """UnifiedReadWrite class."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from inspect import cleandoc
5
+ from os import linesep
6
+ from re import sub
7
+ from sys import flags
8
+ from typing import IO, Literal
9
+
10
+ from rich.pager import Pager
11
+
12
+ from backscattermeter_analyzer.io.registry.default import (
13
+ default_registry,
14
+ )
15
+ from backscattermeter_analyzer.io.registry.unified_io_registry import (
16
+ UnifiedIORegistry,
17
+ )
18
+
19
+
20
+ @dataclass
21
+ class UnifiedReadWrite[T]:
22
+ """
23
+ Same goal than its astropy equivalent.
24
+
25
+ See astropy.io.registry.interface.UnifiedReadWrite
26
+ """
27
+
28
+ # Descriptor calling instance or None if no instance.
29
+ _instance: T | None
30
+ # Descriptor calling class (either owner class or instance class).
31
+ _class_: type[T]
32
+ # Method name (read or write).
33
+ _method_name: Literal["read", "write"]
34
+ # The IO Registry.
35
+ _registry: UnifiedIORegistry = field(init=False)
36
+
37
+ def __post_init__(self) -> None:
38
+ """Post-initialization process."""
39
+ self._registry = default_registry
40
+
41
+ def help(
42
+ self, format_: str | None = None, out: IO | None = None
43
+ ) -> None:
44
+ """
45
+ Output help documentation for the specified unified I/O format.
46
+
47
+ By default the help output is printed to the console via
48
+ pydoc.pager. Instead one can supplied a file handle object as
49
+ `out` and the output will be written to that handle.
50
+
51
+ Parameters
52
+ ----------
53
+ format_: str | None
54
+ Unified I/O format name, e.g. 'fits' or 'ascii.ecsv'
55
+ out: IO | None
56
+ Output destination. If None, stdout via a pager. Default to
57
+ None.
58
+
59
+ """
60
+ if flags.optimize >= 2: # noqa:PLR2004
61
+ message = (
62
+ "The help method is not available under Python's"
63
+ " optimized mode."
64
+ )
65
+ raise RuntimeError(message)
66
+
67
+ class_ = self._class_
68
+ method_name = self._method_name
69
+
70
+ # Get reader or writer function associated with the registry
71
+ get_func = (
72
+ self._registry.get_reader
73
+ if method_name == "read"
74
+ else self._registry.get_writer
75
+ )
76
+ try:
77
+ if format_:
78
+ read_write_func = get_func(format_, class_)
79
+ except ValueError as err:
80
+ reader_doc = "ERROR: " + str(err)
81
+ else:
82
+ if format_:
83
+ # Format-specific
84
+ header = (
85
+ f"{class_.__name__}.{method_name}(format_="
86
+ f"'{format_}') documentation\n"
87
+ )
88
+ doc = read_write_func.__doc__
89
+ else:
90
+ # General docs
91
+ header = (
92
+ f"{class_.__name__}.{method_name} general"
93
+ " documentation\n"
94
+ )
95
+ doc = getattr(class_, method_name).__doc__
96
+
97
+ reader_doc = sub(".", "=", header)
98
+ reader_doc += header
99
+ reader_doc += sub(".", "=", header)
100
+ reader_doc += linesep
101
+ if doc is not None:
102
+ reader_doc += cleandoc(doc)
103
+
104
+ if out is None:
105
+ pager = Pager()
106
+ pager.show(reader_doc)
107
+ else:
108
+ out.write(reader_doc)