ezmsg-sigproc 2.5.0__py3-none-any.whl → 2.7.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.
- ezmsg/sigproc/__version__.py +2 -2
- ezmsg/sigproc/activation.py +5 -11
- ezmsg/sigproc/adaptive_lattice_notch.py +11 -30
- ezmsg/sigproc/affinetransform.py +16 -42
- ezmsg/sigproc/aggregate.py +17 -34
- ezmsg/sigproc/bandpower.py +12 -20
- ezmsg/sigproc/base.py +141 -1276
- ezmsg/sigproc/butterworthfilter.py +8 -16
- ezmsg/sigproc/butterworthzerophase.py +7 -16
- ezmsg/sigproc/cheby.py +4 -10
- ezmsg/sigproc/combfilter.py +5 -8
- ezmsg/sigproc/coordinatespaces.py +142 -0
- ezmsg/sigproc/decimate.py +3 -7
- ezmsg/sigproc/denormalize.py +6 -11
- ezmsg/sigproc/detrend.py +3 -4
- ezmsg/sigproc/diff.py +8 -17
- ezmsg/sigproc/downsample.py +11 -20
- ezmsg/sigproc/ewma.py +11 -28
- ezmsg/sigproc/ewmfilter.py +1 -1
- ezmsg/sigproc/extract_axis.py +3 -4
- ezmsg/sigproc/fbcca.py +34 -59
- ezmsg/sigproc/filter.py +19 -45
- ezmsg/sigproc/filterbank.py +37 -74
- ezmsg/sigproc/filterbankdesign.py +7 -14
- ezmsg/sigproc/fir_hilbert.py +13 -30
- ezmsg/sigproc/fir_pmc.py +5 -10
- ezmsg/sigproc/firfilter.py +12 -14
- ezmsg/sigproc/gaussiansmoothing.py +5 -9
- ezmsg/sigproc/kaiser.py +11 -15
- ezmsg/sigproc/math/abs.py +4 -3
- ezmsg/sigproc/math/add.py +121 -0
- ezmsg/sigproc/math/clip.py +4 -1
- ezmsg/sigproc/math/difference.py +100 -36
- ezmsg/sigproc/math/invert.py +3 -3
- ezmsg/sigproc/math/log.py +5 -6
- ezmsg/sigproc/math/scale.py +2 -0
- ezmsg/sigproc/messages.py +1 -2
- ezmsg/sigproc/quantize.py +3 -6
- ezmsg/sigproc/resample.py +17 -38
- ezmsg/sigproc/rollingscaler.py +12 -37
- ezmsg/sigproc/sampler.py +19 -37
- ezmsg/sigproc/scaler.py +11 -22
- ezmsg/sigproc/signalinjector.py +7 -18
- ezmsg/sigproc/slicer.py +14 -34
- ezmsg/sigproc/spectral.py +3 -3
- ezmsg/sigproc/spectrogram.py +12 -19
- ezmsg/sigproc/spectrum.py +17 -38
- ezmsg/sigproc/transpose.py +12 -24
- ezmsg/sigproc/util/asio.py +25 -156
- ezmsg/sigproc/util/axisarray_buffer.py +12 -26
- ezmsg/sigproc/util/buffer.py +22 -43
- ezmsg/sigproc/util/message.py +17 -31
- ezmsg/sigproc/util/profile.py +23 -174
- ezmsg/sigproc/util/sparse.py +7 -15
- ezmsg/sigproc/util/typeresolution.py +17 -83
- ezmsg/sigproc/wavelets.py +10 -19
- ezmsg/sigproc/window.py +29 -83
- ezmsg_sigproc-2.7.0.dist-info/METADATA +60 -0
- ezmsg_sigproc-2.7.0.dist-info/RECORD +64 -0
- ezmsg/sigproc/synth.py +0 -774
- ezmsg_sigproc-2.5.0.dist-info/METADATA +0 -72
- ezmsg_sigproc-2.5.0.dist-info/RECORD +0 -63
- {ezmsg_sigproc-2.5.0.dist-info → ezmsg_sigproc-2.7.0.dist-info}/WHEEL +0 -0
- /ezmsg_sigproc-2.5.0.dist-info/licenses/LICENSE.txt → /ezmsg_sigproc-2.7.0.dist-info/licenses/LICENSE +0 -0
ezmsg/sigproc/spectrogram.py
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
from typing import Generator
|
|
2
|
+
|
|
2
3
|
import ezmsg.core as ez
|
|
4
|
+
from ezmsg.baseproc import (
|
|
5
|
+
BaseStatefulProcessor,
|
|
6
|
+
BaseTransformerUnit,
|
|
7
|
+
CompositeProcessor,
|
|
8
|
+
)
|
|
3
9
|
from ezmsg.util.messages.axisarray import AxisArray
|
|
4
10
|
from ezmsg.util.messages.modify import modify_axis
|
|
5
11
|
|
|
6
|
-
from .window import Anchor, WindowTransformer
|
|
7
12
|
from .spectrum import (
|
|
8
|
-
WindowFunction,
|
|
9
|
-
SpectralTransform,
|
|
10
13
|
SpectralOutput,
|
|
14
|
+
SpectralTransform,
|
|
11
15
|
SpectrumTransformer,
|
|
16
|
+
WindowFunction,
|
|
12
17
|
)
|
|
13
|
-
from .
|
|
14
|
-
CompositeProcessor,
|
|
15
|
-
BaseStatefulProcessor,
|
|
16
|
-
BaseTransformerUnit,
|
|
17
|
-
)
|
|
18
|
+
from .window import Anchor, WindowTransformer
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class SpectrogramSettings(ez.Settings):
|
|
@@ -41,9 +42,7 @@ class SpectrogramSettings(ez.Settings):
|
|
|
41
42
|
"""The :obj:`SpectralOutput` format."""
|
|
42
43
|
|
|
43
44
|
|
|
44
|
-
class SpectrogramTransformer(
|
|
45
|
-
CompositeProcessor[SpectrogramSettings, AxisArray, AxisArray]
|
|
46
|
-
):
|
|
45
|
+
class SpectrogramTransformer(CompositeProcessor[SpectrogramSettings, AxisArray, AxisArray]):
|
|
47
46
|
@staticmethod
|
|
48
47
|
def _initialize_processors(
|
|
49
48
|
settings: SpectrogramSettings,
|
|
@@ -54,9 +53,7 @@ class SpectrogramTransformer(
|
|
|
54
53
|
newaxis="win",
|
|
55
54
|
window_dur=settings.window_dur,
|
|
56
55
|
window_shift=settings.window_shift,
|
|
57
|
-
zero_pad_until="shift"
|
|
58
|
-
if settings.window_shift is not None
|
|
59
|
-
else "input",
|
|
56
|
+
zero_pad_until="shift" if settings.window_shift is not None else "input",
|
|
60
57
|
anchor=settings.window_anchor,
|
|
61
58
|
),
|
|
62
59
|
"spectrum": SpectrumTransformer(
|
|
@@ -69,11 +66,7 @@ class SpectrogramTransformer(
|
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
|
|
72
|
-
class Spectrogram(
|
|
73
|
-
BaseTransformerUnit[
|
|
74
|
-
SpectrogramSettings, AxisArray, AxisArray, SpectrogramTransformer
|
|
75
|
-
]
|
|
76
|
-
):
|
|
69
|
+
class Spectrogram(BaseTransformerUnit[SpectrogramSettings, AxisArray, AxisArray, SpectrogramTransformer]):
|
|
77
70
|
SETTINGS = SpectrogramSettings
|
|
78
71
|
|
|
79
72
|
|
ezmsg/sigproc/spectrum.py
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import enum
|
|
2
|
-
from functools import partial
|
|
3
2
|
import typing
|
|
3
|
+
from functools import partial
|
|
4
4
|
|
|
5
|
+
import ezmsg.core as ez
|
|
5
6
|
import numpy as np
|
|
6
7
|
import numpy.typing as npt
|
|
7
|
-
|
|
8
|
-
from ezmsg.util.messages.axisarray import (
|
|
9
|
-
AxisArray,
|
|
10
|
-
slice_along_axis,
|
|
11
|
-
replace,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
from .base import (
|
|
8
|
+
from ezmsg.baseproc import (
|
|
15
9
|
BaseStatefulTransformer,
|
|
16
10
|
BaseTransformerUnit,
|
|
17
11
|
processor_state,
|
|
18
12
|
)
|
|
13
|
+
from ezmsg.util.messages.axisarray import (
|
|
14
|
+
AxisArray,
|
|
15
|
+
replace,
|
|
16
|
+
slice_along_axis,
|
|
17
|
+
)
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
class OptionsEnum(enum.Enum):
|
|
@@ -127,17 +126,13 @@ class SpectrumState:
|
|
|
127
126
|
window: npt.NDArray | None = None
|
|
128
127
|
|
|
129
128
|
|
|
130
|
-
class SpectrumTransformer(
|
|
131
|
-
BaseStatefulTransformer[SpectrumSettings, AxisArray, AxisArray, SpectrumState]
|
|
132
|
-
):
|
|
129
|
+
class SpectrumTransformer(BaseStatefulTransformer[SpectrumSettings, AxisArray, AxisArray, SpectrumState]):
|
|
133
130
|
def _hash_message(self, message: AxisArray) -> int:
|
|
134
131
|
axis = self.settings.axis or message.dims[0]
|
|
135
132
|
ax_idx = message.get_axis_idx(axis)
|
|
136
133
|
ax_info = message.axes[axis]
|
|
137
134
|
targ_len = message.data.shape[ax_idx]
|
|
138
|
-
return hash(
|
|
139
|
-
(targ_len, message.data.ndim, message.data.dtype.kind, ax_idx, ax_info.gain)
|
|
140
|
-
)
|
|
135
|
+
return hash((targ_len, message.data.ndim, message.data.dtype.kind, ax_idx, ax_info.gain))
|
|
141
136
|
|
|
142
137
|
def _reset_state(self, message: AxisArray) -> None:
|
|
143
138
|
axis = self.settings.axis or message.dims[0]
|
|
@@ -156,8 +151,7 @@ class SpectrumTransformer(
|
|
|
156
151
|
+ [1] * (message.data.ndim - 1 - ax_idx)
|
|
157
152
|
)
|
|
158
153
|
if self.settings.transform != SpectralTransform.RAW_COMPLEX and not (
|
|
159
|
-
self.settings.transform == SpectralTransform.REAL
|
|
160
|
-
or self.settings.transform == SpectralTransform.IMAG
|
|
154
|
+
self.settings.transform == SpectralTransform.REAL or self.settings.transform == SpectralTransform.IMAG
|
|
161
155
|
):
|
|
162
156
|
scale = np.sum(window**2.0) * ax_info.gain
|
|
163
157
|
|
|
@@ -170,30 +164,21 @@ class SpectrumTransformer(
|
|
|
170
164
|
if (not b_complex) and self.settings.output == SpectralOutput.POSITIVE:
|
|
171
165
|
# If input is not complex and desired output is SpectralOutput.POSITIVE, we can save some computation
|
|
172
166
|
# by using rfft and rfftfreq.
|
|
173
|
-
self.state.fftfun = partial(
|
|
174
|
-
np.fft.rfft, n=nfft, axis=ax_idx, norm=self.settings.norm
|
|
175
|
-
)
|
|
167
|
+
self.state.fftfun = partial(np.fft.rfft, n=nfft, axis=ax_idx, norm=self.settings.norm)
|
|
176
168
|
freqs = np.fft.rfftfreq(nfft, d=ax_info.gain * targ_len / nfft)
|
|
177
169
|
else:
|
|
178
|
-
self.state.fftfun = partial(
|
|
179
|
-
np.fft.fft, n=nfft, axis=ax_idx, norm=self.settings.norm
|
|
180
|
-
)
|
|
170
|
+
self.state.fftfun = partial(np.fft.fft, n=nfft, axis=ax_idx, norm=self.settings.norm)
|
|
181
171
|
freqs = np.fft.fftfreq(nfft, d=ax_info.gain * targ_len / nfft)
|
|
182
172
|
if self.settings.output == SpectralOutput.POSITIVE:
|
|
183
173
|
self.state.f_sl = slice(None, nfft // 2 + 1 - (nfft % 2))
|
|
184
174
|
elif self.settings.output == SpectralOutput.NEGATIVE:
|
|
185
175
|
freqs = np.fft.fftshift(freqs, axes=-1)
|
|
186
176
|
self.state.f_sl = slice(None, nfft // 2 + 1)
|
|
187
|
-
elif
|
|
188
|
-
self.settings.do_fftshift
|
|
189
|
-
and self.settings.output == SpectralOutput.FULL
|
|
190
|
-
):
|
|
177
|
+
elif self.settings.do_fftshift and self.settings.output == SpectralOutput.FULL:
|
|
191
178
|
freqs = np.fft.fftshift(freqs, axes=-1)
|
|
192
179
|
freqs = freqs[self.state.f_sl]
|
|
193
180
|
freqs = freqs.tolist() # To please type checking
|
|
194
|
-
self.state.freq_axis = AxisArray.LinearAxis(
|
|
195
|
-
unit="Hz", gain=freqs[1] - freqs[0], offset=freqs[0]
|
|
196
|
-
)
|
|
181
|
+
self.state.freq_axis = AxisArray.LinearAxis(unit="Hz", gain=freqs[1] - freqs[0], offset=freqs[0])
|
|
197
182
|
self.state.new_dims = (
|
|
198
183
|
message.dims[:ax_idx]
|
|
199
184
|
+ [
|
|
@@ -232,11 +217,7 @@ class SpectrumTransformer(
|
|
|
232
217
|
ax_idx = message.get_axis_idx(axis)
|
|
233
218
|
targ_len = message.data.shape[ax_idx]
|
|
234
219
|
|
|
235
|
-
new_axes = {
|
|
236
|
-
k: v
|
|
237
|
-
for k, v in message.axes.items()
|
|
238
|
-
if k not in [self.settings.out_axis, axis]
|
|
239
|
-
}
|
|
220
|
+
new_axes = {k: v for k, v in message.axes.items() if k not in [self.settings.out_axis, axis]}
|
|
240
221
|
new_axes[self.settings.out_axis or axis] = self.state.freq_axis
|
|
241
222
|
|
|
242
223
|
if self.state.window is not None:
|
|
@@ -261,9 +242,7 @@ class SpectrumTransformer(
|
|
|
261
242
|
return msg_out
|
|
262
243
|
|
|
263
244
|
|
|
264
|
-
class Spectrum(
|
|
265
|
-
BaseTransformerUnit[SpectrumSettings, AxisArray, AxisArray, SpectrumTransformer]
|
|
266
|
-
):
|
|
245
|
+
class Spectrum(BaseTransformerUnit[SpectrumSettings, AxisArray, AxisArray, SpectrumTransformer]):
|
|
267
246
|
SETTINGS = SpectrumSettings
|
|
268
247
|
|
|
269
248
|
|
ezmsg/sigproc/transpose.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
from types import EllipsisType
|
|
2
|
-
import numpy as np
|
|
3
|
-
import ezmsg.core as ez
|
|
4
|
-
from ezmsg.util.messages.axisarray import (
|
|
5
|
-
AxisArray,
|
|
6
|
-
replace,
|
|
7
|
-
)
|
|
8
2
|
|
|
9
|
-
|
|
3
|
+
import ezmsg.core as ez
|
|
4
|
+
import numpy as np
|
|
5
|
+
from ezmsg.baseproc import (
|
|
10
6
|
BaseStatefulTransformer,
|
|
11
7
|
BaseTransformerUnit,
|
|
12
8
|
processor_state,
|
|
13
9
|
)
|
|
10
|
+
from ezmsg.util.messages.axisarray import (
|
|
11
|
+
AxisArray,
|
|
12
|
+
replace,
|
|
13
|
+
)
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class TransposeSettings(ez.Settings):
|
|
@@ -30,9 +30,7 @@ class TransposeState:
|
|
|
30
30
|
axes_ints: tuple[int, ...] | None = None
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
class TransposeTransformer(
|
|
34
|
-
BaseStatefulTransformer[TransposeSettings, AxisArray, AxisArray, TransposeState]
|
|
35
|
-
):
|
|
33
|
+
class TransposeTransformer(BaseStatefulTransformer[TransposeSettings, AxisArray, AxisArray, TransposeState]):
|
|
36
34
|
"""
|
|
37
35
|
Downsampled data simply comprise every `factor`th sample.
|
|
38
36
|
This should only be used following appropriate lowpass filtering.
|
|
@@ -67,11 +65,7 @@ class TransposeTransformer(
|
|
|
67
65
|
if ax not in message.dims:
|
|
68
66
|
raise ValueError(f"Axis {ax} not found in message dims.")
|
|
69
67
|
suffix.append(message.dims.index(ax))
|
|
70
|
-
ells = [
|
|
71
|
-
_
|
|
72
|
-
for _ in range(message.data.ndim)
|
|
73
|
-
if _ not in prefix and _ not in suffix
|
|
74
|
-
]
|
|
68
|
+
ells = [_ for _ in range(message.data.ndim) if _ not in prefix and _ not in suffix]
|
|
75
69
|
re_ix = tuple(prefix + ells + suffix)
|
|
76
70
|
if re_ix == tuple(range(message.data.ndim)):
|
|
77
71
|
self._state.axes_ints = None
|
|
@@ -100,17 +94,13 @@ class TransposeTransformer(
|
|
|
100
94
|
# If the memory is already contiguous in the correct order, np.require won't do anything.
|
|
101
95
|
msg_out = replace(
|
|
102
96
|
message,
|
|
103
|
-
data=np.require(
|
|
104
|
-
message.data, requirements=self.settings.order.upper()[0]
|
|
105
|
-
),
|
|
97
|
+
data=np.require(message.data, requirements=self.settings.order.upper()[0]),
|
|
106
98
|
)
|
|
107
99
|
else:
|
|
108
100
|
dims_out = [message.dims[ix] for ix in self.state.axes_ints]
|
|
109
101
|
data_out = np.transpose(message.data, axes=self.state.axes_ints)
|
|
110
102
|
if self.settings.order is not None:
|
|
111
|
-
data_out = np.require(
|
|
112
|
-
data_out, requirements=self.settings.order.upper()[0]
|
|
113
|
-
)
|
|
103
|
+
data_out = np.require(data_out, requirements=self.settings.order.upper()[0])
|
|
114
104
|
msg_out = replace(
|
|
115
105
|
message,
|
|
116
106
|
data=data_out,
|
|
@@ -119,9 +109,7 @@ class TransposeTransformer(
|
|
|
119
109
|
return msg_out
|
|
120
110
|
|
|
121
111
|
|
|
122
|
-
class Transpose(
|
|
123
|
-
BaseTransformerUnit[TransposeSettings, AxisArray, AxisArray, TransposeTransformer]
|
|
124
|
-
):
|
|
112
|
+
class Transpose(BaseTransformerUnit[TransposeSettings, AxisArray, AxisArray, TransposeTransformer]):
|
|
125
113
|
SETTINGS = TransposeSettings
|
|
126
114
|
|
|
127
115
|
|
ezmsg/sigproc/util/asio.py
CHANGED
|
@@ -1,156 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
The result of the coroutine execution
|
|
27
|
-
|
|
28
|
-
Raises:
|
|
29
|
-
CoroutineExecutionError: If execution fails due to threading or event loop issues
|
|
30
|
-
TimeoutError: If execution exceeds the timeout period
|
|
31
|
-
Exception: Any exception raised by the coroutine
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
def run_in_new_loop() -> T:
|
|
35
|
-
"""
|
|
36
|
-
Creates and runs a new event loop in the current thread.
|
|
37
|
-
Ensures proper cleanup of the loop.
|
|
38
|
-
"""
|
|
39
|
-
new_loop = asyncio.new_event_loop()
|
|
40
|
-
asyncio.set_event_loop(new_loop)
|
|
41
|
-
try:
|
|
42
|
-
return new_loop.run_until_complete(
|
|
43
|
-
asyncio.wait_for(coroutine, timeout=timeout)
|
|
44
|
-
)
|
|
45
|
-
finally:
|
|
46
|
-
with contextlib.suppress(Exception):
|
|
47
|
-
# Clean up any pending tasks
|
|
48
|
-
pending = asyncio.all_tasks(new_loop)
|
|
49
|
-
for task in pending:
|
|
50
|
-
task.cancel()
|
|
51
|
-
new_loop.run_until_complete(
|
|
52
|
-
asyncio.gather(*pending, return_exceptions=True)
|
|
53
|
-
)
|
|
54
|
-
new_loop.close()
|
|
55
|
-
|
|
56
|
-
try:
|
|
57
|
-
loop = asyncio.get_running_loop()
|
|
58
|
-
except RuntimeError:
|
|
59
|
-
try:
|
|
60
|
-
return asyncio.run(asyncio.wait_for(coroutine, timeout=timeout))
|
|
61
|
-
except Exception as e:
|
|
62
|
-
raise CoroutineExecutionError(
|
|
63
|
-
f"Failed to execute coroutine: {str(e)}"
|
|
64
|
-
) from e
|
|
65
|
-
|
|
66
|
-
if threading.current_thread() is threading.main_thread():
|
|
67
|
-
if not loop.is_running():
|
|
68
|
-
try:
|
|
69
|
-
return loop.run_until_complete(
|
|
70
|
-
asyncio.wait_for(coroutine, timeout=timeout)
|
|
71
|
-
)
|
|
72
|
-
except Exception as e:
|
|
73
|
-
raise CoroutineExecutionError(
|
|
74
|
-
f"Failed to execute coroutine in main loop: {str(e)}"
|
|
75
|
-
) from e
|
|
76
|
-
else:
|
|
77
|
-
with ThreadPoolExecutor() as pool:
|
|
78
|
-
try:
|
|
79
|
-
future = pool.submit(run_in_new_loop)
|
|
80
|
-
return future.result(timeout=timeout)
|
|
81
|
-
except Exception as e:
|
|
82
|
-
raise CoroutineExecutionError(
|
|
83
|
-
f"Failed to execute coroutine in thread: {str(e)}"
|
|
84
|
-
) from e
|
|
85
|
-
else:
|
|
86
|
-
try:
|
|
87
|
-
future = asyncio.run_coroutine_threadsafe(coroutine, loop)
|
|
88
|
-
return future.result(timeout=timeout)
|
|
89
|
-
except Exception as e:
|
|
90
|
-
raise CoroutineExecutionError(
|
|
91
|
-
f"Failed to execute coroutine threadsafe: {str(e)}"
|
|
92
|
-
) from e
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class SyncToAsyncGeneratorWrapper:
|
|
96
|
-
"""
|
|
97
|
-
A wrapper for synchronous generators to be used in an async context.
|
|
98
|
-
"""
|
|
99
|
-
|
|
100
|
-
def __init__(self, gen):
|
|
101
|
-
self._gen = gen
|
|
102
|
-
self._closed = False
|
|
103
|
-
# Prime the generator to ready for first send/next call
|
|
104
|
-
try:
|
|
105
|
-
is_not_primed = inspect.getgeneratorstate(self._gen) is inspect.GEN_CREATED
|
|
106
|
-
except AttributeError as e:
|
|
107
|
-
raise TypeError(
|
|
108
|
-
"The provided generator is not a valid generator object"
|
|
109
|
-
) from e
|
|
110
|
-
if is_not_primed:
|
|
111
|
-
try:
|
|
112
|
-
next(self._gen)
|
|
113
|
-
except StopIteration:
|
|
114
|
-
self._closed = True
|
|
115
|
-
except Exception as e:
|
|
116
|
-
raise RuntimeError(f"Failed to prime generator: {e}") from e
|
|
117
|
-
|
|
118
|
-
async def asend(self, value):
|
|
119
|
-
if self._closed:
|
|
120
|
-
raise StopAsyncIteration("Generator is closed")
|
|
121
|
-
try:
|
|
122
|
-
return await asyncio.to_thread(self._gen.send, value)
|
|
123
|
-
except StopIteration as e:
|
|
124
|
-
self._closed = True
|
|
125
|
-
raise StopAsyncIteration("Generator is closed") from e
|
|
126
|
-
except Exception as e:
|
|
127
|
-
raise RuntimeError(f"Error while sending value to generator: {e}") from e
|
|
128
|
-
|
|
129
|
-
async def __anext__(self):
|
|
130
|
-
if self._closed:
|
|
131
|
-
raise StopAsyncIteration("Generator is closed")
|
|
132
|
-
try:
|
|
133
|
-
return await asyncio.to_thread(self._gen.__next__)
|
|
134
|
-
except StopIteration as e:
|
|
135
|
-
self._closed = True
|
|
136
|
-
raise StopAsyncIteration("Generator is closed") from e
|
|
137
|
-
except Exception as e:
|
|
138
|
-
raise RuntimeError(
|
|
139
|
-
f"Error while getting next value from generator: {e}"
|
|
140
|
-
) from e
|
|
141
|
-
|
|
142
|
-
async def aclose(self):
|
|
143
|
-
if self._closed:
|
|
144
|
-
return
|
|
145
|
-
try:
|
|
146
|
-
await asyncio.to_thread(self._gen.close)
|
|
147
|
-
except Exception as e:
|
|
148
|
-
raise RuntimeError(f"Error while closing generator: {e}") from e
|
|
149
|
-
finally:
|
|
150
|
-
self._closed = True
|
|
151
|
-
|
|
152
|
-
def __aiter__(self):
|
|
153
|
-
return self
|
|
154
|
-
|
|
155
|
-
def __getattr__(self, name):
|
|
156
|
-
return getattr(self._gen, name)
|
|
1
|
+
"""
|
|
2
|
+
Backwards-compatible re-exports from ezmsg.baseproc.util.asio.
|
|
3
|
+
|
|
4
|
+
New code should import directly from ezmsg.baseproc instead.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import warnings
|
|
8
|
+
|
|
9
|
+
warnings.warn(
|
|
10
|
+
"Importing from 'ezmsg.sigproc.util.asio' is deprecated. Please import from 'ezmsg.baseproc.util.asio' instead.",
|
|
11
|
+
DeprecationWarning,
|
|
12
|
+
stacklevel=2,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from ezmsg.baseproc.util.asio import ( # noqa: E402
|
|
16
|
+
CoroutineExecutionError,
|
|
17
|
+
SyncToAsyncGeneratorWrapper,
|
|
18
|
+
run_coroutine_sync,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"CoroutineExecutionError",
|
|
23
|
+
"SyncToAsyncGeneratorWrapper",
|
|
24
|
+
"run_coroutine_sync",
|
|
25
|
+
]
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
"""AxisArray support for .buffer.HybridBuffer."""
|
|
2
|
+
|
|
1
3
|
import math
|
|
2
4
|
import typing
|
|
3
5
|
|
|
4
|
-
from array_api_compat import get_namespace
|
|
5
6
|
import numpy as np
|
|
6
|
-
from
|
|
7
|
+
from array_api_compat import get_namespace
|
|
8
|
+
from ezmsg.util.messages.axisarray import AxisArray, CoordinateAxis, LinearAxis
|
|
7
9
|
from ezmsg.util.messages.util import replace
|
|
8
10
|
|
|
9
11
|
from .buffer import HybridBuffer
|
|
10
12
|
|
|
11
|
-
|
|
12
13
|
Array = typing.TypeVar("Array")
|
|
13
14
|
|
|
14
15
|
|
|
@@ -68,9 +69,7 @@ class HybridAxisBuffer:
|
|
|
68
69
|
if hasattr(first_axis, "data"):
|
|
69
70
|
# Initialize a CoordinateAxis buffer
|
|
70
71
|
if len(first_axis.data) > 1:
|
|
71
|
-
_axis_gain = (first_axis.data[-1] - first_axis.data[0]) / (
|
|
72
|
-
len(first_axis.data) - 1
|
|
73
|
-
)
|
|
72
|
+
_axis_gain = (first_axis.data[-1] - first_axis.data[0]) / (len(first_axis.data) - 1)
|
|
74
73
|
else:
|
|
75
74
|
_axis_gain = 1.0
|
|
76
75
|
self._coords_gain_estimate = _axis_gain
|
|
@@ -107,8 +106,7 @@ class HybridAxisBuffer:
|
|
|
107
106
|
)
|
|
108
107
|
if axis.gain != self._linear_axis.gain:
|
|
109
108
|
raise ValueError(
|
|
110
|
-
f"Buffer initialized with gain={self._linear_axis.gain}, "
|
|
111
|
-
f"but received gain={axis.gain}."
|
|
109
|
+
f"Buffer initialized with gain={self._linear_axis.gain}, but received gain={axis.gain}."
|
|
112
110
|
)
|
|
113
111
|
if self._linear_n_available + n_samples > self.capacity:
|
|
114
112
|
# Simulate overflow by advancing the offset and decreasing
|
|
@@ -117,16 +115,12 @@ class HybridAxisBuffer:
|
|
|
117
115
|
self.seek(n_to_discard)
|
|
118
116
|
# Update the offset corresponding to the oldest sample in the buffer
|
|
119
117
|
# by anchoring on the new offset and accounting for the samples already available.
|
|
120
|
-
self._linear_axis.offset =
|
|
121
|
-
axis.offset - self._linear_n_available * axis.gain
|
|
122
|
-
)
|
|
118
|
+
self._linear_axis.offset = axis.offset - self._linear_n_available * axis.gain
|
|
123
119
|
self._linear_n_available += n_samples
|
|
124
120
|
|
|
125
121
|
def peek(self, n_samples: int | None = None) -> LinearAxis | CoordinateAxis:
|
|
126
122
|
if self._coords_buffer is not None:
|
|
127
|
-
return replace(
|
|
128
|
-
self._coords_template, data=self._coords_buffer.peek(n_samples)
|
|
129
|
-
)
|
|
123
|
+
return replace(self._coords_template, data=self._coords_buffer.peek(n_samples))
|
|
130
124
|
else:
|
|
131
125
|
# Return a shallow copy.
|
|
132
126
|
return replace(self._linear_axis, offset=self._linear_axis.offset)
|
|
@@ -184,13 +178,9 @@ class HybridAxisBuffer:
|
|
|
184
178
|
else:
|
|
185
179
|
return None
|
|
186
180
|
|
|
187
|
-
def searchsorted(
|
|
188
|
-
self, values: typing.Union[float, Array], side: str = "left"
|
|
189
|
-
) -> typing.Union[int, Array]:
|
|
181
|
+
def searchsorted(self, values: typing.Union[float, Array], side: str = "left") -> typing.Union[int, Array]:
|
|
190
182
|
if self._coords_buffer is not None:
|
|
191
|
-
return self._coords_buffer.xp.searchsorted(
|
|
192
|
-
self._coords_buffer.peek(self.available()), values, side=side
|
|
193
|
-
)
|
|
183
|
+
return self._coords_buffer.xp.searchsorted(self._coords_buffer.peek(self.available()), values, side=side)
|
|
194
184
|
else:
|
|
195
185
|
if self.available() == 0:
|
|
196
186
|
if isinstance(values, float):
|
|
@@ -312,9 +302,7 @@ class HybridAxisArrayBuffer:
|
|
|
312
302
|
axes={**self._template_msg.axes, self._axis: out_axis},
|
|
313
303
|
)
|
|
314
304
|
|
|
315
|
-
def peek_axis(
|
|
316
|
-
self, n_samples: int | None = None
|
|
317
|
-
) -> LinearAxis | CoordinateAxis | None:
|
|
305
|
+
def peek_axis(self, n_samples: int | None = None) -> LinearAxis | CoordinateAxis | None:
|
|
318
306
|
"""Retrieves the axis data without advancing the read head."""
|
|
319
307
|
if self._data_buffer is None:
|
|
320
308
|
return None
|
|
@@ -369,9 +357,7 @@ class HybridAxisArrayBuffer:
|
|
|
369
357
|
"""
|
|
370
358
|
return self._axis_buffer.gain
|
|
371
359
|
|
|
372
|
-
def axis_searchsorted(
|
|
373
|
-
self, values: typing.Union[float, Array], side: str = "left"
|
|
374
|
-
) -> typing.Union[int, Array]:
|
|
360
|
+
def axis_searchsorted(self, values: typing.Union[float, Array], side: str = "left") -> typing.Union[int, Array]:
|
|
375
361
|
"""
|
|
376
362
|
Find the indices into which the given values would be inserted
|
|
377
363
|
into the target axis data to maintain order.
|