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,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)