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.
- legend_pydataobj-1.0.0.dist-info/LICENSE +674 -0
- legend_pydataobj-1.0.0.dist-info/METADATA +63 -0
- legend_pydataobj-1.0.0.dist-info/RECORD +26 -0
- legend_pydataobj-1.0.0.dist-info/WHEEL +5 -0
- legend_pydataobj-1.0.0.dist-info/top_level.txt +1 -0
- lgdo/__init__.py +75 -0
- lgdo/_version.py +4 -0
- lgdo/compression/__init__.py +36 -0
- lgdo/compression/base.py +29 -0
- lgdo/compression/generic.py +77 -0
- lgdo/compression/radware.py +579 -0
- lgdo/compression/utils.py +34 -0
- lgdo/compression/varlen.py +449 -0
- lgdo/lgdo_utils.py +196 -0
- lgdo/lh5_store.py +1711 -0
- lgdo/types/__init__.py +30 -0
- lgdo/types/array.py +140 -0
- lgdo/types/arrayofequalsizedarrays.py +133 -0
- lgdo/types/encoded.py +390 -0
- lgdo/types/fixedsizearray.py +43 -0
- lgdo/types/lgdo.py +51 -0
- lgdo/types/scalar.py +59 -0
- lgdo/types/struct.py +108 -0
- lgdo/types/table.py +349 -0
- lgdo/types/vectorofvectors.py +627 -0
- lgdo/types/waveform_table.py +264 -0
@@ -0,0 +1,579 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
|
5
|
+
import numba
|
6
|
+
import numpy as np
|
7
|
+
from numpy import int16, int32, ubyte, uint16, uint32
|
8
|
+
from numpy.typing import NDArray
|
9
|
+
|
10
|
+
from .. import types as lgdo
|
11
|
+
from .base import WaveformCodec
|
12
|
+
|
13
|
+
# fmt: off
|
14
|
+
_radware_sigcompress_mask = uint16([0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023,
|
15
|
+
2047, 4095, 8191, 16383, 32767, 65535])
|
16
|
+
# fmt: on
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass(frozen=True)
|
20
|
+
class RadwareSigcompress(WaveformCodec):
|
21
|
+
"""`radware-sigcompress` array codec.
|
22
|
+
|
23
|
+
Examples
|
24
|
+
--------
|
25
|
+
>>> from lgdo.compression import RadwareSigcompress
|
26
|
+
>>> codec = RadwareSigcompress(codec_shift=-32768)
|
27
|
+
"""
|
28
|
+
|
29
|
+
codec_shift: int = 0
|
30
|
+
"""Offset added to the input waveform before encoding.
|
31
|
+
|
32
|
+
The `radware-sigcompress` algorithm is limited to encoding of 16-bit
|
33
|
+
integer values. In certain cases (notably, with *unsigned* 16-bit integer
|
34
|
+
values), shifting incompatible data by a fixed amount circumvents the
|
35
|
+
issue.
|
36
|
+
"""
|
37
|
+
|
38
|
+
|
39
|
+
def encode(
|
40
|
+
sig_in: NDArray | lgdo.VectorOfVectors | lgdo.ArrayOfEqualSizedArrays,
|
41
|
+
sig_out: NDArray[ubyte]
|
42
|
+
| lgdo.VectorOfEncodedVectors
|
43
|
+
| lgdo.ArrayOfEncodedEqualSizedArrays = None,
|
44
|
+
shift: int32 = 0,
|
45
|
+
) -> NDArray[ubyte] | lgdo.VectorOfEncodedVectors:
|
46
|
+
"""Compress digital signal(s) with `radware-sigcompress`.
|
47
|
+
|
48
|
+
Wraps :func:`._radware_sigcompress_encode` and adds support for encoding
|
49
|
+
LGDO arrays. Resizes the encoded array to its actual length.
|
50
|
+
|
51
|
+
Note
|
52
|
+
----
|
53
|
+
The compression algorithm internally interprets the input waveform values as
|
54
|
+
16-bit integers. Make sure that your signal can be safely cast to such a
|
55
|
+
numeric type. If not, you may want to apply a `shift` to the waveform.
|
56
|
+
|
57
|
+
Parameters
|
58
|
+
----------
|
59
|
+
sig_in
|
60
|
+
array(s) holding the input signal(s).
|
61
|
+
sig_out
|
62
|
+
pre-allocated unsigned 8-bit integer array(s) for the compressed
|
63
|
+
signal(s). If not provided, a new one will be allocated.
|
64
|
+
shift
|
65
|
+
value to be added to `sig_in` before compression.
|
66
|
+
|
67
|
+
Returns
|
68
|
+
-------
|
69
|
+
sig_out
|
70
|
+
given pre-allocated `sig_out` structure or new structure of unsigned
|
71
|
+
8-bit integers.
|
72
|
+
|
73
|
+
See Also
|
74
|
+
--------
|
75
|
+
._radware_sigcompress_encode
|
76
|
+
"""
|
77
|
+
# the encoded signal is an array of bytes -> twice as long as a uint16
|
78
|
+
# array
|
79
|
+
max_out_len = 2 * len(sig_in)
|
80
|
+
if isinstance(sig_in, np.ndarray) and sig_in.ndim == 1:
|
81
|
+
if len(sig_in) == 0:
|
82
|
+
return sig_in
|
83
|
+
|
84
|
+
if not sig_out:
|
85
|
+
# pre-allocate ubyte (uint8) array
|
86
|
+
sig_out = np.empty(max_out_len, dtype=ubyte)
|
87
|
+
|
88
|
+
if sig_out.dtype != ubyte:
|
89
|
+
raise ValueError("sig_out must be of type ubyte")
|
90
|
+
|
91
|
+
outlen = _radware_sigcompress_encode(sig_in, sig_out, shift=shift)
|
92
|
+
|
93
|
+
# resize (down) the encoded signal to its actual length
|
94
|
+
# TODO: really even if user supplied? Maybe not
|
95
|
+
if outlen < max_out_len:
|
96
|
+
sig_out.resize(outlen, refcheck=True)
|
97
|
+
|
98
|
+
elif isinstance(sig_in, lgdo.ArrayOfEqualSizedArrays):
|
99
|
+
if not sig_out:
|
100
|
+
# pre-allocate output structure
|
101
|
+
# use maximum length possible
|
102
|
+
sig_out = lgdo.ArrayOfEncodedEqualSizedArrays(
|
103
|
+
encoded_data=lgdo.VectorOfVectors(
|
104
|
+
shape_guess=(len(sig_in), 2 * sig_in.nda.shape[1]), dtype=ubyte
|
105
|
+
),
|
106
|
+
decoded_size=sig_in.nda.shape[1],
|
107
|
+
)
|
108
|
+
elif not isinstance(sig_out, lgdo.ArrayOfEncodedEqualSizedArrays):
|
109
|
+
raise ValueError("sig_out must be an ArrayOfEncodedEqualSizedArrays")
|
110
|
+
|
111
|
+
# use unsafe set_vector to fill pre-allocated memory
|
112
|
+
for i, wf in enumerate(sig_in):
|
113
|
+
sig_out.encoded_data._set_vector_unsafe(i, encode(wf, shift=shift))
|
114
|
+
|
115
|
+
# resize down flattened data array
|
116
|
+
# TODO: really even if user supplied? Maybe not
|
117
|
+
sig_out.resize(len(sig_in))
|
118
|
+
|
119
|
+
elif isinstance(sig_in, lgdo.VectorOfVectors):
|
120
|
+
if not sig_out:
|
121
|
+
max_out_len = 2 * len(sig_in.flattened_data) / len(sig_in.cumulative_length)
|
122
|
+
sig_out = lgdo.VectorOfEncodedVectors(
|
123
|
+
encoded_data=lgdo.VectorOfVectors(
|
124
|
+
shape_guess=(len(sig_in), max_out_len), dtype=ubyte
|
125
|
+
),
|
126
|
+
)
|
127
|
+
elif not isinstance(sig_out, lgdo.VectorOfEncodedVectors):
|
128
|
+
raise ValueError("sig_out must be a VectorOfEncodedVectors")
|
129
|
+
|
130
|
+
# use unsafe set_vector to fill pre-allocated memory
|
131
|
+
# should be fast enough
|
132
|
+
for i, wf in enumerate(sig_in):
|
133
|
+
sig_out.encoded_data._set_vector_unsafe(i, encode(wf, shift=shift))
|
134
|
+
sig_out.decoded_size[i] = len(wf)
|
135
|
+
|
136
|
+
else:
|
137
|
+
raise ValueError(f"unsupported input signal type ({type(sig_in)})")
|
138
|
+
|
139
|
+
# resize down flattened data array
|
140
|
+
# TODO: really even if user supplied? Maybe not
|
141
|
+
sig_out.resize(len(sig_in))
|
142
|
+
|
143
|
+
return sig_out
|
144
|
+
|
145
|
+
|
146
|
+
def decode(
|
147
|
+
sig_in: NDArray[ubyte]
|
148
|
+
| lgdo.VectorOfEncodedVectors
|
149
|
+
| lgdo.ArrayOfEncodedEqualSizedArrays,
|
150
|
+
sig_out: NDArray | lgdo.VectorOfVectors | lgdo.ArrayOfEqualSizedArrays = None,
|
151
|
+
shift: int32 = 0,
|
152
|
+
) -> NDArray | lgdo.VectorOfVectors | lgdo.ArrayOfEqualSizedArrays:
|
153
|
+
"""Decompress digital signal(s) with `radware-sigcompress`.
|
154
|
+
|
155
|
+
Wraps :func:`._radware_sigcompress_decode` and adds support for decoding
|
156
|
+
LGDOs. Resizes the decoded signals to their actual length.
|
157
|
+
|
158
|
+
Parameters
|
159
|
+
----------
|
160
|
+
sig_in
|
161
|
+
array(s) holding the input, compressed signal(s).
|
162
|
+
sig_out
|
163
|
+
pre-allocated array(s) for the decompressed signal(s). If not
|
164
|
+
provided, will allocate a 32-bit integer array(s) structure.
|
165
|
+
shift
|
166
|
+
the value the original signal(s) was shifted before compression. The
|
167
|
+
value is *subtracted* from samples in `sig_out` right after decoding.
|
168
|
+
|
169
|
+
Returns
|
170
|
+
-------
|
171
|
+
sig_out
|
172
|
+
given pre-allocated structure or new structure of 32-bit integers.
|
173
|
+
|
174
|
+
See Also
|
175
|
+
--------
|
176
|
+
._radware_sigcompress_decode
|
177
|
+
"""
|
178
|
+
if isinstance(sig_in, np.ndarray) and sig_in.ndim == 1 and sig_in.dtype == ubyte:
|
179
|
+
if len(sig_in) == 0:
|
180
|
+
return sig_in
|
181
|
+
|
182
|
+
siglen = _get_hton_u16(sig_in, 0)
|
183
|
+
if not sig_out:
|
184
|
+
# pre-allocate memory, use safe int32
|
185
|
+
sig_out = np.empty(siglen, dtype="int32")
|
186
|
+
elif len(sig_out) < siglen:
|
187
|
+
# TODO: really even if user supplied? Maybe not
|
188
|
+
sig_out.resize(siglen, refcheck=False)
|
189
|
+
|
190
|
+
_radware_sigcompress_decode(sig_in, sig_out, shift=shift)
|
191
|
+
|
192
|
+
elif isinstance(sig_in, lgdo.ArrayOfEncodedEqualSizedArrays):
|
193
|
+
if not sig_out:
|
194
|
+
# pre-allocate output structure
|
195
|
+
sig_out = lgdo.ArrayOfEqualSizedArrays(
|
196
|
+
dims=(1, 1),
|
197
|
+
shape=(len(sig_in), sig_in.decoded_size.value),
|
198
|
+
dtype="int32",
|
199
|
+
)
|
200
|
+
|
201
|
+
elif not isinstance(sig_out, lgdo.ArrayOfEqualSizedArrays):
|
202
|
+
raise ValueError("sig_out must be an ArrayOfEqualSizedArrays")
|
203
|
+
|
204
|
+
for i, wf in enumerate(sig_in):
|
205
|
+
sig_out[i] = decode(wf, shift=shift)
|
206
|
+
|
207
|
+
elif isinstance(sig_in, lgdo.VectorOfEncodedVectors):
|
208
|
+
if not sig_out:
|
209
|
+
# pre-allocate output structure
|
210
|
+
sig_out = lgdo.VectorOfVectors(
|
211
|
+
cumulative_length=np.cumsum(sig_in.decoded_size), dtype="int32"
|
212
|
+
)
|
213
|
+
|
214
|
+
elif not isinstance(sig_out, lgdo.VectorOfVectors):
|
215
|
+
raise ValueError("sig_out must be a VectorOfVectors")
|
216
|
+
|
217
|
+
for i, wf in enumerate(sig_in):
|
218
|
+
sig_out[i] = decode(wf[0], shift=shift)
|
219
|
+
|
220
|
+
else:
|
221
|
+
raise ValueError(f"unsupported input signal type ({type(sig_in)})")
|
222
|
+
|
223
|
+
return sig_out
|
224
|
+
|
225
|
+
|
226
|
+
@numba.jit(nopython=True)
|
227
|
+
def _set_hton_u16(a: NDArray[ubyte], i: int, x: int) -> int:
|
228
|
+
"""Store an unsigned 16-bit integer value in an array of unsigned 8-bit integers.
|
229
|
+
|
230
|
+
The first two most significant bytes from `x` are stored contiguously in
|
231
|
+
`a` with big-endian order.
|
232
|
+
"""
|
233
|
+
x_u16 = uint16(x)
|
234
|
+
i_1 = i * 2
|
235
|
+
i_2 = i_1 + 1
|
236
|
+
a[i_1] = ubyte(x_u16 >> 8)
|
237
|
+
a[i_2] = ubyte(x_u16 >> 0)
|
238
|
+
return x
|
239
|
+
|
240
|
+
|
241
|
+
@numba.jit(nopython=True)
|
242
|
+
def _get_hton_u16(a: NDArray[ubyte], i: int) -> uint16:
|
243
|
+
"""Read unsigned 16-bit integer values from an array of unsigned 8-bit integers.
|
244
|
+
|
245
|
+
The first two most significant bytes of the values must be stored
|
246
|
+
contiguously in `a` with big-endian order.
|
247
|
+
"""
|
248
|
+
i_1 = i * 2
|
249
|
+
i_2 = i_1 + 1
|
250
|
+
return uint16(a[i_1] << 8 | a[i_2])
|
251
|
+
|
252
|
+
|
253
|
+
@numba.jit("uint16(uint32)", nopython=True)
|
254
|
+
def _get_high_u16(x: uint32) -> uint16:
|
255
|
+
return uint16(x >> 16)
|
256
|
+
|
257
|
+
|
258
|
+
@numba.jit("uint32(uint32, uint16)", nopython=True)
|
259
|
+
def _set_high_u16(x: uint32, y: uint16) -> uint32:
|
260
|
+
return uint32(x & 0x0000FFFF | (y << 16))
|
261
|
+
|
262
|
+
|
263
|
+
@numba.jit("uint16(uint32)", nopython=True)
|
264
|
+
def _get_low_u16(x: uint32) -> uint16:
|
265
|
+
return uint16(x >> 0)
|
266
|
+
|
267
|
+
|
268
|
+
@numba.jit("uint32(uint32, uint16)", nopython=True)
|
269
|
+
def _set_low_u16(x: uint32, y: uint16) -> uint32:
|
270
|
+
return uint32(x & 0xFFFF0000 | (y << 0))
|
271
|
+
|
272
|
+
|
273
|
+
@numba.jit(nopython=True)
|
274
|
+
def _radware_sigcompress_encode(
|
275
|
+
sig_in: NDArray,
|
276
|
+
sig_out: NDArray[ubyte],
|
277
|
+
shift: int32,
|
278
|
+
_mask: NDArray[uint16] = _radware_sigcompress_mask,
|
279
|
+
) -> int32:
|
280
|
+
"""Compress a digital signal.
|
281
|
+
|
282
|
+
Shifts the signal values by ``+shift`` and internally interprets the result
|
283
|
+
as :any:`numpy.int16`. Shifted signals must be therefore representable as
|
284
|
+
:any:`numpy.int16`, for lossless compression.
|
285
|
+
|
286
|
+
Note
|
287
|
+
----
|
288
|
+
The algorithm also computes the first derivative of the input signal, which
|
289
|
+
cannot always be represented as a 16-bit integer. In such cases, overflows
|
290
|
+
occur, but they seem to be innocuous.
|
291
|
+
|
292
|
+
Almost literal translations of ``compress_signal()`` from the
|
293
|
+
`radware-sigcompress` v1.0 C-code by David Radford [1]_. Summary of
|
294
|
+
changes:
|
295
|
+
|
296
|
+
- Shift the input signal by `shift` before encoding.
|
297
|
+
- Store encoded, :class:`numpy.uint16` signal as an array of bytes
|
298
|
+
(:class:`numpy.ubyte`), in big-endian ordering.
|
299
|
+
- Declare mask globally to avoid extra memory allocation.
|
300
|
+
- Apply just-in-time compilation with Numba.
|
301
|
+
- Add a couple of missing array boundary checks.
|
302
|
+
|
303
|
+
.. [1] `radware-sigcompress source code
|
304
|
+
<https://legend-exp.github.io/legend-data-format-specs/dev/data_compression/#radware-sigcompress-1>`_.
|
305
|
+
released under MIT license `[Copyright (c) 2018, David C. Radford
|
306
|
+
<radforddc@ornl.gov>]`.
|
307
|
+
|
308
|
+
Parameters
|
309
|
+
----------
|
310
|
+
sig_in
|
311
|
+
array of integers holding the input signal. In the original C code,
|
312
|
+
an array of 16-bit integers was expected.
|
313
|
+
sig_out
|
314
|
+
pre-allocated array for the unsigned 8-bit encoded signal. In the
|
315
|
+
original C code, an array of unsigned 16-bit integers was expected.
|
316
|
+
|
317
|
+
Returns
|
318
|
+
-------
|
319
|
+
length
|
320
|
+
number of bytes in the encoded signal
|
321
|
+
"""
|
322
|
+
mask = _mask
|
323
|
+
|
324
|
+
i = j = max1 = max2 = min1 = min2 = ds = int16(0)
|
325
|
+
nb1 = nb2 = iso = nw = bp = dd1 = dd2 = int16(0)
|
326
|
+
dd = uint32(0)
|
327
|
+
|
328
|
+
_set_hton_u16(sig_out, iso, sig_in.size)
|
329
|
+
|
330
|
+
iso += 1
|
331
|
+
while j < sig_in.size: # j = starting index of section of signal
|
332
|
+
# find optimal method and length for compression
|
333
|
+
# of next section of signal
|
334
|
+
si_j = int16(sig_in[j] + shift)
|
335
|
+
max1 = min1 = si_j
|
336
|
+
max2 = int32(-16000)
|
337
|
+
min2 = int32(16000)
|
338
|
+
nb1 = nb2 = 2
|
339
|
+
nw = 1
|
340
|
+
i = j + 1
|
341
|
+
# FIXME: 48 could be tuned better?
|
342
|
+
while (i < sig_in.size) and (i < j + 48):
|
343
|
+
si_i = int16(sig_in[i] + shift)
|
344
|
+
si_im1 = int16(sig_in[i - 1] + shift)
|
345
|
+
if max1 < si_i:
|
346
|
+
max1 = si_i
|
347
|
+
if min1 > si_i:
|
348
|
+
min1 = si_i
|
349
|
+
ds = si_i - si_im1
|
350
|
+
if max2 < ds:
|
351
|
+
max2 = ds
|
352
|
+
if min2 > ds:
|
353
|
+
min2 = ds
|
354
|
+
nw += 1
|
355
|
+
i += 1
|
356
|
+
if max1 - min1 <= max2 - min2: # use absolute values
|
357
|
+
nb2 = 99
|
358
|
+
while (max1 - min1) > mask[nb1]:
|
359
|
+
nb1 += 1
|
360
|
+
while (i < sig_in.size) and (
|
361
|
+
i < j + 128
|
362
|
+
): # FIXME: 128 could be tuned better?
|
363
|
+
si_i = int16(sig_in[i] + shift)
|
364
|
+
if max1 < si_i:
|
365
|
+
max1 = si_i
|
366
|
+
dd1 = max1 - min1
|
367
|
+
if min1 > si_i:
|
368
|
+
dd1 = max1 - si_i
|
369
|
+
if dd1 > mask[nb1]:
|
370
|
+
break
|
371
|
+
if min1 > si_i:
|
372
|
+
min1 = si_i
|
373
|
+
nw += 1
|
374
|
+
i += 1
|
375
|
+
else: # use difference values
|
376
|
+
nb1 = 99
|
377
|
+
while max2 - min2 > mask[nb2]:
|
378
|
+
nb2 += 1
|
379
|
+
while (i < sig_in.size) and (
|
380
|
+
i < j + 128
|
381
|
+
): # FIXME: 128 could be tuned better?
|
382
|
+
si_i = int16(sig_in[i] + shift)
|
383
|
+
si_im1 = int16(sig_in[i - 1] + shift)
|
384
|
+
ds = si_i - si_im1
|
385
|
+
if max2 < ds:
|
386
|
+
max2 = ds
|
387
|
+
dd2 = max2 - min2
|
388
|
+
if min2 > ds:
|
389
|
+
dd2 = max2 - ds
|
390
|
+
if dd2 > mask[nb2]:
|
391
|
+
break
|
392
|
+
if min2 > ds:
|
393
|
+
min2 = ds
|
394
|
+
nw += 1
|
395
|
+
i += 1
|
396
|
+
|
397
|
+
if bp > 0:
|
398
|
+
iso += 1
|
399
|
+
# do actual compression
|
400
|
+
_set_hton_u16(sig_out, iso, nw)
|
401
|
+
iso += 1
|
402
|
+
bp = 0
|
403
|
+
if nb1 <= nb2:
|
404
|
+
# encode absolute values
|
405
|
+
_set_hton_u16(sig_out, iso, nb1)
|
406
|
+
iso += 1
|
407
|
+
_set_hton_u16(sig_out, iso, uint16(min1))
|
408
|
+
iso += 1
|
409
|
+
|
410
|
+
i = iso
|
411
|
+
while i <= (iso + nw * nb1 / 16):
|
412
|
+
_set_hton_u16(sig_out, i, 0)
|
413
|
+
i += 1
|
414
|
+
|
415
|
+
i = j
|
416
|
+
while i < j + nw:
|
417
|
+
dd = int16(sig_in[i] + shift) - min1 # value to encode
|
418
|
+
dd = dd << (32 - bp - nb1)
|
419
|
+
_set_hton_u16(
|
420
|
+
sig_out, iso, _get_hton_u16(sig_out, iso) | _get_high_u16(dd)
|
421
|
+
)
|
422
|
+
bp += nb1
|
423
|
+
if bp > 15:
|
424
|
+
iso += 1
|
425
|
+
_set_hton_u16(sig_out, iso, _get_low_u16(dd))
|
426
|
+
bp -= 16
|
427
|
+
i += 1
|
428
|
+
|
429
|
+
else:
|
430
|
+
# encode derivative / difference values
|
431
|
+
_set_hton_u16(sig_out, iso, nb2 + 32) # bits used for encoding, plus flag
|
432
|
+
iso += 1
|
433
|
+
_set_hton_u16(sig_out, iso, int16(si_j)) # starting signal value
|
434
|
+
iso += 1
|
435
|
+
_set_hton_u16(sig_out, iso, int16(min2)) # min value used for encoding
|
436
|
+
iso += 1
|
437
|
+
|
438
|
+
i = iso
|
439
|
+
while i <= iso + nw * nb2 / 16:
|
440
|
+
_set_hton_u16(sig_out, i, 0)
|
441
|
+
i += 1
|
442
|
+
|
443
|
+
i = j + 1
|
444
|
+
while i < j + nw:
|
445
|
+
si_i = int16(sig_in[i] + shift)
|
446
|
+
si_im1 = int16(sig_in[i - 1] + shift)
|
447
|
+
dd = si_i - si_im1 - min2
|
448
|
+
dd = dd << (32 - bp - nb2)
|
449
|
+
_set_hton_u16(
|
450
|
+
sig_out, iso, _get_hton_u16(sig_out, iso) | _get_high_u16(dd)
|
451
|
+
)
|
452
|
+
bp += nb2
|
453
|
+
if bp > 15:
|
454
|
+
iso += 1
|
455
|
+
_set_hton_u16(sig_out, iso, _get_low_u16(dd))
|
456
|
+
bp -= 16
|
457
|
+
i += 1
|
458
|
+
j += nw
|
459
|
+
|
460
|
+
if bp > 0:
|
461
|
+
iso += 1
|
462
|
+
|
463
|
+
if iso % 2 > 0:
|
464
|
+
iso += 1
|
465
|
+
|
466
|
+
return 2 * iso # number of bytes in compressed signal data
|
467
|
+
|
468
|
+
|
469
|
+
@numba.jit(nopython=True)
|
470
|
+
def _radware_sigcompress_decode(
|
471
|
+
sig_in: NDArray[ubyte],
|
472
|
+
sig_out: NDArray,
|
473
|
+
shift: int32,
|
474
|
+
_mask: NDArray[uint16] = _radware_sigcompress_mask,
|
475
|
+
) -> int32:
|
476
|
+
"""Deompress a digital signal.
|
477
|
+
|
478
|
+
After decoding, the signal values are shifted by ``-shift`` to restore the
|
479
|
+
original waveform. The dtype of `sig_out` must be large enough to contain it.
|
480
|
+
|
481
|
+
Almost literal translations of ``decompress_signal()`` from the
|
482
|
+
`radware-sigcompress` v1.0 C-code by David Radford [1]_. See
|
483
|
+
:func:`._radware_sigcompress_encode` for a list of changes to the original
|
484
|
+
algorithm.
|
485
|
+
|
486
|
+
Parameters
|
487
|
+
----------
|
488
|
+
sig_in
|
489
|
+
array holding the input, compressed signal. In the original code, an
|
490
|
+
array of 16-bit unsigned integers was expected.
|
491
|
+
sig_out
|
492
|
+
pre-allocated array for the decompressed signal. In the original code,
|
493
|
+
an array of 16-bit integers was expected.
|
494
|
+
|
495
|
+
Returns
|
496
|
+
-------
|
497
|
+
length
|
498
|
+
length of output, decompressed signal.
|
499
|
+
"""
|
500
|
+
mask = _mask
|
501
|
+
|
502
|
+
i = j = min_val = nb = isi = iso = nw = bp = int16(0)
|
503
|
+
dd = uint32(0)
|
504
|
+
|
505
|
+
sig_len_in = int(sig_in.size / 2)
|
506
|
+
siglen = int16(_get_hton_u16(sig_in, isi)) # signal length
|
507
|
+
isi += 1
|
508
|
+
|
509
|
+
while (isi < sig_len_in) and (iso < siglen):
|
510
|
+
if bp > 0:
|
511
|
+
isi += 1
|
512
|
+
bp = 0 # bit pointer
|
513
|
+
nw = _get_hton_u16(sig_in, isi) # number of samples encoded in this chunk
|
514
|
+
isi += 1
|
515
|
+
nb = _get_hton_u16(sig_in, isi) # number of bits used in compression
|
516
|
+
isi += 1
|
517
|
+
|
518
|
+
if nb < 32:
|
519
|
+
# decode absolute values
|
520
|
+
min_val = int16(_get_hton_u16(sig_in, isi)) # min value used for encoding
|
521
|
+
isi += 1
|
522
|
+
dd = _set_low_u16(dd, _get_hton_u16(sig_in, isi))
|
523
|
+
i = 0
|
524
|
+
while (i < nw) and (iso < siglen):
|
525
|
+
if (bp + nb) > 15:
|
526
|
+
bp -= 16
|
527
|
+
dd = _set_high_u16(dd, _get_hton_u16(sig_in, isi))
|
528
|
+
isi += 1
|
529
|
+
if isi < sig_len_in:
|
530
|
+
dd = _set_low_u16(dd, _get_hton_u16(sig_in, isi))
|
531
|
+
dd = dd << (bp + nb)
|
532
|
+
else:
|
533
|
+
dd = dd << nb
|
534
|
+
sig_out[iso] = (_get_high_u16(dd) & mask[nb]) + min_val - shift
|
535
|
+
iso += 1
|
536
|
+
bp += nb
|
537
|
+
i += 1
|
538
|
+
else:
|
539
|
+
nb -= 32
|
540
|
+
# decode derivative / difference values
|
541
|
+
sig_out[iso] = (
|
542
|
+
int16(_get_hton_u16(sig_in, isi)) - shift
|
543
|
+
) # starting signal value
|
544
|
+
iso += 1
|
545
|
+
isi += 1
|
546
|
+
min_val = int16(_get_hton_u16(sig_in, isi)) # min value used for encoding
|
547
|
+
isi += 1
|
548
|
+
if isi < sig_len_in:
|
549
|
+
dd = _set_low_u16(dd, _get_hton_u16(sig_in, isi))
|
550
|
+
|
551
|
+
i = 1
|
552
|
+
while (i < nw) and (iso < siglen):
|
553
|
+
if (bp + nb) > 15:
|
554
|
+
bp -= 16
|
555
|
+
dd = _set_high_u16(dd, _get_hton_u16(sig_in, isi))
|
556
|
+
isi += 1
|
557
|
+
if isi < sig_len_in:
|
558
|
+
dd = _set_low_u16(dd, _get_hton_u16(sig_in, isi))
|
559
|
+
dd = dd << (bp + nb)
|
560
|
+
else:
|
561
|
+
dd = dd << nb
|
562
|
+
sig_out[iso] = (
|
563
|
+
int16(
|
564
|
+
(_get_high_u16(dd) & mask[nb])
|
565
|
+
+ min_val
|
566
|
+
+ sig_out[iso - 1]
|
567
|
+
+ shift
|
568
|
+
)
|
569
|
+
- shift
|
570
|
+
)
|
571
|
+
iso += 1
|
572
|
+
bp += nb
|
573
|
+
i += 1
|
574
|
+
j += nw
|
575
|
+
|
576
|
+
if siglen != iso:
|
577
|
+
raise RuntimeError("failure: unexpected signal length after decompression")
|
578
|
+
|
579
|
+
return siglen # number of shorts in decompressed signal data
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import re
|
2
|
+
import sys
|
3
|
+
|
4
|
+
from .base import WaveformCodec
|
5
|
+
from .radware import RadwareSigcompress # noqa: F401
|
6
|
+
from .varlen import ULEB128ZigZagDiff # noqa: F401
|
7
|
+
|
8
|
+
|
9
|
+
def str2wfcodec(expr: str) -> WaveformCodec:
|
10
|
+
"""Eval strings containing :class:`.WaveformCodec` declarations.
|
11
|
+
|
12
|
+
Simple tool to avoid using :func:`eval`. Used to read
|
13
|
+
:class:`.WaveformCodec` declarations configured in JSON files.
|
14
|
+
"""
|
15
|
+
match = re.match(r"(\w+)\((.*)\)", expr.strip())
|
16
|
+
if match is None:
|
17
|
+
raise ValueError(f"invalid WaveformCodec expression '{expr}'")
|
18
|
+
|
19
|
+
match = match.groups()
|
20
|
+
codec = getattr(sys.modules[__name__], match[0].strip())
|
21
|
+
args = {}
|
22
|
+
|
23
|
+
if match[1]:
|
24
|
+
for items in match[1].split(","):
|
25
|
+
sp = items.split("=")
|
26
|
+
if len(sp) != 2:
|
27
|
+
raise ValueError(f"invalid WaveformCodec expression '{expr}'")
|
28
|
+
|
29
|
+
try:
|
30
|
+
args[sp[0].strip()] = float(sp[1].strip())
|
31
|
+
except ValueError:
|
32
|
+
args[sp[0].strip()] = sp[1].strip().strip("'\"")
|
33
|
+
|
34
|
+
return codec(**args)
|