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.
- ezmsg/sigproc/__init__.py +1 -1
- ezmsg/sigproc/__version__.py +34 -1
- ezmsg/sigproc/activation.py +78 -0
- ezmsg/sigproc/adaptive_lattice_notch.py +212 -0
- ezmsg/sigproc/affinetransform.py +235 -0
- ezmsg/sigproc/aggregate.py +276 -0
- ezmsg/sigproc/bandpower.py +80 -0
- ezmsg/sigproc/base.py +149 -0
- ezmsg/sigproc/butterworthfilter.py +129 -39
- ezmsg/sigproc/butterworthzerophase.py +305 -0
- ezmsg/sigproc/cheby.py +125 -0
- ezmsg/sigproc/combfilter.py +160 -0
- ezmsg/sigproc/coordinatespaces.py +159 -0
- ezmsg/sigproc/decimate.py +46 -18
- ezmsg/sigproc/denormalize.py +78 -0
- ezmsg/sigproc/detrend.py +28 -0
- ezmsg/sigproc/diff.py +82 -0
- ezmsg/sigproc/downsample.py +97 -49
- ezmsg/sigproc/ewma.py +217 -0
- ezmsg/sigproc/ewmfilter.py +45 -19
- ezmsg/sigproc/extract_axis.py +39 -0
- ezmsg/sigproc/fbcca.py +307 -0
- ezmsg/sigproc/filter.py +282 -117
- ezmsg/sigproc/filterbank.py +292 -0
- ezmsg/sigproc/filterbankdesign.py +129 -0
- ezmsg/sigproc/fir_hilbert.py +336 -0
- ezmsg/sigproc/fir_pmc.py +209 -0
- ezmsg/sigproc/firfilter.py +117 -0
- ezmsg/sigproc/gaussiansmoothing.py +89 -0
- ezmsg/sigproc/kaiser.py +106 -0
- ezmsg/sigproc/linear.py +120 -0
- ezmsg/sigproc/math/__init__.py +0 -0
- ezmsg/sigproc/math/abs.py +35 -0
- ezmsg/sigproc/math/add.py +120 -0
- ezmsg/sigproc/math/clip.py +48 -0
- ezmsg/sigproc/math/difference.py +143 -0
- ezmsg/sigproc/math/invert.py +28 -0
- ezmsg/sigproc/math/log.py +57 -0
- ezmsg/sigproc/math/scale.py +39 -0
- ezmsg/sigproc/messages.py +3 -6
- ezmsg/sigproc/quantize.py +68 -0
- ezmsg/sigproc/resample.py +278 -0
- ezmsg/sigproc/rollingscaler.py +232 -0
- ezmsg/sigproc/sampler.py +232 -241
- ezmsg/sigproc/scaler.py +165 -0
- ezmsg/sigproc/signalinjector.py +70 -0
- ezmsg/sigproc/slicer.py +138 -0
- ezmsg/sigproc/spectral.py +6 -132
- ezmsg/sigproc/spectrogram.py +90 -0
- ezmsg/sigproc/spectrum.py +277 -0
- ezmsg/sigproc/transpose.py +134 -0
- ezmsg/sigproc/util/__init__.py +0 -0
- ezmsg/sigproc/util/asio.py +25 -0
- ezmsg/sigproc/util/axisarray_buffer.py +365 -0
- ezmsg/sigproc/util/buffer.py +449 -0
- ezmsg/sigproc/util/message.py +17 -0
- ezmsg/sigproc/util/profile.py +23 -0
- ezmsg/sigproc/util/sparse.py +115 -0
- ezmsg/sigproc/util/typeresolution.py +17 -0
- ezmsg/sigproc/wavelets.py +187 -0
- ezmsg/sigproc/window.py +301 -117
- ezmsg_sigproc-2.10.0.dist-info/METADATA +60 -0
- ezmsg_sigproc-2.10.0.dist-info/RECORD +65 -0
- {ezmsg_sigproc-1.2.2.dist-info → ezmsg_sigproc-2.10.0.dist-info}/WHEEL +1 -2
- ezmsg/sigproc/synth.py +0 -411
- ezmsg_sigproc-1.2.2.dist-info/METADATA +0 -36
- ezmsg_sigproc-1.2.2.dist-info/RECORD +0 -17
- ezmsg_sigproc-1.2.2.dist-info/top_level.txt +0 -1
- /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,,
|
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
|
/ezmsg_sigproc-1.2.2.dist-info/LICENSE.txt → /ezmsg_sigproc-2.10.0.dist-info/licenses/LICENSE
RENAMED
|
File without changes
|