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/window.py CHANGED
@@ -1,121 +1,147 @@
1
1
  from dataclasses import replace
2
2
  import traceback
3
- from typing import AsyncGenerator, Optional, Tuple, List, Generator
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
- from ezmsg.util.messages.axisarray import AxisArray, slice_along_axis, sliding_win_oneaxis
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: Optional[str] = None,
17
- window_dur: Optional[float] = None,
18
- window_shift: Optional[float] = None,
19
- zero_pad_until: str = "input"
20
- ) -> Generator[AxisArray, List[AxisArray], None]:
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
- Window function that generates windows of data from an input `AxisArray`.
23
- :param axis: The axis along which to segment windows.
24
- If None, defaults to the first dimension of the first seen AxisArray.
25
- :param newaxis: Optional new axis for the output. If None, no new axes will be added.
26
- If a string, windows will be stacked in a new axis with key `newaxis`, immediately preceding the windowed axis.
27
- :param window_dur: The duration of the window in seconds.
28
- If None, the function acts as a passthrough and all other parameters are ignored.
29
- :param window_shift: The shift of the window in seconds.
30
- If None (default), windowing operates in "1:1 mode", where each input yields exactly one most-recent window.
31
- :param zero_pad_until: Determines how the function initializes the buffer.
32
- Can be one of "input" (default), "full", "shift", or "none". If `window_shift` is None then this field is
33
- ignored and "input" is always used.
34
- "input" (default) initializes the buffer with the input then prepends with zeros to the window size.
35
- The first input will always yield at least one output.
36
- "shift" fills the buffer until `window_shift`.
37
- No outputs will be yielded until at least `window_shift` data has been seen.
38
- "none" does not pad the buffer. No outputs will be yielded until at least `window_dur` data has been seen.
39
- :return:
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
- # TODO: The return should be an AxisArray. i.e., always add a new axis. The Unit can do a multi-yield-per-pub
44
- # if the parameterization does not expect a newaxis.
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("`zero_pad_until` must be 'input' if `window_shift` is None. "
48
- f"Ignoring received argument value: {zero_pad_until}")
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("windowing is non-deterministic with `zero_pad_until='input'` as it depends on the size "
52
- "of the first input. We recommend using 'shift' when `window_shift` is float-valued.")
53
- axis_arr_in = AxisArray(np.array([]), dims=[""])
54
- axis_arr_out = [AxisArray(np.array([]), dims=[""])]
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
- prev_samp_shape: Optional[Tuple[int, ...]] = None
58
- prev_fs: Optional[float] = None
59
- buffer: Optional[npt.NDArray] = None
60
- window_samples: Optional[int] = None
61
- window_shift_samples: Optional[int] = None
62
- shift_deficit: int = 0 # Number of incoming samples to ignore. Only relevant when shift > window.
63
- newaxis_warn_flag: bool = False
64
- mod_ax: Optional[str] = None # The key of the modified axis in the output's .axes
65
- out_template: Optional[AxisArray] = None # Template for building return values.
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
- axis_arr_in = yield axis_arr_out
85
+ msg_in: AxisArray = yield msg_out
69
86
 
70
87
  if window_dur is None:
71
- axis_arr_out = [axis_arr_in]
88
+ msg_out = msg_in
72
89
  continue
73
90
 
74
- if axis is None:
75
- axis = axis_arr_in.dims[0]
76
- axis_idx = axis_arr_in.get_axis_idx(axis)
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 (not newaxis_warn_flag) and newaxis is not None and newaxis in axis_arr_in.dims:
81
- ez.logger.warning(f"newaxis {newaxis} present in input dims and will be ignored.")
82
- newaxis_warn_flag = True
83
- b_newaxis = newaxis is not None and newaxis not in axis_arr_in.dims
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 = axis_arr_in.data.shape[:axis_idx] + axis_arr_in.data.shape[axis_idx + 1:]
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
- if buffer is None or samp_shape != prev_samp_shape or fs != prev_fs:
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 = axis_arr_in.data.shape[axis_idx]
124
+ req_samples = msg_in.data.shape[axis_idx]
99
125
  n_zero = max(0, window_samples - req_samples)
100
- buffer_shape = axis_arr_in.data.shape[:axis_idx] + (n_zero,) + axis_arr_in.data.shape[axis_idx + 1:]
101
- buffer = np.zeros(buffer_shape)
102
- prev_samp_shape = samp_shape
103
- prev_fs = fs
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 just concatenate the new time samples and clip the output
107
- # np.roll actually returns a copy, and there's no way to construct a
108
- # rolling view of the data. In current numpy implementations, np.concatenate
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
- buffer = np.concatenate((buffer, axis_arr_in.data), axis=axis_idx)
112
- # Note: if we ever move to using a circular buffer without copies then we need to create copies somewhere,
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[-axis_arr_in.data.shape[axis_idx]]
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, np.s_[n_skip:], axis_idx)
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 out_template is None:
132
- out_dims = axis_arr_in.dims
133
- if newaxis is None:
134
- out_axes = {
135
- **axis_arr_in.axes,
136
- axis: replace(axis_info, offset=0.0) # offset modified below.
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
- axis_arr_out: List[AxisArray] = []
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, np.s_[-window_samples:], axis_idx)
157
- axis_arr_out.append(replace(
158
- out_template,
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
- win_view = sliding_win_oneaxis(buffer, window_samples, axis_idx)
168
- win_view = slice_along_axis(win_view, np.s_[::window_shift_samples], axis_idx)
169
- offset_view = sliding_win_oneaxis(buffer_offset, window_samples, 0)[::window_shift_samples]
170
- # Place in output
171
- if b_newaxis:
172
- axis_arr_out.append(replace(
173
- out_template,
174
- data=win_view,
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 * win_view.shape[axis_idx]
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, np.s_[multi_shift:], axis_idx)
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(ez.Unit):
208
- STATE: WindowState
209
- SETTINGS: WindowSettings
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.STATE.cur_settings.axis,
227
- newaxis=self.STATE.cur_settings.newaxis,
228
- window_dur=self.STATE.cur_settings.window_dur,
229
- window_shift=self.STATE.cur_settings.window_shift,
230
- zero_pad_until=self.STATE.cur_settings.zero_pad_until
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
- # TODO: Refactor window generator so it always returns an axis array.
238
- # Then, if the configuration is such that a new "win" axis is not expected,
239
- # then iterate over the "win" axis -- dropping the "win" axis in the process.
240
- out_msgs = self.STATE.gen.send(msg)
241
- for out_msg in out_msgs:
242
- yield self.OUTPUT_SIGNAL, out_msg
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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: hatchling 1.25.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,