ezmsg-sigproc 1.7.1__tar.gz → 1.8.2__tar.gz

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.
Files changed (68) hide show
  1. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/PKG-INFO +2 -1
  2. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/pyproject.toml +1 -0
  3. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/__version__.py +2 -2
  4. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/downsample.py +16 -10
  5. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_downsample.py +13 -6
  6. ezmsg_sigproc-1.8.2/uv.lock +716 -0
  7. ezmsg_sigproc-1.7.1/uv.lock +0 -609
  8. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/.github/workflows/python-publish-ezmsg-sigproc.yml +0 -0
  9. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/.github/workflows/python-tests.yml +0 -0
  10. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/.gitignore +0 -0
  11. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/.pre-commit-config.yaml +0 -0
  12. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/LICENSE.txt +0 -0
  13. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/README.md +0 -0
  14. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/__init__.py +0 -0
  15. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/activation.py +0 -0
  16. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/affinetransform.py +0 -0
  17. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/aggregate.py +0 -0
  18. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/bandpower.py +0 -0
  19. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/base.py +0 -0
  20. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/butterworthfilter.py +0 -0
  21. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/cheby.py +0 -0
  22. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/decimate.py +0 -0
  23. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/ewmfilter.py +0 -0
  24. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/filter.py +0 -0
  25. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/filterbank.py +0 -0
  26. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/math/__init__.py +0 -0
  27. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/math/abs.py +0 -0
  28. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/math/clip.py +0 -0
  29. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/math/difference.py +0 -0
  30. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/math/invert.py +0 -0
  31. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/math/log.py +0 -0
  32. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/math/scale.py +0 -0
  33. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/messages.py +0 -0
  34. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/sampler.py +0 -0
  35. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/scaler.py +0 -0
  36. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/signalinjector.py +0 -0
  37. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/slicer.py +0 -0
  38. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/spectral.py +0 -0
  39. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/spectrogram.py +0 -0
  40. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/spectrum.py +0 -0
  41. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/synth.py +0 -0
  42. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/util/__init__.py +0 -0
  43. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/util/profile.py +0 -0
  44. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/util/sparse.py +0 -0
  45. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/wavelets.py +0 -0
  46. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/src/ezmsg/sigproc/window.py +0 -0
  47. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/conftest.py +0 -0
  48. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/helpers/__init__.py +0 -0
  49. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/helpers/util.py +0 -0
  50. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/resources/xform.csv +0 -0
  51. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_activation.py +0 -0
  52. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_affine_transform.py +0 -0
  53. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_aggregate.py +0 -0
  54. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_bandpower.py +0 -0
  55. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_butter.py +0 -0
  56. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_butterworth.py +0 -0
  57. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_decimate.py +0 -0
  58. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_filter_system.py +0 -0
  59. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_filterbank.py +0 -0
  60. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_math.py +0 -0
  61. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_sampler.py +0 -0
  62. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_scaler.py +0 -0
  63. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_slicer.py +0 -0
  64. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_spectrogram.py +0 -0
  65. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_spectrum.py +0 -0
  66. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_synth.py +0 -0
  67. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_wavelets.py +0 -0
  68. {ezmsg_sigproc-1.7.1 → ezmsg_sigproc-1.8.2}/tests/test_window.py +0 -0
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ezmsg-sigproc
3
- Version: 1.7.1
3
+ Version: 1.8.2
4
4
  Summary: Timeseries signal processing implementations in ezmsg
5
5
  Author-email: Griffin Milsap <griffin.milsap@gmail.com>, Preston Peranich <pperanich@gmail.com>, Chadwick Boulay <chadwick.boulay@gmail.com>
6
6
  License-Expression: MIT
7
7
  License-File: LICENSE.txt
8
8
  Requires-Python: >=3.10.15
9
9
  Requires-Dist: ezmsg>=3.6.0
10
+ Requires-Dist: numba>=0.61.0
10
11
  Requires-Dist: numpy>=1.26.0
