ezmsg-sigproc 1.2.3__py3-none-any.whl → 1.3.2__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/__init__.py +1 -4
- ezmsg/sigproc/__version__.py +16 -0
- ezmsg/sigproc/activation.py +75 -0
- ezmsg/sigproc/affinetransform.py +149 -39
- ezmsg/sigproc/aggregate.py +84 -29
- ezmsg/sigproc/bandpower.py +36 -15
- ezmsg/sigproc/base.py +38 -0
- ezmsg/sigproc/butterworthfilter.py +76 -20
- ezmsg/sigproc/decimate.py +7 -4
- ezmsg/sigproc/downsample.py +79 -61
- ezmsg/sigproc/ewmfilter.py +28 -14
- ezmsg/sigproc/filter.py +51 -31
- ezmsg/sigproc/filterbank.py +278 -0
- ezmsg/sigproc/math/__init__.py +0 -0
- ezmsg/sigproc/math/abs.py +28 -0
- ezmsg/sigproc/math/clip.py +30 -0
- ezmsg/sigproc/math/difference.py +60 -0
- ezmsg/sigproc/math/invert.py +29 -0
- ezmsg/sigproc/math/log.py +32 -0
- ezmsg/sigproc/math/scale.py +31 -0
- ezmsg/sigproc/messages.py +2 -3
- ezmsg/sigproc/sampler.py +152 -90
- ezmsg/sigproc/scaler.py +88 -42
- ezmsg/sigproc/signalinjector.py +7 -10
- ezmsg/sigproc/slicer.py +71 -36
- ezmsg/sigproc/spectral.py +6 -9
- ezmsg/sigproc/spectrogram.py +48 -30
- ezmsg/sigproc/spectrum.py +177 -76
- ezmsg/sigproc/synth.py +162 -67
- ezmsg/sigproc/wavelets.py +167 -0
- ezmsg/sigproc/window.py +193 -157
- ezmsg_sigproc-1.3.2.dist-info/METADATA +59 -0
- ezmsg_sigproc-1.3.2.dist-info/RECORD +35 -0
- {ezmsg_sigproc-1.2.3.dist-info → ezmsg_sigproc-1.3.2.dist-info}/WHEEL +1 -1
- ezmsg_sigproc-1.2.3.dist-info/METADATA +0 -38
- ezmsg_sigproc-1.2.3.dist-info/RECORD +0 -23
- {ezmsg_sigproc-1.2.3.dist-info → ezmsg_sigproc-1.3.2.dist-info/licenses}/LICENSE.txt +0 -0
ezmsg/sigproc/window.py
CHANGED
|
@@ -1,121 +1,147 @@
|
|
|
1
1
|
from dataclasses import replace
|
|
2
2
|
import traceback
|
|
3
|
-
|
|
3
|
+
import typing
|
|
4
4
|
|
|
5
5
|
import ezmsg.core as ez
|
|
6
6
|
import numpy as np
|
|
7
7
|
import numpy.typing as npt
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
from ezmsg.util.messages.axisarray import (
|
|
9
|
+
AxisArray,
|
|
10
|
+
slice_along_axis,
|
|
11
|
+
sliding_win_oneaxis,
|
|
12
|
+
)
|
|
10
13
|
from ezmsg.util.generator import consumer
|
|
11
14
|
|
|
15
|
+
from .base import GenAxisArray
|
|
16
|
+
|
|
12
17
|
|
|
13
18
|
@consumer
|
|
14
19
|
def windowing(
|
|
15
|
-
axis: Optional[str] = None,
|
|
16
|
-
newaxis:
|
|
17
|
-
window_dur: Optional[float] = None,
|
|
18
|
-
window_shift: Optional[float] = None,
|
|
19
|
-
zero_pad_until: str = "input"
|
|
20
|
-
) -> Generator[AxisArray,
|
|
20
|
+
axis: typing.Optional[str] = None,
|
|
21
|
+
newaxis: str = "win",
|
|
22
|
+
window_dur: typing.Optional[float] = None,
|
|
23
|
+
window_shift: typing.Optional[float] = None,
|
|
24
|
+
zero_pad_until: str = "input",
|
|
25
|
+
) -> typing.Generator[AxisArray, AxisArray, None]:
|
|
21
26
|
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
If
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
Construct a generator that yields windows of data from an input :obj:`AxisArray`.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
axis: The axis along which to segment windows.
|
|
31
|
+
If None, defaults to the first dimension of the first seen AxisArray.
|
|
32
|
+
newaxis: New axis on which windows are delimited, immediately
|
|
33
|
+
preceding the target windowed axis. The data length along newaxis may be 0 if
|
|
34
|
+
this most recent push did not provide enough data for a new window.
|
|
35
|
+
If window_shift is None then the newaxis length will always be 1.
|
|
36
|
+
window_dur: The duration of the window in seconds.
|
|
37
|
+
If None, the function acts as a passthrough and all other parameters are ignored.
|
|
38
|
+
window_shift: The shift of the window in seconds.
|
|
39
|
+
If None (default), windowing operates in "1:1 mode", where each input yields exactly one most-recent window.
|
|
40
|
+
zero_pad_until: Determines how the function initializes the buffer.
|
|
41
|
+
Can be one of "input" (default), "full", "shift", or "none". If `window_shift` is None then this field is
|
|
42
|
+
ignored and "input" is always used.
|
|
43
|
+
|
|
44
|
+
- "input" (default) initializes the buffer with the input then prepends with zeros to the window size.
|
|
45
|
+
The first input will always yield at least one output.
|
|
46
|
+
- "shift" fills the buffer until `window_shift`.
|
|
47
|
+
No outputs will be yielded until at least `window_shift` data has been seen.
|
|
48
|
+
- "none" does not pad the buffer. No outputs will be yielded until at least `window_dur` data has been seen.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
40
51
|
A (primed) generator that accepts .send(an AxisArray object) and yields a list of windowed
|
|
41
52
|
AxisArray objects. The list will always be length-1 if `newaxis` is not None or `window_shift` is None.
|
|
42
53
|
"""
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
# Check arguments
|
|
55
|
+
if newaxis is None:
|
|
56
|
+
ez.logger.warning("`newaxis` must not be None. Setting to 'win'.")
|
|
57
|
+
newaxis = "win"
|
|
46
58
|
if window_shift is None and zero_pad_until != "input":
|
|
47
|
-
ez.logger.warning(
|
|
48
|
-
|
|
59
|
+
ez.logger.warning(
|
|
60
|
+
"`zero_pad_until` must be 'input' if `window_shift` is None. "
|
|
61
|
+
f"Ignoring received argument value: {zero_pad_until}"
|
|
62
|
+
)
|
|
49
63
|
zero_pad_until = "input"
|
|
50
64
|
elif window_shift is not None and zero_pad_until == "input":
|
|
51
|
-
ez.logger.warning(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
ez.logger.warning(
|
|
66
|
+
"windowing is non-deterministic with `zero_pad_until='input'` as it depends on the size "
|
|
67
|
+
"of the first input. We recommend using 'shift' when `window_shift` is float-valued."
|
|
68
|
+
)
|
|
69
|
+
msg_out = AxisArray(np.array([]), dims=[""])
|
|
55
70
|
|
|
56
71
|
# State variables
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
buffer: typing.Optional[npt.NDArray] = None
|
|
73
|
+
window_samples: typing.Optional[int] = None
|
|
74
|
+
window_shift_samples: typing.Optional[int] = None
|
|
75
|
+
# Number of incoming samples to ignore. Only relevant when shift > window.:
|
|
76
|
+
shift_deficit: int = 0
|
|
77
|
+
b_1to1 = window_shift is None
|
|
78
|
+
newaxis_warned: bool = b_1to1
|
|
79
|
+
out_newaxis: typing.Optional[AxisArray.Axis] = None
|
|
80
|
+
out_dims: typing.typing.Optional[typing.List[str]] = None
|
|
81
|
+
|
|
82
|
+
check_inputs = {"samp_shape": None, "fs": None, "key": None}
|
|
66
83
|
|
|
67
84
|
while True:
|
|
68
|
-
|
|
85
|
+
msg_in: AxisArray = yield msg_out
|
|
69
86
|
|
|
70
87
|
if window_dur is None:
|
|
71
|
-
|
|
88
|
+
msg_out = msg_in
|
|
72
89
|
continue
|
|
73
90
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
axis_info = axis_arr_in.get_axis(axis)
|
|
91
|
+
axis = axis or msg_in.dims[0]
|
|
92
|
+
axis_idx = msg_in.get_axis_idx(axis)
|
|
93
|
+
axis_info = msg_in.get_axis(axis)
|
|
78
94
|
fs = 1.0 / axis_info.gain
|
|
79
95
|
|
|
80
|
-
if
|
|
81
|
-
ez.logger.warning(
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
if not newaxis_warned and newaxis in msg_in.dims:
|
|
97
|
+
ez.logger.warning(
|
|
98
|
+
f"newaxis {newaxis} present in input dims. Using {newaxis}_win instead"
|
|
99
|
+
)
|
|
100
|
+
newaxis_warned = True
|
|
101
|
+
newaxis = f"{newaxis}_win"
|
|
84
102
|
|
|
85
|
-
samp_shape =
|
|
86
|
-
window_samples = int(window_dur * fs)
|
|
87
|
-
b_1to1 = window_shift is None
|
|
88
|
-
if not b_1to1:
|
|
89
|
-
window_shift_samples = int(window_shift * fs)
|
|
103
|
+
samp_shape = msg_in.data.shape[:axis_idx] + msg_in.data.shape[axis_idx + 1 :]
|
|
90
104
|
|
|
91
105
|
# If buffer unset or input stats changed, create a new buffer
|
|
92
|
-
|
|
106
|
+
b_reset = buffer is None
|
|
107
|
+
b_reset = b_reset or samp_shape != check_inputs["samp_shape"]
|
|
108
|
+
b_reset = b_reset or fs != check_inputs["fs"]
|
|
109
|
+
b_reset = b_reset or msg_in.key != check_inputs["key"]
|
|
110
|
+
if b_reset:
|
|
111
|
+
# Update check variables
|
|
112
|
+
check_inputs["samp_shape"] = samp_shape
|
|
113
|
+
check_inputs["fs"] = fs
|
|
114
|
+
check_inputs["key"] = msg_in.key
|
|
115
|
+
|
|
116
|
+
window_samples = int(window_dur * fs)
|
|
117
|
+
if not b_1to1:
|
|
118
|
+
window_shift_samples = int(window_shift * fs)
|
|
93
119
|
if zero_pad_until == "none":
|
|
94
120
|
req_samples = window_samples
|
|
95
121
|
elif zero_pad_until == "shift" and not b_1to1:
|
|
96
122
|
req_samples = window_shift_samples
|
|
97
123
|
else: # i.e. zero_pad_until == "input"
|
|
98
|
-
req_samples =
|
|
124
|
+
req_samples = msg_in.data.shape[axis_idx]
|
|
99
125
|
n_zero = max(0, window_samples - req_samples)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
126
|
+
buffer = np.zeros(
|
|
127
|
+
msg_in.data.shape[:axis_idx]
|
|
128
|
+
+ (n_zero,)
|
|
129
|
+
+ msg_in.data.shape[axis_idx + 1 :]
|
|
130
|
+
)
|
|
104
131
|
|
|
105
132
|
# Add new data to buffer.
|
|
106
|
-
# Currently we
|
|
107
|
-
# np.roll
|
|
108
|
-
# rolling view of the data.
|
|
133
|
+
# Currently, we concatenate the new time samples and clip the output.
|
|
134
|
+
# np.roll is not preferred as it returns a copy, and there's no way to construct a
|
|
135
|
+
# rolling view of the data. In current numpy implementations, np.concatenate
|
|
109
136
|
# is generally faster than np.roll and slicing anyway, but this could still
|
|
110
137
|
# be a performance bottleneck for large memory arrays.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
# because currently the outputs are merely views into the buffer.
|
|
138
|
+
# A circular buffer might be faster.
|
|
139
|
+
buffer = np.concatenate((buffer, msg_in.data), axis=axis_idx)
|
|
114
140
|
|
|
115
141
|
# Create a vector of buffer timestamps to track axis `offset` in output(s)
|
|
116
142
|
buffer_offset = np.arange(buffer.shape[axis_idx]).astype(float)
|
|
117
143
|
# Adjust so first _new_ sample at index 0
|
|
118
|
-
buffer_offset -= buffer_offset[-
|
|
144
|
+
buffer_offset -= buffer_offset[-msg_in.data.shape[axis_idx]]
|
|
119
145
|
# Convert form indices to 'units' (probably seconds).
|
|
120
146
|
buffer_offset *= axis_info.gain
|
|
121
147
|
buffer_offset += axis_info.offset
|
|
@@ -123,123 +149,133 @@ def windowing(
|
|
|
123
149
|
if not b_1to1 and shift_deficit > 0:
|
|
124
150
|
n_skip = min(buffer.shape[axis_idx], shift_deficit)
|
|
125
151
|
if n_skip > 0:
|
|
126
|
-
buffer = slice_along_axis(buffer,
|
|
152
|
+
buffer = slice_along_axis(buffer, slice(n_skip, None), axis_idx)
|
|
127
153
|
buffer_offset = buffer_offset[n_skip:]
|
|
128
154
|
shift_deficit -= n_skip
|
|
129
155
|
|
|
130
156
|
# Prepare reusable parts of output
|
|
131
|
-
if
|
|
132
|
-
out_dims =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
mod_ax = axis
|
|
139
|
-
else:
|
|
140
|
-
out_dims = out_dims[:axis_idx] + [newaxis] + out_dims[axis_idx:]
|
|
141
|
-
out_axes = {
|
|
142
|
-
**axis_arr_in.axes,
|
|
143
|
-
newaxis: AxisArray.Axis(
|
|
144
|
-
unit=axis_info.unit,
|
|
145
|
-
gain=0.0 if b_1to1 else axis_info.gain * window_shift_samples,
|
|
146
|
-
offset=0.0 # offset modified below
|
|
147
|
-
)
|
|
148
|
-
}
|
|
149
|
-
mod_ax = newaxis
|
|
150
|
-
out_template = replace(axis_arr_in, data=np.zeros([0 for _ in out_dims]), dims=out_dims)
|
|
157
|
+
if out_newaxis is None:
|
|
158
|
+
out_dims = msg_in.dims[:axis_idx] + [newaxis] + msg_in.dims[axis_idx:]
|
|
159
|
+
out_newaxis = replace(
|
|
160
|
+
axis_info,
|
|
161
|
+
gain=0.0 if b_1to1 else axis_info.gain * window_shift_samples,
|
|
162
|
+
offset=0.0, # offset modified per-msg below
|
|
163
|
+
)
|
|
151
164
|
|
|
152
165
|
# Generate outputs.
|
|
153
|
-
|
|
166
|
+
# Preliminary copy of axes without the axes that we are modifying.
|
|
167
|
+
out_axes = {k: v for k, v in msg_in.axes.items() if k not in [newaxis, axis]}
|
|
168
|
+
|
|
169
|
+
# Update targeted (windowed) axis so that its offset is relative to the new axis
|
|
170
|
+
# TODO: If we have `anchor_newest=True` then offset should be -win_dur
|
|
171
|
+
out_axes[axis] = replace(axis_info, offset=0.0)
|
|
172
|
+
|
|
173
|
+
# How we update .data and .axes[newaxis] depends on the windowing mode.
|
|
154
174
|
if b_1to1:
|
|
155
175
|
# one-to-one mode -- Each send yields exactly one window containing only the most recent samples.
|
|
156
|
-
buffer = slice_along_axis(buffer,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
data=np.expand_dims(buffer, axis=axis_idx) if b_newaxis else buffer,
|
|
160
|
-
axes={
|
|
161
|
-
**out_axes,
|
|
162
|
-
mod_ax: replace(out_axes[mod_ax], offset=buffer_offset[-window_samples])
|
|
163
|
-
}
|
|
164
|
-
))
|
|
176
|
+
buffer = slice_along_axis(buffer, slice(-window_samples, None), axis_idx)
|
|
177
|
+
out_dat = np.expand_dims(buffer, axis=axis_idx)
|
|
178
|
+
out_newaxis = replace(out_newaxis, offset=buffer_offset[-window_samples])
|
|
165
179
|
elif buffer.shape[axis_idx] >= window_samples:
|
|
166
180
|
# Deterministic window shifts.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
axes={**out_axes, mod_ax: replace(out_axes[mod_ax], offset=offset_view[0, 0])}
|
|
176
|
-
))
|
|
177
|
-
else:
|
|
178
|
-
for win_ix in range(win_view.shape[axis_idx]):
|
|
179
|
-
axis_arr_out.append(replace(
|
|
180
|
-
out_template,
|
|
181
|
-
data=slice_along_axis(win_view, win_ix, axis_idx),
|
|
182
|
-
axes={
|
|
183
|
-
**out_axes,
|
|
184
|
-
mod_ax: replace(out_axes[mod_ax], offset=offset_view[win_ix, 0])
|
|
185
|
-
}
|
|
186
|
-
))
|
|
181
|
+
out_dat = sliding_win_oneaxis(buffer, window_samples, axis_idx)
|
|
182
|
+
out_dat = slice_along_axis(
|
|
183
|
+
out_dat, slice(None, None, window_shift_samples), axis_idx
|
|
184
|
+
)
|
|
185
|
+
offset_view = sliding_win_oneaxis(buffer_offset, window_samples, 0)[
|
|
186
|
+
::window_shift_samples
|
|
187
|
+
]
|
|
188
|
+
out_newaxis = replace(out_newaxis, offset=offset_view[0, 0])
|
|
187
189
|
|
|
188
190
|
# Drop expired beginning of buffer and update shift_deficit
|
|
189
|
-
multi_shift = window_shift_samples *
|
|
191
|
+
multi_shift = window_shift_samples * out_dat.shape[axis_idx]
|
|
190
192
|
shift_deficit = max(0, multi_shift - buffer.shape[axis_idx])
|
|
191
|
-
buffer = slice_along_axis(buffer,
|
|
193
|
+
buffer = slice_along_axis(buffer, slice(multi_shift, None), axis_idx)
|
|
194
|
+
else:
|
|
195
|
+
# Not enough data to make a new window. Return empty data.
|
|
196
|
+
empty_data_shape = (
|
|
197
|
+
msg_in.data.shape[:axis_idx]
|
|
198
|
+
+ (0, window_samples)
|
|
199
|
+
+ msg_in.data.shape[axis_idx + 1 :]
|
|
200
|
+
)
|
|
201
|
+
out_dat = np.zeros(empty_data_shape, dtype=msg_in.data.dtype)
|
|
202
|
+
# out_newaxis will have first timestamp in input... but mostly meaningless because output is size-zero.
|
|
203
|
+
out_newaxis = replace(out_newaxis, offset=axis_info.offset)
|
|
204
|
+
|
|
205
|
+
msg_out = replace(
|
|
206
|
+
msg_in, data=out_dat, dims=out_dims, axes={**out_axes, newaxis: out_newaxis}
|
|
207
|
+
)
|
|
192
208
|
|
|
193
209
|
|
|
194
210
|
class WindowSettings(ez.Settings):
|
|
195
|
-
axis: Optional[str] = None
|
|
196
|
-
newaxis: Optional[str] = None # new axis for output. No new axes if None
|
|
197
|
-
window_dur: Optional[float] = None # Sec. passthrough if None
|
|
198
|
-
window_shift: Optional[float] = None # Sec. Use "1:1 mode" if None
|
|
211
|
+
axis: typing.Optional[str] = None
|
|
212
|
+
newaxis: typing.Optional[str] = None # new axis for output. No new axes if None
|
|
213
|
+
window_dur: typing.Optional[float] = None # Sec. passthrough if None
|
|
214
|
+
window_shift: typing.Optional[float] = None # Sec. Use "1:1 mode" if None
|
|
199
215
|
zero_pad_until: str = "full" # "full", "shift", "input", "none"
|
|
200
216
|
|
|
201
217
|
|
|
202
218
|
class WindowState(ez.State):
|
|
203
219
|
cur_settings: WindowSettings
|
|
204
|
-
gen: Generator
|
|
220
|
+
gen: typing.Generator
|
|
205
221
|
|
|
206
222
|
|
|
207
|
-
class Window(
|
|
208
|
-
|
|
209
|
-
|
|
223
|
+
class Window(GenAxisArray):
|
|
224
|
+
""":obj:`Unit` for :obj:`bandpower`."""
|
|
225
|
+
|
|
226
|
+
SETTINGS = WindowSettings
|
|
210
227
|
|
|
211
228
|
INPUT_SIGNAL = ez.InputStream(AxisArray)
|
|
212
229
|
OUTPUT_SIGNAL = ez.OutputStream(AxisArray)
|
|
213
|
-
INPUT_SETTINGS = ez.InputStream(WindowSettings)
|
|
214
|
-
|
|
215
|
-
def initialize(self) -> None:
|
|
216
|
-
self.STATE.cur_settings = self.SETTINGS
|
|
217
|
-
self.construct_generator()
|
|
218
|
-
|
|
219
|
-
@ez.subscriber(INPUT_SETTINGS)
|
|
220
|
-
async def on_settings(self, msg: WindowSettings) -> None:
|
|
221
|
-
self.STATE.cur_settings = msg
|
|
222
|
-
self.construct_generator()
|
|
223
230
|
|
|
224
231
|
def construct_generator(self):
|
|
225
232
|
self.STATE.gen = windowing(
|
|
226
|
-
axis=self.
|
|
227
|
-
newaxis=self.
|
|
228
|
-
window_dur=self.
|
|
229
|
-
window_shift=self.
|
|
230
|
-
zero_pad_until=self.
|
|
233
|
+
axis=self.SETTINGS.axis,
|
|
234
|
+
newaxis=self.SETTINGS.newaxis,
|
|
235
|
+
window_dur=self.SETTINGS.window_dur,
|
|
236
|
+
window_shift=self.SETTINGS.window_shift,
|
|
237
|
+
zero_pad_until=self.SETTINGS.zero_pad_until,
|
|
231
238
|
)
|
|
232
239
|
|
|
233
|
-
@ez.subscriber(INPUT_SIGNAL)
|
|
240
|
+
@ez.subscriber(INPUT_SIGNAL, zero_copy=True)
|
|
234
241
|
@ez.publisher(OUTPUT_SIGNAL)
|
|
235
|
-
async def on_signal(self, msg: AxisArray) -> AsyncGenerator:
|
|
242
|
+
async def on_signal(self, msg: AxisArray) -> typing.AsyncGenerator:
|
|
236
243
|
try:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
244
|
+
out_msg = self.STATE.gen.send(msg)
|
|
245
|
+
if out_msg.data.size > 0:
|
|
246
|
+
if (
|
|
247
|
+
self.SETTINGS.newaxis is not None
|
|
248
|
+
or self.SETTINGS.window_dur is None
|
|
249
|
+
):
|
|
250
|
+
# Multi-win mode or pass-through mode.
|
|
251
|
+
yield self.OUTPUT_SIGNAL, out_msg
|
|
252
|
+
else:
|
|
253
|
+
# We need to split out_msg into multiple yields, dropping newaxis.
|
|
254
|
+
axis_idx = out_msg.get_axis_idx("win")
|
|
255
|
+
win_axis = out_msg.axes["win"]
|
|
256
|
+
offsets = (
|
|
257
|
+
np.arange(out_msg.data.shape[axis_idx]) * win_axis.gain
|
|
258
|
+
+ win_axis.offset
|
|
259
|
+
)
|
|
260
|
+
for msg_ix in range(out_msg.data.shape[axis_idx]):
|
|
261
|
+
# Need to drop 'win' and replace self.SETTINGS.axis from axes.
|
|
262
|
+
_out_axes = {
|
|
263
|
+
**{
|
|
264
|
+
k: v
|
|
265
|
+
for k, v in out_msg.axes.items()
|
|
266
|
+
if k not in ["win", self.SETTINGS.axis]
|
|
267
|
+
},
|
|
268
|
+
self.SETTINGS.axis: replace(
|
|
269
|
+
out_msg.axes[self.SETTINGS.axis], offset=offsets[msg_ix]
|
|
270
|
+
),
|
|
271
|
+
}
|
|
272
|
+
_out_msg = replace(
|
|
273
|
+
out_msg,
|
|
274
|
+
data=slice_along_axis(out_msg.data, msg_ix, axis_idx),
|
|
275
|
+
dims=out_msg.dims[:axis_idx] + out_msg.dims[axis_idx + 1 :],
|
|
276
|
+
axes=_out_axes,
|
|
277
|
+
)
|
|
278
|
+
yield self.OUTPUT_SIGNAL, _out_msg
|
|
243
279
|
except (StopIteration, GeneratorExit):
|
|
244
280
|
ez.logger.debug(f"Window closed in {self.address}")
|
|
245
281
|
except Exception:
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ezmsg-sigproc
|
|
3
|
+
Version: 1.3.2
|
|
4
|
+
Summary: Timeseries signal processing implementations in ezmsg
|
|
5
|
+
Author-email: Griffin Milsap <griffin.milsap@gmail.com>, Preston Peranich <pperanich@gmail.com>, Chadwick Boulay <chadwick.boulay@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE.txt
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Requires-Dist: ezmsg>=3.5.0
|
|
10
|
+
Requires-Dist: numpy>=1.26.0
|
|
11
|
+
Requires-Dist: pywavelets>=1.6.0
|
|
12
|
+
Requires-Dist: scipy>=1.13.1
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: flake8>=7.1.1; extra == 'test'
|
|
15
|
+
Requires-Dist: frozendict>=2.4.4; extra == 'test'
|
|
16
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'test'
|
|
17
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
|
|
18
|
+
Requires-Dist: pytest>=8.3.3; extra == 'test'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# ezmsg.sigproc
|
|
22
|
+
|
|
23
|
+
Timeseries signal processing implementations for ezmsg
|
|
24
|
+
|
|
25
|
+
## Dependencies
|
|
26
|
+
|
|
27
|
+
* `ezmsg`
|
|
28
|
+
* `numpy`
|
|
29
|
+
* `scipy`
|
|
30
|
+
* `pywavelets`
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### Release
|
|
35
|
+
|
|
36
|
+
Install the latest release from pypi with: `pip install ezmsg-sigproc` (or `uv add ...` or `poetry add ...`).
|
|
37
|
+
|
|
38
|
+
### Development Version
|
|
39
|
+
|
|
40
|
+
You can add the development version of `ezmsg-sigproc` to your project's dependencies in one of several ways.
|
|
41
|
+
|
|
42
|
+
You can clone it and add its path to your project dependencies. You may wish to do this if you intend to edit `ezmsg-sigproc`. If so, please refer to the [Developers](#developers) section below.
|
|
43
|
+
|
|
44
|
+
You can also add it directly from GitHub:
|
|
45
|
+
|
|
46
|
+
* Using `pip`: `pip install git+https://github.com/ezmsg-org/ezmsg-sigproc.git@dev`
|
|
47
|
+
* Using `poetry`: `poetry add "git+https://github.com/ezmsg-org/ezmsg-sigproc.git@dev"`
|
|
48
|
+
* Using `uv`: `uv add git+https://github.com/ezmsg-org/ezmsg-sigproc --branch dev`
|
|
49
|
+
|
|
50
|
+
## Developers
|
|
51
|
+
|
|
52
|
+
We use [`uv`](https://docs.astral.sh/uv/getting-started/installation/) for development. It is not strictly required, but if you intend to contribute to ezmsg-sigproc then using `uv` will lead to the smoothest collaboration.
|
|
53
|
+
|
|
54
|
+
1. Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/) if not already installed.
|
|
55
|
+
2. Fork ezmsg-sigproc and clone your fork to your local computer.
|
|
56
|
+
3. Open a terminal and `cd` to the cloned folder.
|
|
57
|
+
4. `uv sync` to create a .venv and install dependencies.
|
|
58
|
+
5. `uv run pre-commit install` to install pre-commit hooks to do linting and formatting.
|
|
59
|
+
6. After editing code and making commits, Run the test suite before making a PR: `uv run pytest tests`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
ezmsg/sigproc/__init__.py,sha256=8K4IcOA3-pfzadoM6s2Sfg5460KlJUocGgyTJTJl96U,52
|
|
2
|
+
ezmsg/sigproc/__version__.py,sha256=ik9WUFYTvRcmzFEUSqkI2H_62uw0XFdUT3mTLq8-_RY,411
|
|
3
|
+
ezmsg/sigproc/activation.py,sha256=-KgDSVmgYx8QhHM5UQHkMN6rHudeasuWOYhmM2_aWH0,2068
|
|
4
|
+
ezmsg/sigproc/affinetransform.py,sha256=pJlg07sXf-o-Rv-pSD1_M_kH0xWBM9vYKGcJ7HCGndU,8938
|
|
5
|
+
ezmsg/sigproc/aggregate.py,sha256=8kVs2S2MUuTZkRwn-vh4utv8uoWwWrIkgalxcBdT39M,5490
|
|
6
|
+
ezmsg/sigproc/bandpower.py,sha256=l_R_qcrRTLGYjg40VAinIHz24pdwET4AS4RXd2Ctcig,2126
|
|
7
|
+
ezmsg/sigproc/base.py,sha256=VtlneWR0GAsWzAfqMzFy-WYPbeF8C3qVenqj7h_Cfek,1224
|
|
8
|
+
ezmsg/sigproc/butterworthfilter.py,sha256=busvcDtVJx2ZHgenM4o0SDTeBbtXbPEXrClCRty0iF0,5233
|
|
9
|
+
ezmsg/sigproc/decimate.py,sha256=zNxJXauE4Z54Yy7tpN9_oJuW0I0ppQRWhSpwb3Ri2gc,1473
|
|
10
|
+
ezmsg/sigproc/downsample.py,sha256=Zuk_sCTk3eYOmyVsKGk9P6s-yUkfim_neBBc61ZVUKY,3304
|
|
11
|
+
ezmsg/sigproc/ewmfilter.py,sha256=XzTmouRa7AnPjTiI32-oumwm4ZfOl6uPPUSldExNpAs,4516
|
|
12
|
+
ezmsg/sigproc/filter.py,sha256=DHF-4CvxhNCGaAuVpn64wCxYuq5TpMH7bi0KKQNaOk0,8161
|
|
13
|
+
ezmsg/sigproc/filterbank.py,sha256=K3Y2SJ7kfEp-CiNNSgzEM7sq1hbYaXOvufKSVyYnUEw,12252
|
|
14
|
+
ezmsg/sigproc/messages.py,sha256=KpYCWWRD5itVAq0-Lf8qd7ErEE1A9qdm2p7O8Y4zEFA,955
|
|
15
|
+
ezmsg/sigproc/sampler.py,sha256=3-O5nplutDcojb0CCbkTQsH8cZ8yKxqYts_02LryIKM,12762
|
|
16
|
+
ezmsg/sigproc/scaler.py,sha256=eQuF3DL4NvxuEBjDgf2k4qqoctw5hIS_NIOfbOjOY0k,5823
|
|
17
|
+
ezmsg/sigproc/signalinjector.py,sha256=HzQvI6jvkuX7miGHy6h0yXVRz_wvM3ecpQvquCV0H7g,2408
|
|
18
|
+
ezmsg/sigproc/slicer.py,sha256=TcTI464UH9y1abCwT6eEMaqo7B1geuwJNNYScMUyyFM,4609
|
|
19
|
+
ezmsg/sigproc/spectral.py,sha256=_2qO6as4Nesmc9V1WW2EXNMH5pPz8aVTEcIPOi4-g2o,322
|
|
20
|
+
ezmsg/sigproc/spectrogram.py,sha256=osaqIhC04RbMnpHy5ecAxnYhQOn022oTvF2wbz-lKec,2977
|
|
21
|
+
ezmsg/sigproc/spectrum.py,sha256=AmoPObVVvuXkUSClCExvKzLoasLyTlIwtRsrz1XFOLI,9366
|
|
22
|
+
ezmsg/sigproc/synth.py,sha256=ygJRI9Seqhrg13lUhcA6XSpxcEr3LL1NupviSQLrC9o,18351
|
|
23
|
+
ezmsg/sigproc/wavelets.py,sha256=sccj7xsIyHmsUL7XWYm7wxnNvIGEcpCZ8qsfRXSObiA,6488
|
|
24
|
+
ezmsg/sigproc/window.py,sha256=bzOJDQRxDfcHNHrTgWkILGH9uYk4z0jy-JkCb8zguFQ,12599
|
|
25
|
+
ezmsg/sigproc/math/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
ezmsg/sigproc/math/abs.py,sha256=8rtcdyMSGtnMQgGwbcW8Ky-3uRspkYD7lRdpcFl6uEE,608
|
|
27
|
+
ezmsg/sigproc/math/clip.py,sha256=WBKe6wPTcqb041OwuUB5OGK-s_R3gc95qKBJY2Aspgo,779
|
|
28
|
+
ezmsg/sigproc/math/difference.py,sha256=tacbt3KKQOhoYaFMlrASC2GhYQG1J6Hdaa9J1zg_Aw8,1735
|
|
29
|
+
ezmsg/sigproc/math/invert.py,sha256=X6Pp0oYg5A3tpDxfI513xjYg2yXjsosR048AxCWzIwI,667
|
|
30
|
+
ezmsg/sigproc/math/log.py,sha256=T3DST5gpva3lTHmbJkDWVpJyqEVeydUuZSOQCThbvIg,757
|
|
31
|
+
ezmsg/sigproc/math/scale.py,sha256=Ge99e43m1tYoxwQEZ2y-jPq6XQn_IvuKLPaTT2y5vPI,746
|
|
32
|
+
ezmsg_sigproc-1.3.2.dist-info/METADATA,sha256=zFIJ6FXjyBXOcJLjgGE0OO8R_IPKnAIWT8AEQ9SS_io,2376
|
|
33
|
+
ezmsg_sigproc-1.3.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
34
|
+
ezmsg_sigproc-1.3.2.dist-info/licenses/LICENSE.txt,sha256=seu0tKhhAMPCUgc1XpXGGaCxY1YaYvFJwqFuQZAl2go,1100
|
|
35
|
+
ezmsg_sigproc-1.3.2.dist-info/RECORD,,
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: ezmsg-sigproc
|
|
3
|
-
Version: 1.2.3
|
|
4
|
-
Summary: Timeseries signal processing implementations in ezmsg
|
|
5
|
-
License: MIT
|
|
6
|
-
Author: Milsap, Griffin
|
|
7
|
-
Author-email: griffin.milsap@gmail.com
|
|
8
|
-
Requires-Python: >=3.8,<4.0
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Requires-Dist: ezmsg (>=3.3.0,<4.0.0)
|
|
17
|
-
Requires-Dist: numpy (>=1.19.5,<2.0.0)
|
|
18
|
-
Requires-Dist: scipy (>=1.6.3,<2.0.0)
|
|
19
|
-
Description-Content-Type: text/markdown
|
|
20
|
-
|
|
21
|
-
# ezmsg.sigproc
|
|
22
|
-
|
|
23
|
-
Timeseries signal processing implementations for ezmsg
|
|
24
|
-
|
|
25
|
-
## Installation
|
|
26
|
-
`pip install ezmsg-sigproc`
|
|
27
|
-
|
|
28
|
-
## Dependencies
|
|
29
|
-
* `ezmsg`
|
|
30
|
-
* `numpy`
|
|
31
|
-
* `scipy`
|
|
32
|
-
|
|
33
|
-
## Setup (Development)
|
|
34
|
-
1. Install `ezmsg` either using `pip install ezmsg` or set up the repo for development as described in the `ezmsg` readme.
|
|
35
|
-
2. `cd` to this directory (`ezmsg-sigproc`) and run `pip install -e .`
|
|
36
|
-
3. Signal processing modules are available under `import ezmsg.sigproc`
|
|
37
|
-
|
|
38
|
-
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
ezmsg/sigproc/__init__.py,sha256=0GC1NyV5G-wC90N72vxdMdq8IjbroUkcgfzkEq8mtas,86
|
|
2
|
-
ezmsg/sigproc/affinetransform.py,sha256=fmSqu26d_GnHU2LtvgdACOcsyJbTqQUurY75CbXiJ54,4547
|
|
3
|
-
ezmsg/sigproc/aggregate.py,sha256=rrcn9Zt5BneOMN6nzdbSPggTwOJm43Z_faLn5CEZJpI,3549
|
|
4
|
-
ezmsg/sigproc/bandpower.py,sha256=7Bc1vPZb9VMYQvcaDRBlTuoUzG5n9etfJ0ATv0j3T-Y,1708
|
|
5
|
-
ezmsg/sigproc/butterworthfilter.py,sha256=roA-EoxxMiKZsrh9UGomZ9HQab7zs7iH0CK4_LkpVs0,3322
|
|
6
|
-
ezmsg/sigproc/decimate.py,sha256=xQgyGSrje9hiNjMpEpGOyAUluTxm5zgY66GcKur7oYs,1328
|
|
7
|
-
ezmsg/sigproc/downsample.py,sha256=ffLW4waASy9zRjq1y39Iuag_r5thejLB_zvqYEco738,2968
|
|
8
|
-
ezmsg/sigproc/ewmfilter.py,sha256=IBorlWd9YXRIgDWYwgTCBtkjSv3YzhH0f4D-gqX-8kc,4097
|
|
9
|
-
ezmsg/sigproc/filter.py,sha256=8GrgF_HNfTfW9WOXnNYt0bJMFkfr47yJJY_G6GuSg90,7584
|
|
10
|
-
ezmsg/sigproc/messages.py,sha256=JU9l_QNL7SKY54ac9SARKf5tSxWqSAyUqvfvoamHzKs,963
|
|
11
|
-
ezmsg/sigproc/sampler.py,sha256=Bo4On8jKZ1AF5-RH4pn_HWJnx1E6OobTZ5w1qf3zLiw,10761
|
|
12
|
-
ezmsg/sigproc/scaler.py,sha256=nE2YVK_pxOSnjLm1w7sOpmC69sHIgze81XBuwntfuhM,4387
|
|
13
|
-
ezmsg/sigproc/signalinjector.py,sha256=_hYtLNNC2uSr7nz2dg9Ne8W-02Y10o0RJL9lDluR9gY,2408
|
|
14
|
-
ezmsg/sigproc/slicer.py,sha256=khmmMDHjiAk5G5iOS0z9oHrjoTLPGE1bcZiae7qf7as,3249
|
|
15
|
-
ezmsg/sigproc/spectral.py,sha256=KvM4pwqd2YYt2zsiLw8PgDDh5GVTjbUeUJxVbL2K8-A,160
|
|
16
|
-
ezmsg/sigproc/spectrogram.py,sha256=tOQ4MjAIs7orvd5qaW6ZmFHxRW1TSr_hHauL18Bo9Rk,2471
|
|
17
|
-
ezmsg/sigproc/spectrum.py,sha256=Cni5HZaeu-RaDWkLMyXBBAdtSwKpxXESONfl1AUsmHQ,5398
|
|
18
|
-
ezmsg/sigproc/synth.py,sha256=mcBd1H1quoB5PxKzpuDDBfJYYRs0Sbi7icajabXhv1s,16229
|
|
19
|
-
ezmsg/sigproc/window.py,sha256=OZF8LjPHu5FK3jRBhelKs-aBdeSKT_3qxZNDaMiws08,11742
|
|
20
|
-
ezmsg_sigproc-1.2.3.dist-info/LICENSE.txt,sha256=seu0tKhhAMPCUgc1XpXGGaCxY1YaYvFJwqFuQZAl2go,1100
|
|
21
|
-
ezmsg_sigproc-1.2.3.dist-info/METADATA,sha256=wKa6rW06PJYh0da1xjINzOvc0ixSoki9Uy8juys9NwM,1188
|
|
22
|
-
ezmsg_sigproc-1.2.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
23
|
-
ezmsg_sigproc-1.2.3.dist-info/RECORD,,
|
|
File without changes
|