legend-pydataobj 1.0.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,264 @@
1
+ """
2
+ Implements a LEGEND Data Object representing a special
3
+ :class:`~.lgdo.table.Table` to store blocks of one-dimensional time-series
4
+ data.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import logging
9
+ from typing import Any
10
+
11
+ import numpy as np
12
+
13
+ from .array import Array
14
+ from .arrayofequalsizedarrays import ArrayOfEqualSizedArrays
15
+ from .encoded import ArrayOfEncodedEqualSizedArrays, VectorOfEncodedVectors
16
+ from .table import Table
17
+ from .vectorofvectors import VectorOfVectors
18
+
19
+ log = logging.getLogger(__name__)
20
+
21
+
22
+ class WaveformTable(Table):
23
+ r"""An LGDO for storing blocks of (1D) time-series data.
24
+
25
+ A :class:`WaveformTable` is an LGDO :class:`.Table` with the 3
26
+ columns ``t0``, ``dt``, and ``values``:
27
+
28
+ * ``t0[i]`` is a time offset (relative to a user-defined global reference)
29
+ for the sample in ``values[i][0]``. Implemented as an LGDO
30
+ :class:`.Array` with optional attribute ``units``.
31
+ * ``dt[i]`` is the sampling period for the waveform at ``values[i]``.
32
+ Implemented as an LGDO :class:`.Array` with optional attribute ``units``.
33
+ * ``values[i]`` is the ``i``'th waveform in the table. Internally, the
34
+ waveforms values may be either an LGDO :class:`.ArrayOfEqualSizedArrays`\
35
+ ``<1,1>``, an LGDO :class:`.VectorOfVectors` or
36
+ :class:`.VectorOfEncodedVectors` that supports waveforms of unequal
37
+ length. Can optionally be given a ``units`` attribute.
38
+
39
+ Note
40
+ ----
41
+ On-disk and in-memory versions could be different e.g. if a compression
42
+ routine is used.
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ size: int = None,
48
+ t0: float | Array | np.ndarray = 0,
49
+ t0_units: str = None,
50
+ dt: float | Array | np.ndarray = 1,
51
+ dt_units: str = None,
52
+ values: ArrayOfEqualSizedArrays | VectorOfVectors | np.ndarray = None,
53
+ values_units: str = None,
54
+ wf_len: int = None,
55
+ dtype: np.dtype = None,
56
+ attrs: dict[str, Any] = None,
57
+ ) -> None:
58
+ r"""
59
+ Parameters
60
+ ----------
61
+ size
62
+ sets the number of rows in the table. If ``None``, the size will be
63
+ determined from the first among `t0`, `dt`, or `values` to return a
64
+ valid length. If not ``None``, `t0`, `dt`, and values will be
65
+ resized as necessary to match `size`. If `size` is ``None`` and
66
+ `t0`, `dt`, and `values` are all non-array-like, a default size of
67
+ 1024 is used.
68
+ t0
69
+ :math:`t_0` values to be used (or broadcast) to the `t0` column.
70
+ t0_units
71
+ units for the :math:`t_0` values. If not ``None`` and `t0` is an
72
+ LGDO :class:`.Array`, overrides what's in `t0`.
73
+ dt
74
+ :math:`\delta t` values (sampling period) to be used (or
75
+ broadcasted) to the `t0` column.
76
+ dt_units
77
+ units for the `dt` values. If not ``None`` and `dt` is an LGDO
78
+ :class:`.Array`, overrides what's in `dt`.
79
+ values
80
+ The waveform data to be stored in the table. If ``None`` a block of
81
+ data is prepared based on the `wf_len` and `dtype` arguments.
82
+ values_units
83
+ units for the waveform values. If not ``None`` and `values` is an
84
+ LGDO :class:`.Array`, overrides what's in `values`.
85
+ wf_len
86
+ The length of the waveforms in each entry of a table. If ``None``
87
+ (the default), unequal lengths are assumed and
88
+ :class:`.VectorOfVectors` is used for the `values` column. Ignored
89
+ if `values` is a 2D ndarray, in which case ``values.shape[1]`` is
90
+ used.
91
+ dtype
92
+ The NumPy :class:`numpy.dtype` of the waveform data. If `values` is
93
+ not ``None``, this argument is ignored. If both `values` and
94
+ `dtype` are ``None``, :class:`numpy.float64` is used.
95
+ attrs
96
+ A set of user attributes to be carried along with this LGDO.
97
+ """
98
+
99
+ if size is None:
100
+ if hasattr(t0, "__len__"):
101
+ size = len(t0)
102
+ elif hasattr(dt, "__len__"):
103
+ size = len(dt)
104
+ elif hasattr(values, "__len__"):
105
+ size = len(values)
106
+ if size is None:
107
+ size = 1024
108
+
109
+ if not isinstance(t0, Array):
110
+ shape = (size,)
111
+ t0_dtype = t0.dtype if hasattr(t0, "dtype") else np.float32
112
+ nda = (
113
+ t0 if isinstance(t0, np.ndarray) else np.full(shape, t0, dtype=t0_dtype)
114
+ )
115
+ if nda.shape != shape:
116
+ nda.resize(shape, refcheck=True)
117
+ t0 = Array(nda=nda)
118
+
119
+ if t0_units is not None:
120
+ t0.attrs["units"] = f"{t0_units}"
121
+
122
+ if not isinstance(dt, Array):
123
+ shape = (size,)
124
+ dt_dtype = dt.dtype if hasattr(dt, "dtype") else np.float32
125
+ nda = (
126
+ dt if isinstance(dt, np.ndarray) else np.full(shape, dt, dtype=dt_dtype)
127
+ )
128
+ if nda.shape != shape:
129
+ nda.resize(shape, refcheck=True)
130
+ dt = Array(nda=nda)
131
+ if dt_units is not None:
132
+ dt.attrs["units"] = f"{dt_units}"
133
+
134
+ if not isinstance(
135
+ values,
136
+ (
137
+ ArrayOfEqualSizedArrays,
138
+ VectorOfVectors,
139
+ VectorOfEncodedVectors,
140
+ ArrayOfEncodedEqualSizedArrays,
141
+ ),
142
+ ):
143
+ if isinstance(values, np.ndarray):
144
+ try:
145
+ wf_len = values.shape[1]
146
+ except Exception:
147
+ wf_len = None
148
+ if wf_len is None: # make a VectorOfVectors
149
+ shape_guess = (size, 100)
150
+ if dtype is None:
151
+ dtype = np.dtype(np.float64)
152
+ if values is None:
153
+ values = VectorOfVectors(shape_guess=shape_guess, dtype=dtype)
154
+ else:
155
+ flattened_data = np.concatenate(values)
156
+ length = 0
157
+ cumulative_length = []
158
+ for i in range(size):
159
+ length += len(values[i])
160
+ cumulative_length.append(length)
161
+ values = VectorOfVectors(
162
+ flattened_data=flattened_data,
163
+ cumulative_length=cumulative_length,
164
+ dtype=dtype,
165
+ )
166
+ else: # make a ArrayOfEqualSizedArrays
167
+ shape = (size, wf_len)
168
+ if dtype is None:
169
+ dtype = (
170
+ values.dtype
171
+ if hasattr(values, "dtype")
172
+ else np.dtype(np.float64)
173
+ )
174
+ nda = (
175
+ values
176
+ if isinstance(values, np.ndarray)
177
+ else np.zeros(shape, dtype=dtype)
178
+ )
179
+ if nda.shape != shape:
180
+ nda.resize(shape, refcheck=True)
181
+ values = ArrayOfEqualSizedArrays(dims=(1, 1), nda=nda)
182
+ if values_units is not None:
183
+ values.attrs["units"] = f"{values_units}"
184
+
185
+ col_dict = {}
186
+ col_dict["t0"] = t0
187
+ col_dict["dt"] = dt
188
+ col_dict["values"] = values
189
+ super().__init__(size=size, col_dict=col_dict, attrs=attrs)
190
+
191
+ @property
192
+ def values(self) -> ArrayOfEqualSizedArrays | VectorOfVectors:
193
+ return self["values"]
194
+
195
+ @property
196
+ def values_units(self) -> str:
197
+ return self.values.attrs.get("units", None)
198
+
199
+ @values_units.setter
200
+ def values_units(self, units) -> None:
201
+ self.values.attrs["units"] = f"{units}"
202
+
203
+ @property
204
+ def wf_len(self) -> int:
205
+ if isinstance(self.values, VectorOfVectors):
206
+ return -1
207
+ return self.values.nda.shape[1]
208
+
209
+ @wf_len.setter
210
+ def wf_len(self, wf_len) -> None:
211
+ if isinstance(self.values, VectorOfVectors):
212
+ return
213
+ shape = self.values.nda.shape
214
+ shape = (shape[0], wf_len)
215
+ self.values.nda.resize(shape, refcheck=True)
216
+
217
+ def resize_wf_len(self, new_len: int) -> None:
218
+ """Alias for `wf_len.setter`, for when we want to make it clear in
219
+ the code that memory is being reallocated.
220
+ """
221
+ self.wf_len = new_len
222
+
223
+ @property
224
+ def t0(self) -> Array:
225
+ return self["t0"]
226
+
227
+ @property
228
+ def t0_units(self) -> str:
229
+ return self.t0.attrs.get("units", None)
230
+
231
+ @t0_units.setter
232
+ def t0_units(self, units: str) -> None:
233
+ self.t0.attrs["units"] = f"{units}"
234
+
235
+ @property
236
+ def dt(self) -> Array:
237
+ return self["dt"]
238
+
239
+ @property
240
+ def dt_units(self) -> str:
241
+ return self.dt.attrs.get("units", None)
242
+
243
+ @dt_units.setter
244
+ def dt_units(self, units: str) -> None:
245
+ self.dt.attrs["units"] = f"{units}"
246
+
247
+ def __str__(self):
248
+ npopt = np.get_printoptions()
249
+ np.set_printoptions(threshold=100)
250
+
251
+ string = ""
252
+
253
+ for i in range(self.size):
254
+ string += f"{self.values[i]}, dt={self.dt[i]}"
255
+ if self.dt_units:
256
+ string += f" {self.dt_units}"
257
+ string += f", t0={self.t0[i]}"
258
+ if self.t0_units:
259
+ string += f" {self.t0_units}"
260
+ if i < self.size - 1:
261
+ string += "\n"
262
+
263
+ np.set_printoptions(**npopt)
264
+ return string