11
12
  Requires-Dist: pywavelets>=1.6.0
12
13
  Requires-Dist: scipy>=1.13.1
@@ -12,6 +12,7 @@ requires-python = ">=3.10.15"
12
12
  dynamic = ["version"]
13
13
  dependencies = [
14
14
  "ezmsg>=3.6.0",
15
+ "numba>=0.61.0",
15
16
  "numpy>=1.26.0",
16
17
  "pywavelets>=1.6.0",
17
18
  "scipy>=1.13.1",
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '1.7.1'
21
- __version_tuple__ = version_tuple = (1, 7, 1)
20
+ __version__ = version = '1.8.2'
21
+ __version_tuple__ = version_tuple = (1, 8, 2)
@@ -14,7 +14,7 @@ from .base import GenAxisArray
14
14
 
15
15
  @consumer
16
16
  def downsample(
17
- axis: str | None = None, target_rate: float | None = None
17
+ axis: str | None = None, target_rate: float | None = None, factor: int | None = None
18
18
  ) -> typing.Generator[AxisArray, AxisArray, None]:
19
19
  """
20
20
  Construct a generator that yields a downsampled version of the data .send() to it.
@@ -28,6 +28,7 @@ def downsample(
28
28
  Note: The axis must exist in the message .axes and be of type AxisArray.LinearAxis.
29
29
  target_rate: Desired rate after downsampling. The actual rate will be the nearest integer factor of the
30
30
  input rate that is the same or higher than the target rate.
31
+ factor: Explicitly specify downsample factor. If specified, target_rate is ignored.
31
32
 
32
33
  Returns:
33
34
  A primed generator object ready to receive an :obj:`AxisArray` via `.send(axis_array)`
@@ -39,7 +40,7 @@ def downsample(
39
40
  msg_out = AxisArray(np.array([]), dims=[""])
40
41
 
41
42
  # state variables
42
- factor: int = 0 # The integer downsampling factor. It will be determined based on the target rate.
43
+ q: int = 0 # The integer downsampling factor. It will be determined based on the target rate.
43
44
  s_idx: int = 0 # Index of the next msg's first sample into the virtual rotating ds_factor counter.
44
45
 
45
46
  check_input = {"gain": None, "key": None}
@@ -61,19 +62,21 @@ def downsample(
61
62
  check_input["key"] = msg_in.key
62
63
  # Reset state variables
63
64
  s_idx = 0
64
- if target_rate is None:
65
- factor = 1
65
+ if factor is not None:
66
+ q = factor
67
+ elif target_rate is None:
68
+ q = 1
66
69
  else:
67
- factor = int(1 / (axis_info.gain * target_rate))
68
- if factor < 1:
70
+ q = int(1 / (axis_info.gain * target_rate))
71
+ if q < 1:
69
72
  ez.logger.warning(
70
73
  f"Target rate {target_rate} cannot be achieved with input rate of {1/axis_info.gain}."
71
74
  "Setting factor to 1."
72
75
  )
73
- factor = 1
76
+ q = 1
74
77
 
75
78
  n_samples = msg_in.data.shape[axis_idx]
76
- samples = np.arange(s_idx, s_idx + n_samples) % factor
79
+ samples = np.arange(s_idx, s_idx + n_samples) % q
77
80
  if n_samples > 0:
78
81
  # Update state for next iteration.
79
82
  s_idx = samples[-1] + 1
@@ -92,7 +95,7 @@ def downsample(
92
95
  **msg_in.axes,
93
96
  axis: replace(
94
97
  axis_info,
95
- gain=axis_info.gain * factor,
98
+ gain=axis_info.gain * q,
96
99
  offset=axis_info.offset + axis_info.gain * n_step,
97
100
  ),
98
101
  },
@@ -107,6 +110,7 @@ class DownsampleSettings(ez.Settings):
107
110
 
108
111
  axis: str | None = None
109
112
  target_rate: float | None = None
113
+ factor: int | None = None
110
114
 
111
115
 
112
116
  class Downsample(GenAxisArray):
@@ -116,5 +120,7 @@ class Downsample(GenAxisArray):
116
120
 
117
121
  def construct_generator(self):
118
122
  self.STATE.gen = downsample(
119
- axis=self.SETTINGS.axis, target_rate=self.SETTINGS.target_rate
123
+ axis=self.SETTINGS.axis,
124
+ target_rate=self.SETTINGS.target_rate,
125
+ factor=self.SETTINGS.factor,
120
126
  )
@@ -21,7 +21,8 @@ from ezmsg.util.debuglog import DebugLog
21
21
 
22
22
  @pytest.mark.parametrize("block_size", [1, 5, 10, 20])
23
23
  @pytest.mark.parametrize("target_rate", [19.0, 9.5, 6.3])
24
- def test_downsample_core(block_size: int, target_rate: float):
24
+ @pytest.mark.parametrize("factor", [None, 1, 2])
25
+ def test_downsample_core(block_size: int, target_rate: float, factor: int | None):
25
26
  in_fs = 19.0
26
27
  test_dur = 4.0
27
28
  n_channels = 2
@@ -60,7 +61,7 @@ def test_downsample_core(block_size: int, target_rate: float):
60
61
  in_msgs = list(msg_generator())
61
62
  backup = [copy.deepcopy(msg) for msg in in_msgs]
62
63
 
63
- proc = downsample(axis="time", target_rate=target_rate)
64
+ proc = downsample(axis="time", target_rate=target_rate, factor=factor)
64
65
  out_msgs = []
65
66
  for msg in in_msgs:
66
67
  res = proc.send(msg)
@@ -70,7 +71,7 @@ def test_downsample_core(block_size: int, target_rate: float):
70
71
  assert_messages_equal(in_msgs, backup)
71
72
 
72
73
  # Assert correctness of gain
73
- expected_factor: int = int(in_fs // target_rate)
74
+ expected_factor: int = int(in_fs // target_rate) if factor is None else factor
74
75
  assert all(msg.axes["time"].gain == expected_factor / in_fs for msg in out_msgs)
75
76
 
76
77
  # Assert messages have the correct timestamps
@@ -132,7 +133,13 @@ class DownsampleSystem(ez.Collection):
132
133
 
133
134
  @pytest.mark.parametrize("block_size", [10])
134
135
  @pytest.mark.parametrize("target_rate", [6.3])
135
- def test_downsample_system(block_size: int, target_rate: float, test_name: str | None = None):
136
+ @pytest.mark.parametrize("factor", [None, 2])
137
+ def test_downsample_system(
138
+ block_size: int,
139
+ target_rate: float,
140
+ factor: int | None,
141
+ test_name: str | None = None,
142
+ ):
136
143
  in_fs = 19.0
137
144
  num_msgs = int(4.0 / (block_size / in_fs)) # Ensure 4 seconds of data
138
145
 
@@ -146,7 +153,7 @@ def test_downsample_system(block_size: int, target_rate: float, test_name: str |
146
153
  fs=in_fs,
147
154
  dispatch_rate=20.0,
148
155
  ),
149
- down_settings=DownsampleSettings(target_rate=target_rate),
156
+ down_settings=DownsampleSettings(target_rate=target_rate, factor=factor),
150
157
  log_settings=MessageLoggerSettings(output=test_filename),
151
158
  term_settings=TerminateTestSettings(time=1.0),
152
159
  )
@@ -160,7 +167,7 @@ def test_downsample_system(block_size: int, target_rate: float, test_name: str |
160
167
  ez.logger.info(f"Analyzing recording of { len( messages ) } messages...")
161
168
 
162
169
  # Check fs
163
- expected_factor: int = int(in_fs // target_rate)
170
+ expected_factor: int = int(in_fs // target_rate) if factor is None else factor
164
171
  out_fs = in_fs / expected_factor
165
172
  assert np.allclose(
166
173
  np.array([1 / msg.axes["time"].gain for msg in messages]),