ezmsg-sigproc 1.2.2__py3-none-any.whl → 2.10.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.
Files changed (69) hide show
  1. ezmsg/sigproc/__init__.py +1 -1
  2. ezmsg/sigproc/__version__.py +34 -1
  3. ezmsg/sigproc/activation.py +78 -0
  4. ezmsg/sigproc/adaptive_lattice_notch.py +212 -0
  5. ezmsg/sigproc/affinetransform.py +235 -0
  6. ezmsg/sigproc/aggregate.py +276 -0
  7. ezmsg/sigproc/bandpower.py +80 -0
  8. ezmsg/sigproc/base.py +149 -0
  9. ezmsg/sigproc/butterworthfilter.py +129 -39
  10. ezmsg/sigproc/butterworthzerophase.py +305 -0
  11. ezmsg/sigproc/cheby.py +125 -0
  12. ezmsg/sigproc/combfilter.py +160 -0
  13. ezmsg/sigproc/coordinatespaces.py +159 -0
  14. ezmsg/sigproc/decimate.py +46 -18
  15. ezmsg/sigproc/denormalize.py +78 -0
  16. ezmsg/sigproc/detrend.py +28 -0
  17. ezmsg/sigproc/diff.py +82 -0
  18. ezmsg/sigproc/downsample.py +97 -49
  19. ezmsg/sigproc/ewma.py +217 -0
  20. ezmsg/sigproc/ewmfilter.py +45 -19
  21. ezmsg/sigproc/extract_axis.py +39 -0
  22. ezmsg/sigproc/fbcca.py +307 -0
  23. ezmsg/sigproc/filter.py +282 -117
  24. ezmsg/sigproc/filterbank.py +292 -0
  25. ezmsg/sigproc/filterbankdesign.py +129 -0
  26. ezmsg/sigproc/fir_hilbert.py +336 -0
  27. ezmsg/sigproc/fir_pmc.py +209 -0
  28. ezmsg/sigproc/firfilter.py +117 -0
  29. ezmsg/sigproc/gaussiansmoothing.py +89 -0
  30. ezmsg/sigproc/kaiser.py +106 -0
  31. ezmsg/sigproc/linear.py +120 -0
  32. ezmsg/sigproc/math/__init__.py +0 -0
  33. ezmsg/sigproc/math/abs.py +35 -0
  34. ezmsg/sigproc/math/add.py +120 -0
  35. ezmsg/sigproc/math/clip.py +48 -0
  36. ezmsg/sigproc/math/difference.py +143 -0
  37. ezmsg/sigproc/math/invert.py +28 -0
  38. ezmsg/sigproc/math/log.py +57 -0
  39. ezmsg/sigproc/math/scale.py +39 -0
  40. ezmsg/sigproc/messages.py +3 -6
  41. ezmsg/sigproc/quantize.py +68 -0
  42. ezmsg/sigproc/resample.py +278 -0
  43. ezmsg/sigproc/rollingscaler.py +232 -0
  44. ezmsg/sigproc/sampler.py +232 -241
  45. ezmsg/sigproc/scaler.py +165 -0
  46. ezmsg/sigproc/signalinjector.py +70 -0
  47. ezmsg/sigproc/slicer.py +138 -0
  48. ezmsg/sigproc/spectral.py +6 -132
  49. ezmsg/sigproc/spectrogram.py +90 -0
  50. ezmsg/sigproc/spectrum.py +277 -0
  51. ezmsg/sigproc/transpose.py +134 -0
  52. ezmsg/sigproc/util/__init__.py +0 -0
  53. ezmsg/sigproc/util/asio.py +25 -0
  54. ezmsg/sigproc/util/axisarray_buffer.py +365 -0
  55. ezmsg/sigproc/util/buffer.py +449 -0
  56. ezmsg/sigproc/util/message.py +17 -0
  57. ezmsg/sigproc/util/profile.py +23 -0
  58. ezmsg/sigproc/util/sparse.py +115 -0
  59. ezmsg/sigproc/util/typeresolution.py +17 -0
  60. ezmsg/sigproc/wavelets.py +187 -0
  61. ezmsg/sigproc/window.py +301 -117
  62. ezmsg_sigproc-2.10.0.dist-info/METADATA +60 -0
  63. ezmsg_sigproc-2.10.0.dist-info/RECORD +65 -0
  64. {ezmsg_sigproc-1.2.2.dist-info → ezmsg_sigproc-2.10.0.dist-info}/WHEEL +1 -2
  65. ezmsg/sigproc/synth.py +0 -411
  66. ezmsg_sigproc-1.2.2.dist-info/METADATA +0 -36
  67. ezmsg_sigproc-1.2.2.dist-info/RECORD +0 -17
  68. ezmsg_sigproc-1.2.2.dist-info/top_level.txt +0 -1
  69. /ezmsg_sigproc-1.2.2.dist-info/LICENSE.txt → /ezmsg_sigproc-2.10.0.dist-info/licenses/LICENSE +0 -0
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: ezmsg-sigproc
3
+ Version: 2.10.0
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>, Kyle McGraw <kmcgraw@blackrockneuro.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10.15
9
+ Requires-Dist: array-api-compat>=1.11.1
10
+ Requires-Dist: ezmsg-baseproc>=1.1.0
11
+ Requires-Dist: ezmsg>=3.6.0
12
+ Requires-Dist: numba>=0.61.0
13
+ Requires-Dist: numpy>=1.26.0
14
+ Requires-Dist: pywavelets>=1.6.0
15
+ Requires-Dist: scipy>=1.13.1
16
+ Requires-Dist: sparse>=0.15.4
17
+ Description-Content-Type: text/markdown
18
+
19
+ # ezmsg-sigproc
20
+
21
+ Signal processing primitives for the [ezmsg](https://www.ezmsg.org) message-passing framework.
22
+
23
+ ## Features
24
+
25
+ * **Filtering** - Chebyshev, comb filters, and more
26
+ * **Spectral analysis** - Spectrogram, spectrum, and wavelet transforms
27
+ * **Resampling** - Downsample, decimate, and resample operations
28
+ * **Windowing** - Sliding windows and buffering utilities
29
+ * **Math operations** - Arithmetic, log, abs, difference, and more
30
+ * **Signal generation** - Synthetic signal generators
31
+
32
+ All modules use `AxisArray` as the primary data structure for passing signals between components.
33
+
34
+ ## Installation
35
+
36
+ Install from PyPI:
37
+
38
+ ```bash
39
+ pip install ezmsg-sigproc
40
+ ```
41
+
42
+ Or install from GitHub for the latest development version:
43
+
44
+ ```bash
45
+ pip install git+https://github.com/ezmsg-org/ezmsg-sigproc.git@dev
46
+ ```
47
+
48
+ ## Documentation
49
+
50
+ Full documentation is available at [ezmsg.org](https://www.ezmsg.org).
51
+
52
+ ## Development
53
+
54
+ We use [`uv`](https://docs.astral.sh/uv/) for development.
55
+
56
+ 1. Fork and clone the repository
57
+ 2. `uv sync` to create a virtual environment and install dependencies
58
+ 3. `uv run pre-commit install` to set up linting and formatting hooks
59
+ 4. `uv run pytest tests` to run the test suite
60
+ 5. Submit a PR against the `dev` branch
@@ -0,0 +1,65 @@
1
+ ezmsg/sigproc/__init__.py,sha256=8K4IcOA3-pfzadoM6s2Sfg5460KlJUocGgyTJTJl96U,52
2
+ ezmsg/sigproc/__version__.py,sha256=2cFLkHY3I9n23ToIWm38PohvvIEYjdsebREPVvOKAiU,706
3
+ ezmsg/sigproc/activation.py,sha256=83vnTa3ZcC4Q3VSWcGfaqhCEqYRNySUOyVpMHZXfz-c,2755
4
+ ezmsg/sigproc/adaptive_lattice_notch.py,sha256=ThUR48mbSHuThkimtD0j4IXNMrOVcpZgGhE7PCYfXhU,8818
5
+ ezmsg/sigproc/affinetransform.py,sha256=jl7DiSa5Yb0qsmFJbfSiSeGmvK1SGoBgycFC5JU5DVY,9434
6
+ ezmsg/sigproc/aggregate.py,sha256=7Hdz1m-S6Cl9h0oRQHeS_UTGBemhOB4XdFyX6cGcdHo,9362
7
+ ezmsg/sigproc/bandpower.py,sha256=dAhH56sUrXNhcRFymTTwjdM_KcU5OxFzrR_sxIPAxyw,2264
8
+ ezmsg/sigproc/base.py,sha256=SJvKEb8gw6mUMwlV5sH0iPG0bXrgS8tvkPwhI-j89MQ,3672
9
+ ezmsg/sigproc/butterworthfilter.py,sha256=NKTGkgjvlmC1Dc9gD2Z6UBzUq12KicfnczrzM5ZTosk,5255
10
+ ezmsg/sigproc/butterworthzerophase.py,sha256=CU6cXkI6j1LQCEz0sr2IthAPCq_TEtbvSb7h2Nw1w74,11820
11
+ ezmsg/sigproc/cheby.py,sha256=B8pGt5_pOBpNZCmaibNl_NKkyuasd8ZEJXeTDCTaino,3711
12
+ ezmsg/sigproc/combfilter.py,sha256=MSxr1I-jBePW_9AuCiv3RQ1HUNxIsNhLk0q1Iu8ikAw,4766
13
+ ezmsg/sigproc/coordinatespaces.py,sha256=bp_0fTS9b27OQqLoFzgE3f9rb287P8y0S1dWWGrS08o,5298
14
+ ezmsg/sigproc/decimate.py,sha256=DCX9p4ZrcGoQ9di-jmPKqiKERTkvTAtSqLg8chQLp84,2336
15
+ ezmsg/sigproc/denormalize.py,sha256=OACzoINCISNUmZcR2pqWfxi3p8uuBLCz1PMw4XNwOCs,3035
16
+ ezmsg/sigproc/detrend.py,sha256=2C-dhVy-ty4b22aE07kIF3VjqCoFitoZqyrX6hAjXYQ,894
17
+ ezmsg/sigproc/diff.py,sha256=nDf9-XywZKlfeainIeubl_NccmkuAdLjYMcWSPMquss,3179
18
+ ezmsg/sigproc/downsample.py,sha256=Jqxt1Va1FrQLH1wUQpP0U79iQARTTHEKklKHy7yGL2o,3679
19
+ ezmsg/sigproc/ewma.py,sha256=ecLq1ZlON85NWdW_5cz9chWirxjf4y9zCPMOt3IqQuk,7723
20
+ ezmsg/sigproc/ewmfilter.py,sha256=9AuvVn3TDdf5R4bVolYNM46AtDVHFJa8MLGltY6mYPE,4795
21
+ ezmsg/sigproc/extract_axis.py,sha256=T2e9zW8N0bwOj4_zlB3I0oT0iwaBijpzF6wrFsCfD24,1593
22
+ ezmsg/sigproc/fbcca.py,sha256=JYFWsMDRJEWwUNujr4EsFL5t1ux-cnBGamNVrCRO_RA,12043
23
+ ezmsg/sigproc/filter.py,sha256=IWg3J5IGVdv7kfp4zaKRzfjEuRa5xNzcq6fHtq4tPCM,11604
24
+ ezmsg/sigproc/filterbank.py,sha256=tD7fn4dZzEvsmp_sSn16VVJ4WcJ5sN5lsSuNTLDCQZ8,13056
25
+ ezmsg/sigproc/filterbankdesign.py,sha256=vLXQVJwoFEK4V6umqzcr1PJKcwv6pGO29klSWQXk7y0,4712
26
+ ezmsg/sigproc/fir_hilbert.py,sha256=ByZDsDFjbJx-EgLZF85vZCPQbprQaiMEuG2dyvTPiDY,10855
27
+ ezmsg/sigproc/fir_pmc.py,sha256=Ze2pd9K8XrdDhgJZG2E7x-5C2hfd6kIns4bYjTJsBvc,6997
28
+ ezmsg/sigproc/firfilter.py,sha256=7r_I476nYuixJsuwc_hQ0Fbq8WB0gnYBZUKs3zultOQ,3790
29
+ ezmsg/sigproc/gaussiansmoothing.py,sha256=BfXm9YQoOtieM4ABK2KRgxeQz055rd7mqtTVqmjT3Rk,2672
30
+ ezmsg/sigproc/kaiser.py,sha256=dIwgHbLXUHPtdotsGNLE9VG_clhcMgvVnSoFkMVgF9M,3483
31
+ ezmsg/sigproc/linear.py,sha256=b3NRzQNBvdU2jqenZT9XXFHax9Mavbj2xFiVxOwl1Ms,4662
32
+ ezmsg/sigproc/messages.py,sha256=KQczHTeifn4BZycChN8ZcpfZoQW3lC_xuFmN72QT97s,925
33
+ ezmsg/sigproc/quantize.py,sha256=uSM2z2xXwL0dgSltyzLEmlKjaJZ2meA3PDWX8_bM0Hs,2195
34
+ ezmsg/sigproc/resample.py,sha256=3mm9pvxryNVhQuTCIMW3ToUkUfbVOCsIgvXUiurit1Y,11389
35
+ ezmsg/sigproc/rollingscaler.py,sha256=e-smSKDhmDD2nWIf6I77CtRxQp_7sHS268SGPi7aXp8,8499
36
+ ezmsg/sigproc/sampler.py,sha256=iOk2YoUX22u9iTjFKimzP5V074RDBVcmswgfyxvZRZo,10761
37
+ ezmsg/sigproc/scaler.py,sha256=oBZa6uzyftChvk6aqBD5clil6pedx3IF-dptrb74EA0,5888
38
+ ezmsg/sigproc/signalinjector.py,sha256=mB62H2b-ScgPtH1jajEpxgDHqdb-RKekQfgyNncsE8Y,2874
39
+ ezmsg/sigproc/slicer.py,sha256=xLXxWf722V08ytVwvPimYjDKKj0pkC2HjdgCVaoaOvs,5195
40
+ ezmsg/sigproc/spectral.py,sha256=wFzuihS7qJZMQcp0ds_qCG-zCbvh5DyhFRjn2wA9TWQ,322
41
+ ezmsg/sigproc/spectrogram.py,sha256=g8xYWENzle6O5uEF-vfjsF5gOSDnJTwiu3ZudicO470,2893
42
+ ezmsg/sigproc/spectrum.py,sha256=AAxrywIYpAPENRWvaaM2VjcKEaqMt6ra1E3Ao26jdZs,9523
43
+ ezmsg/sigproc/transpose.py,sha256=uRGieeYRFTO9gAhWzkq49f-Qo49rpDQhn9r3nLuwx4I,4983
44
+ ezmsg/sigproc/wavelets.py,sha256=mN9TrZencyvKBfnuMiGZ_lzrE1O7DhVo05EYgCjXncg,7428
45
+ ezmsg/sigproc/window.py,sha256=ZlawY4TPbLc46qIgcKhP4X7dpMDo4zNlnfzgV1eFaGU,15335
46
+ ezmsg/sigproc/math/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
+ ezmsg/sigproc/math/abs.py,sha256=Zrt0xhGnhf90Vk00S1PYkbeCOQshqkvh1HLj26N4AL0,979
48
+ ezmsg/sigproc/math/add.py,sha256=UikpS_vhaY58ssf46C1r4_tnRUFMl86xCmd5Y8GFZ1Q,3666
49
+ ezmsg/sigproc/math/clip.py,sha256=1D6mUlOzBB7L35G_KKYZmfg7nYlbuDdITV4EH0R-yUo,1529
50
+ ezmsg/sigproc/math/difference.py,sha256=uUYZgbLe-GrFSN6EOFjs9fQZllp827IluxL6m8TJuH8,4791
51
+ ezmsg/sigproc/math/invert.py,sha256=nz8jbfvDoez6s9NmAprBtTAI5oSDj0wNUPk8j13XiVk,855
52
+ ezmsg/sigproc/math/log.py,sha256=JhjSqLnQnvx_3F4txRYHuUPSJ12Yj2HvRTsCMNvlxpo,2022
53
+ ezmsg/sigproc/math/scale.py,sha256=4_xHcHNuf13E1fxIF5vbkPfkN4En6zkfPIKID7lCERk,1133
54
+ ezmsg/sigproc/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
+ ezmsg/sigproc/util/asio.py,sha256=aAj0e7OoBvkRy28k05HL2s9YPCTxOddc05xMN-qd4lQ,577
56
+ ezmsg/sigproc/util/axisarray_buffer.py,sha256=TGDeC6CXmmp7OUuiGd6xYQijRGYDE4QGdWxjK5Vs3nE,14057
57
+ ezmsg/sigproc/util/buffer.py,sha256=83Gm0IuowmcMlXgLFB_rz8_ZPhkwG4DNNejyWJDKJl8,19658
58
+ ezmsg/sigproc/util/message.py,sha256=ppN3IYtIAwrxWG9JOvgWFn1wDdIumkEzYFfqpH9VQkY,338
59
+ ezmsg/sigproc/util/profile.py,sha256=eVOo9pXgusrnH1yfRdd2RsM7Dbe2UpyC0LJ9MfGpB08,416
60
+ ezmsg/sigproc/util/sparse.py,sha256=NjbJitCtO0B6CENTlyd9c-lHEJwoCan-T3DIgPyeShw,4834
61
+ ezmsg/sigproc/util/typeresolution.py,sha256=fMFzLi63dqCIclGFLcMdM870OYxJnkeWw6aWKNMk718,362
62
+ ezmsg_sigproc-2.10.0.dist-info/METADATA,sha256=lsHkW6Abh6GK3bOOl5MHtXthhwOQWuVUrByPz2AYOfk,1909
63
+ ezmsg_sigproc-2.10.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
64
+ ezmsg_sigproc-2.10.0.dist-info/licenses/LICENSE,sha256=seu0tKhhAMPCUgc1XpXGGaCxY1YaYvFJwqFuQZAl2go,1100
65
+ ezmsg_sigproc-2.10.0.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
ezmsg/sigproc/synth.py DELETED
@@ -1,411 +0,0 @@
1
- import asyncio
2
- import time
3
- from dataclasses import dataclass, replace, field
4
-
5
- import ezmsg.core as ez
6
- import numpy as np
7
-
8
- from ezmsg.util.messages.axisarray import AxisArray
9
-
10
- from .butterworthfilter import ButterworthFilter, ButterworthFilterSettings
11
-
12
- from typing import Optional, AsyncGenerator, Union
13
-
14
-
15
- class ClockSettings(ez.Settings):
16
- # Message dispatch rate (Hz), or None (fast as possible)
17
- dispatch_rate: Optional[float]
18
-
19
-
20
- class ClockState(ez.State):
21
- cur_settings: ClockSettings
22
-
23
-
24
- class Clock(ez.Unit):
25
- SETTINGS: ClockSettings
26
- STATE: ClockState
27
-
28
- INPUT_SETTINGS = ez.InputStream(ClockSettings)
29
- OUTPUT_CLOCK = ez.OutputStream(ez.Flag)
30
-
31
- def initialize(self) -> None:
32
- self.STATE.cur_settings = self.SETTINGS
33
-
34
- @ez.subscriber(INPUT_SETTINGS)
35
- async def on_settings(self, msg: ClockSettings) -> None:
36
- self.STATE.cur_settings = msg
37
-
38
- @ez.publisher(OUTPUT_CLOCK)
39
- async def generate(self) -> AsyncGenerator:
40
- while True:
41
- if self.STATE.cur_settings.dispatch_rate is not None:
42
- await asyncio.sleep(1.0 / self.STATE.cur_settings.dispatch_rate)
43
- yield self.OUTPUT_CLOCK, ez.Flag
44
-
45
-
46
- class CounterSettings(ez.Settings):
47
- """
48
- TODO: Adapt this to use ezmsg.util.rate?
49
- NOTE: This module uses asyncio.sleep to delay appropriately in realtime mode.
50
- This method of sleeping/yielding execution priority has quirky behavior with
51
- sub-millisecond sleep periods which may result in unexpected behavior (e.g.
52
- fs = 2000, n_time = 1, realtime = True -- may result in ~1400 msgs/sec)
53
- """
54
-
55
- n_time: int # Number of samples to output per block
56
- fs: float # Sampling rate of signal output in Hz
57
- n_ch: int = 1 # Number of channels to synthesize
58
-
59
- # Message dispatch rate (Hz), 'realtime', 'ext_clock', or None (fast as possible)
60
- dispatch_rate: Optional[Union[float, str]] = None
61
-
62
- # If set to an integer, counter will rollover
63
- mod: Optional[int] = None
64
-
65
-
66
- class CounterState(ez.State):
67
- cur_settings: CounterSettings
68
- samp: int = 0 # current sample counter
69
- clock_event: asyncio.Event
70
-
71
-
72
- class Counter(ez.Unit):
73
- """Generates monotonically increasing counter"""
74
-
75
- SETTINGS: CounterSettings
76
- STATE: CounterState
77
-
78
- INPUT_CLOCK = ez.InputStream(ez.Flag)
79
- INPUT_SETTINGS = ez.InputStream(CounterSettings)
80
- OUTPUT_SIGNAL = ez.OutputStream(AxisArray)
81
-
82
- def initialize(self) -> None:
83
- self.STATE.clock_event = asyncio.Event()
84
- self.STATE.clock_event.clear()
85
- self.validate_settings(self.SETTINGS)
86
-
87
- @ez.subscriber(INPUT_SETTINGS)
88
- async def on_settings(self, msg: CounterSettings) -> None:
89
- self.validate_settings(msg)
90
-
91
- def validate_settings(self, settings: CounterSettings) -> None:
92
- if isinstance(
93
- settings.dispatch_rate, str
94
- ) and self.SETTINGS.dispatch_rate not in ["realtime", "ext_clock"]:
95
- raise ValueError(f"Unknown dispatch_rate: {self.SETTINGS.dispatch_rate}")
96
-
97
- self.STATE.cur_settings = settings
98
-
99
- @ez.subscriber(INPUT_CLOCK)
100
- async def on_clock(self, _: ez.Flag):
101
- self.STATE.clock_event.set()
102
-
103
- @ez.publisher(OUTPUT_SIGNAL)
104
- async def publish(self) -> AsyncGenerator:
105
- while True:
106
- block_dur = self.STATE.cur_settings.n_time / self.STATE.cur_settings.fs
107
-
108
- dispatch_rate = self.STATE.cur_settings.dispatch_rate
109
- if dispatch_rate is not None:
110
- if isinstance(dispatch_rate, str):
111
- if dispatch_rate == "realtime":
112
- await asyncio.sleep(block_dur)
113
- elif dispatch_rate == "ext_clock":
114
- await self.STATE.clock_event.wait()
115
- self.STATE.clock_event.clear()
116
- else:
117
- await asyncio.sleep(1.0 / dispatch_rate)
118
-
119
- block_samp = np.arange(self.STATE.cur_settings.n_time)[:, np.newaxis]
120
-
121
- t_samp = block_samp + self.STATE.samp
122
- self.STATE.samp = t_samp[-1] + 1
123
-
124
- if self.STATE.cur_settings.mod is not None:
125
- t_samp %= self.STATE.cur_settings.mod
126
- self.STATE.samp %= self.STATE.cur_settings.mod
127
-
128
- t_samp = np.tile(t_samp, (1, self.STATE.cur_settings.n_ch))
129
-
130
- offset_adj = self.STATE.cur_settings.n_time / self.STATE.cur_settings.fs
131
-
132
- out = AxisArray(
133
- t_samp,
134
- dims=["time", "ch"],
135
- axes=dict(
136
- time=AxisArray.Axis.TimeAxis(
137
- fs=self.STATE.cur_settings.fs, offset=time.time() - offset_adj
138
- )
139
- ),
140
- )
141
-
142
- yield self.OUTPUT_SIGNAL, out
143
-
144
-
145
- class SinGeneratorSettings(ez.Settings):
146
- time_axis: Optional[str] = "time"
147
- freq: float = 1.0 # Oscillation frequency in Hz
148
- amp: float = 1.0 # Amplitude
149
- phase: float = 0.0 # Phase offset (in radians)
150
-
151
-
152
- class SinGeneratorState(ez.State):
153
- ang_freq: float # pre-calculated angular frequency in radians
154
-
155
-
156
- class SinGenerator(ez.Unit):
157
- SETTINGS: SinGeneratorSettings
158
- STATE: SinGeneratorState
159
-
160
- INPUT_SIGNAL = ez.InputStream(AxisArray)
161
- OUTPUT_SIGNAL = ez.OutputStream(AxisArray)
162
-
163
- def initialize(self) -> None:
164
- self.STATE.ang_freq = 2.0 * np.pi * self.SETTINGS.freq
165
-
166
- @ez.subscriber(INPUT_SIGNAL)
167
- @ez.publisher(OUTPUT_SIGNAL)
168
- async def generate(self, msg: AxisArray) -> AsyncGenerator:
169
- """
170
- msg is assumed to be a monotonically increasing counter ..
171
- .. or at least a counter with an intelligently chosen modulus
172
- """
173
- axis_name = self.SETTINGS.time_axis
174
- if axis_name is None:
175
- axis_name = msg.dims[0]
176
- fs = 1.0 / msg.get_axis(axis_name).gain
177
- t_sec = msg.data / fs
178
- w = self.STATE.ang_freq * t_sec
179
- out_data = self.SETTINGS.amp * np.sin(w + self.SETTINGS.phase)
180
- yield (self.OUTPUT_SIGNAL, replace(msg, data=out_data))
181
-
182
-
183
- class OscillatorSettings(ez.Settings):
184
- n_time: int # Number of samples to output per block
185
- fs: float # Sampling rate of signal output in Hz
186
- n_ch: int = 1 # Number of channels to output per block
187
- dispatch_rate: Optional[Union[float, str]] = None # (Hz) | 'realtime' | 'ext_clock'
188
- freq: float = 1.0 # Oscillation frequency in Hz
189
- amp: float = 1.0 # Amplitude
190
- phase: float = 0.0 # Phase offset (in radians)
191
- sync: bool = False # Adjust `freq` to sync with sampling rate
192
-
193
-
194
- class Oscillator(ez.Collection):
195
- SETTINGS: OscillatorSettings
196
-
197
- INPUT_CLOCK = ez.InputStream(ez.Flag)
198
- OUTPUT_SIGNAL = ez.OutputStream(AxisArray)
199
-
200
- COUNTER = Counter()
201
- SIN = SinGenerator()
202
-
203
- def configure(self) -> None:
204
- # Calculate synchronous settings if necessary
205
- freq = self.SETTINGS.freq
206
- mod = None
207
- if self.SETTINGS.sync:
208
- period = 1.0 / self.SETTINGS.freq
209
- mod = round(period * self.SETTINGS.fs)
210
- freq = 1.0 / (mod / self.SETTINGS.fs)
211
-
212
- self.COUNTER.apply_settings(
213
- CounterSettings(
214
- n_time=self.SETTINGS.n_time,
215
- fs=self.SETTINGS.fs,
216
- n_ch=self.SETTINGS.n_ch,
217
- dispatch_rate=self.SETTINGS.dispatch_rate,
218
- mod=mod,
219
- )
220
- )
221
-
222
- self.SIN.apply_settings(
223
- SinGeneratorSettings(
224
- freq=freq, amp=self.SETTINGS.amp, phase=self.SETTINGS.phase
225
- )
226
- )
227
-
228
- def network(self) -> ez.NetworkDefinition:
229
- return (
230
- (self.INPUT_CLOCK, self.COUNTER.INPUT_CLOCK),
231
- (self.COUNTER.OUTPUT_SIGNAL, self.SIN.INPUT_SIGNAL),
232
- (self.SIN.OUTPUT_SIGNAL, self.OUTPUT_SIGNAL),
233
- )
234
-
235
-
236
- class RandomGeneratorSettings(ez.Settings):
237
- loc: float = 0.0
238
- scale: float = 1.0
239
-
240
-
241
- class RandomGenerator(ez.Unit):
242
- SETTINGS: RandomGeneratorSettings
243
-
244
- INPUT_SIGNAL = ez.InputStream(AxisArray)
245
- OUTPUT_SIGNAL = ez.OutputStream(AxisArray)
246
-
247
- @ez.subscriber(INPUT_SIGNAL)
248
- @ez.publisher(OUTPUT_SIGNAL)
249
- async def generate(self, msg: AxisArray) -> AsyncGenerator:
250
- random_data = np.random.normal(
251
- size=msg.shape, loc=self.SETTINGS.loc, scale=self.SETTINGS.scale
252
- )
253
-
254
- yield self.OUTPUT_SIGNAL, replace(msg, data=random_data)
255
-
256
-
257
- class NoiseSettings(ez.Settings):
258
- n_time: int # Number of samples to output per block
259
- fs: float # Sampling rate of signal output in Hz
260
- n_ch: int = 1 # Number of channels to output
261
- dispatch_rate: Optional[
262
- Union[float, str]
263
- ] = None # (Hz), 'realtime', or 'ext_clock'
264
- loc: float = 0.0 # DC offset
265
- scale: float = 1.0 # Scale (in standard deviations)
266
-
267
-
268
- WhiteNoiseSettings = NoiseSettings
269
-
270
-
271
- class WhiteNoise(ez.Collection):
272
- SETTINGS: NoiseSettings
273
-
274
- INPUT_CLOCK = ez.InputStream(ez.Flag)
275
- OUTPUT_SIGNAL = ez.OutputStream(AxisArray)
276
-
277
- COUNTER = Counter()
278
- RANDOM = RandomGenerator()
279
-
280
- def configure(self) -> None:
281
- self.RANDOM.apply_settings(
282
- RandomGeneratorSettings(loc=self.SETTINGS.loc, scale=self.SETTINGS.scale)
283
- )
284
-
285
- self.COUNTER.apply_settings(
286
- CounterSettings(
287
- n_time=self.SETTINGS.n_time,
288
- fs=self.SETTINGS.fs,
289
- n_ch=self.SETTINGS.n_ch,
290
- dispatch_rate=self.SETTINGS.dispatch_rate,
291
- mod=None,
292
- )
293
- )
294
-
295
- def network(self) -> ez.NetworkDefinition:
296
- return (
297
- (self.INPUT_CLOCK, self.COUNTER.INPUT_CLOCK),
298
- (self.COUNTER.OUTPUT_SIGNAL, self.RANDOM.INPUT_SIGNAL),
299
- (self.RANDOM.OUTPUT_SIGNAL, self.OUTPUT_SIGNAL),
300
- )
301
-
302
-
303
- PinkNoiseSettings = NoiseSettings
304
-
305
-
306
- class PinkNoise(ez.Collection):
307
- SETTINGS: PinkNoiseSettings
308
-
309
- INPUT_CLOCK = ez.InputStream(ez.Flag)
310
- OUTPUT_SIGNAL = ez.OutputStream(AxisArray)
311
-
312
- WHITE_NOISE = WhiteNoise()
313
- FILTER = ButterworthFilter()
314
-
315
- def configure(self) -> None:
316
- self.WHITE_NOISE.apply_settings(self.SETTINGS)
317
- self.FILTER.apply_settings(
318
- ButterworthFilterSettings(
319
- axis="time", order=1, cutoff=self.SETTINGS.fs * 0.01 # Hz
320
- )
321
- )
322
-
323
- def network(self) -> ez.NetworkDefinition:
324
- return (
325
- (self.INPUT_CLOCK, self.WHITE_NOISE.INPUT_CLOCK),
326
- (self.WHITE_NOISE.OUTPUT_SIGNAL, self.FILTER.INPUT_SIGNAL),
327
- (self.FILTER.OUTPUT_SIGNAL, self.OUTPUT_SIGNAL),
328
- )
329
-
330
-
331
- class AddState(ez.State):
332
- queue_a: "asyncio.Queue[AxisArray]" = field(default_factory=asyncio.Queue)
333
- queue_b: "asyncio.Queue[AxisArray]" = field(default_factory=asyncio.Queue)
334
-
335
-
336
- class Add(ez.Unit):
337
- """Add two signals together. Assumes compatible/similar axes/dimensions."""
338
-
339
- STATE: AddState
340
-
341
- INPUT_SIGNAL_A = ez.InputStream(AxisArray)
342
- INPUT_SIGNAL_B = ez.InputStream(AxisArray)
343
- OUTPUT_SIGNAL = ez.OutputStream(AxisArray)
344
-
345
- @ez.subscriber(INPUT_SIGNAL_A)
346
- async def on_a(self, msg: AxisArray) -> None:
347
- self.STATE.queue_a.put_nowait(msg)
348
-
349
- @ez.subscriber(INPUT_SIGNAL_B)
350
- async def on_b(self, msg: AxisArray) -> None:
351
- self.STATE.queue_b.put_nowait(msg)
352
-
353
- @ez.publisher(OUTPUT_SIGNAL)
354
- async def output(self) -> AsyncGenerator:
355
- while True:
356
- a = await self.STATE.queue_a.get()
357
- b = await self.STATE.queue_b.get()
358
-
359
- yield (self.OUTPUT_SIGNAL, replace(a, data=a.data + b.data))
360
-
361
-
362
- class EEGSynthSettings(ez.Settings):
363
- fs: float = 500.0 # Hz
364
- n_time: int = 100
365
- alpha_freq: float = 10.5 # Hz
366
- n_ch: int = 8
367
-
368
-
369
- class EEGSynth(ez.Collection):
370
- SETTINGS: EEGSynthSettings
371
-
372
- OUTPUT_SIGNAL = ez.OutputStream(AxisArray)
373
-
374
- CLOCK = Clock()
375
- NOISE = PinkNoise()
376
- OSC = Oscillator()
377
- ADD = Add()
378
-
379
- def configure(self) -> None:
380
- self.CLOCK.apply_settings(
381
- ClockSettings(dispatch_rate=self.SETTINGS.fs / self.SETTINGS.n_time)
382
- )
383
-
384
- self.OSC.apply_settings(
385
- OscillatorSettings(
386
- n_time=self.SETTINGS.n_time,
387
- fs=self.SETTINGS.fs,
388
- n_ch=self.SETTINGS.n_ch,
389
- dispatch_rate="ext_clock",
390
- freq=self.SETTINGS.alpha_freq,
391
- )
392
- )
393
-
394
- self.NOISE.apply_settings(
395
- PinkNoiseSettings(
396
- n_time=self.SETTINGS.n_time,
397
- fs=self.SETTINGS.fs,
398
- n_ch=self.SETTINGS.n_ch,
399
- dispatch_rate="ext_clock",
400
- scale=5.0,
401
- )
402
- )
403
-
404
- def network(self) -> ez.NetworkDefinition:
405
- return (
406
- (self.CLOCK.OUTPUT_CLOCK, self.OSC.INPUT_CLOCK),
407
- (self.CLOCK.OUTPUT_CLOCK, self.NOISE.INPUT_CLOCK),
408
- (self.OSC.OUTPUT_SIGNAL, self.ADD.INPUT_SIGNAL_A),
409
- (self.NOISE.OUTPUT_SIGNAL, self.ADD.INPUT_SIGNAL_B),
410
- (self.ADD.OUTPUT_SIGNAL, self.OUTPUT_SIGNAL),
411
- )
@@ -1,36 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: ezmsg-sigproc
3
- Version: 1.2.2
4
- Summary: Timeseries signal processing implementations in ezmsg
5
- Home-page: https://github.com/iscoe/ezmsg
6
- Author: Griffin Milsap
7
- Author-email: griffin.milsap@jhuapl.edu
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.8
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE.txt
13
- Requires-Dist: ezmsg >=3.3.0
14
- Requires-Dist: numpy >=1.19.5
15
- Requires-Dist: scipy >=1.6.3
16
- Provides-Extra: test
17
- Requires-Dist: pytest ; extra == 'test'
18
- Requires-Dist: pytest-cov ; extra == 'test'
19
-
20
- # ezmsg.sigproc
21
-
22
- Timeseries signal processing implementations for ezmsg
23
-
24
- ## Installation
25
- `pip install ezmsg-sigproc`
26
-
27
- ## Dependencies
28
- * `ezmsg`
29
- * `numpy`
30
- * `scipy`
31
-
32
- ## Setup (Development)
33
- 1. Install `ezmsg` either using `pip install ezmsg` or set up the repo for development as described in the `ezmsg` readme.
34
- 2. `cd` to this directory (`ezmsg-sigproc`) and run `pip install -e .`
35
- 3. Signal processing modules are available under `import ezmsg.sigproc`
36
-
@@ -1,17 +0,0 @@
1
- ezmsg/sigproc/__init__.py,sha256=e3S0EcDGJlWIp2hddSFjvt633jfeZ4W7VyAeLpfTWVM,37
2
- ezmsg/sigproc/__version__.py,sha256=uuf4VNtTNA93fMhoAur9YafzaKJFnczY-H1SSCSuRVQ,22
3
- ezmsg/sigproc/butterworthfilter.py,sha256=rCS6nUg7_qV3N0A4dN07akRvCeOjisnNYZa-FpZJrj0,2144
4
- ezmsg/sigproc/decimate.py,sha256=xQgyGSrje9hiNjMpEpGOyAUluTxm5zgY66GcKur7oYs,1328
5
- ezmsg/sigproc/downsample.py,sha256=UYP5tPsnJusLR4_YB11FmjadOZgKkgXKclt8vidPAUM,2069
6
- ezmsg/sigproc/ewmfilter.py,sha256=WL9fhW_Z9L6u_n5xiI_XnK7o44QmQ8jXZSAcvzBn_Mw,3930
7
- ezmsg/sigproc/filter.py,sha256=iPTIIaiO7N68H7gL-SDBJNug6FzXAf2IIclTXIYB71k,4819
8
- ezmsg/sigproc/messages.py,sha256=JU9l_QNL7SKY54ac9SARKf5tSxWqSAyUqvfvoamHzKs,963
9
- ezmsg/sigproc/sampler.py,sha256=xPRWz648jio1pz8U2nwm9sdcB5jypwtvnkcDc9tilTU,10107
10
- ezmsg/sigproc/spectral.py,sha256=xF4xiE7D_xo7ly3sV-TZRoUUHRAcw_yo-cjk93x9vAw,4172
11
- ezmsg/sigproc/synth.py,sha256=Rjm_q7yF2Qd9ZxuclwC1bsCKb3dMly3a--_AGkswzmc,12929
12
- ezmsg/sigproc/window.py,sha256=-Qcit-Jrwfrjr7oD6waSMOTA0dVCOKMDjDqJMx4Wp2s,5616
13
- ezmsg_sigproc-1.2.2.dist-info/LICENSE.txt,sha256=seu0tKhhAMPCUgc1XpXGGaCxY1YaYvFJwqFuQZAl2go,1100
14
- ezmsg_sigproc-1.2.2.dist-info/METADATA,sha256=gtVo3s4SRvt_P-8AsAZJhXSZ3ooQsM8pCY1uimxvaeg,1058
15
- ezmsg_sigproc-1.2.2.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
16
- ezmsg_sigproc-1.2.2.dist-info/top_level.txt,sha256=Xas7TrqbpgKTacz3JJh_2R81t8Dusuk9QtHzyu5VXPQ,6
17
- ezmsg_sigproc-1.2.2.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- ezmsg