ezmsg-sigproc 2.4.1__tar.gz → 2.5.0__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.
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/PKG-INFO +2 -2
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/pyproject.toml +1 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/__version__.py +2 -2
- ezmsg_sigproc-2.5.0/src/ezmsg/sigproc/butterworthzerophase.py +132 -0
- ezmsg_sigproc-2.5.0/src/ezmsg/sigproc/fir_hilbert.py +353 -0
- ezmsg_sigproc-2.5.0/src/ezmsg/sigproc/fir_pmc.py +214 -0
- ezmsg_sigproc-2.5.0/src/ezmsg/sigproc/rollingscaler.py +257 -0
- ezmsg_sigproc-2.5.0/tests/integration/ezmsg/test_butterworthzerophase_system.py +51 -0
- ezmsg_sigproc-2.5.0/tests/integration/ezmsg/test_fir_hilbert_system.py +48 -0
- ezmsg_sigproc-2.5.0/tests/integration/ezmsg/test_fir_pmc_system.py +51 -0
- ezmsg_sigproc-2.5.0/tests/integration/ezmsg/test_rollingscaler_system.py +48 -0
- ezmsg_sigproc-2.5.0/tests/unit/test_butterworthzerophase.py +178 -0
- ezmsg_sigproc-2.5.0/tests/unit/test_fir_hilbert.py +271 -0
- ezmsg_sigproc-2.5.0/tests/unit/test_fir_pmc.py +93 -0
- ezmsg_sigproc-2.5.0/tests/unit/test_rollingscaler.py +155 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/.github/workflows/docs.yml +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/.github/workflows/python-publish-ezmsg-sigproc.yml +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/.github/workflows/python-tests.yml +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/.gitignore +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/.pre-commit-config.yaml +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/LICENSE.txt +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/README.md +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/Makefile +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/make.bat +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/_templates/autosummary/module.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/api/index.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/conf.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/HybridBuffer.md +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/ProcessorsBase.md +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/explanations/sigproc.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/adaptive.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/checkpoint.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/composite.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/content-signalprocessing.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/processor.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/standalone.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/stateful.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/unit.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/img/HybridBufferBasic.svg +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/img/HybridBufferOverflow.svg +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/sigproc/base.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/sigproc/content-sigproc.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/sigproc/processors.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/sigproc/units.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/tutorials/signalprocessing.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/index.rst +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/__init__.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/activation.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/adaptive_lattice_notch.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/affinetransform.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/aggregate.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/bandpower.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/base.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/butterworthfilter.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/cheby.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/combfilter.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/decimate.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/denormalize.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/detrend.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/diff.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/downsample.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/ewma.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/ewmfilter.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/extract_axis.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/fbcca.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/filter.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/filterbank.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/filterbankdesign.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/firfilter.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/gaussiansmoothing.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/kaiser.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/__init__.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/abs.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/clip.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/difference.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/invert.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/log.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/scale.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/messages.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/quantize.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/resample.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/sampler.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/scaler.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/signalinjector.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/slicer.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/spectral.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/spectrogram.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/spectrum.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/synth.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/transpose.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/__init__.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/asio.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/axisarray_buffer.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/buffer.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/message.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/profile.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/sparse.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/typeresolution.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/wavelets.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/window.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/__init__.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/conftest.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/helpers/__init__.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/helpers/util.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/bytewax/test_spectrum_bytewax.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/bytewax/test_window_bytewax.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_butterworth_system.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_decimate_system.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_downsample_system.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_filter_system.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_sampler_system.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_scaler_system.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_spectrum_system.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_synth_system.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_window_system.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/resources/xform.csv +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/test_profile.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/buffer/test_axisarray_buffer.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/buffer/test_buffer.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/buffer/test_buffer_overflow.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_activation.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_adaptive_lattice_notch.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_affine_transform.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_aggregate.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_bandpower.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_base.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_butter.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_combfilter.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_denormalize.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_diff.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_downsample.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_ewma.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_extract_axis.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_fbcca.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_filter.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_filterbank.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_filterbankdesign.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_firfilter.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_gaussian_smoothing_filter.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_kaiser.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_math.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_quantize.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_resample.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_sampler.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_scaler.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_slicer.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_spectrogram.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_spectrum.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_synth.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_transpose.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_util.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_wavelets.py +0 -0
- {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_window.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ezmsg-sigproc
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
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>
|
|
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
6
|
License-Expression: MIT
|
|
7
7
|
License-File: LICENSE.txt
|
|
8
8
|
Requires-Python: >=3.10.15
|
|
@@ -5,6 +5,7 @@ authors = [
|
|
|
5
5
|
{ name = "Griffin Milsap", email = "griffin.milsap@gmail.com" },
|
|
6
6
|
{ name = "Preston Peranich", email = "pperanich@gmail.com" },
|
|
7
7
|
{ name = "Chadwick Boulay", email = "chadwick.boulay@gmail.com" },
|
|
8
|
+
{ name = "Kyle McGraw", email = "kmcgraw@blackrockneuro.com" },
|
|
8
9
|
]
|
|
9
10
|
license = "MIT"
|
|
10
11
|
readme = "README.md"
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '2.
|
|
32
|
-
__version_tuple__ = version_tuple = (2,
|
|
31
|
+
__version__ = version = '2.5.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 5, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
import ezmsg.core as ez
|
|
5
|
+
import numpy as np
|
|
6
|
+
import scipy.signal
|
|
7
|
+
from ezmsg.sigproc.base import SettingsType
|
|
8
|
+
from ezmsg.sigproc.butterworthfilter import ButterworthFilterSettings, butter_design_fun
|
|
9
|
+
from ezmsg.sigproc.filter import (
|
|
10
|
+
BACoeffs,
|
|
11
|
+
BaseFilterByDesignTransformerUnit,
|
|
12
|
+
FilterByDesignTransformer,
|
|
13
|
+
SOSCoeffs,
|
|
14
|
+
)
|
|
15
|
+
from ezmsg.util.messages.axisarray import AxisArray
|
|
16
|
+
from ezmsg.util.messages.util import replace
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ButterworthZeroPhaseSettings(ButterworthFilterSettings):
|
|
20
|
+
"""Settings for :obj:`ButterworthZeroPhase`."""
|
|
21
|
+
|
|
22
|
+
# axis, coef_type, order, cuton, cutoff, wn_hz are inherited from ButterworthFilterSettings
|
|
23
|
+
padtype: str | None = None
|
|
24
|
+
"""
|
|
25
|
+
Padding type to use in `scipy.signal.filtfilt`.
|
|
26
|
+
Must be one of {'odd', 'even', 'constant', None}.
|
|
27
|
+
Default is None for no padding.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
padlen: int | None = 0
|
|
31
|
+
"""
|
|
32
|
+
Length of the padding to use in `scipy.signal.filtfilt`.
|
|
33
|
+
If None, SciPy's default padding is used.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ButterworthZeroPhaseTransformer(
|
|
38
|
+
FilterByDesignTransformer[ButterworthZeroPhaseSettings, BACoeffs | SOSCoeffs]
|
|
39
|
+
):
|
|
40
|
+
"""Zero-phase (filtfilt) Butterworth using your design function."""
|
|
41
|
+
|
|
42
|
+
def get_design_function(
|
|
43
|
+
self,
|
|
44
|
+
) -> typing.Callable[[float], BACoeffs | SOSCoeffs | None]:
|
|
45
|
+
return functools.partial(
|
|
46
|
+
butter_design_fun,
|
|
47
|
+
order=self.settings.order,
|
|
48
|
+
cuton=self.settings.cuton,
|
|
49
|
+
cutoff=self.settings.cutoff,
|
|
50
|
+
coef_type=self.settings.coef_type,
|
|
51
|
+
wn_hz=self.settings.wn_hz,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def update_settings(
|
|
55
|
+
self, new_settings: typing.Optional[SettingsType] = None, **kwargs
|
|
56
|
+
) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Update settings and mark that filter coefficients need to be recalculated.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
new_settings: Complete new settings object to replace current settings
|
|
62
|
+
**kwargs: Individual settings to update
|
|
63
|
+
"""
|
|
64
|
+
# Update settings
|
|
65
|
+
if new_settings is not None:
|
|
66
|
+
self.settings = new_settings
|
|
67
|
+
else:
|
|
68
|
+
self.settings = replace(self.settings, **kwargs)
|
|
69
|
+
|
|
70
|
+
# Set flag to trigger recalculation on next message
|
|
71
|
+
self._coefs_cache = None
|
|
72
|
+
self._fs_cache = None
|
|
73
|
+
self.state.needs_redesign = True
|
|
74
|
+
|
|
75
|
+
def _reset_state(self, message: AxisArray) -> None:
|
|
76
|
+
self._coefs_cache = None
|
|
77
|
+
self._fs_cache = None
|
|
78
|
+
self.state.needs_redesign = True
|
|
79
|
+
|
|
80
|
+
def _process(self, message: AxisArray) -> AxisArray:
|
|
81
|
+
axis = message.dims[0] if self.settings.axis is None else self.settings.axis
|
|
82
|
+
ax_idx = message.get_axis_idx(axis)
|
|
83
|
+
fs = 1 / message.axes[axis].gain
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
self._coefs_cache is None
|
|
87
|
+
or self.state.needs_redesign
|
|
88
|
+
or (self._fs_cache is None or not np.isclose(self._fs_cache, fs))
|
|
89
|
+
):
|
|
90
|
+
self._coefs_cache = self.get_design_function()(fs)
|
|
91
|
+
self._fs_cache = fs
|
|
92
|
+
self.state.needs_redesign = False
|
|
93
|
+
|
|
94
|
+
if (
|
|
95
|
+
self._coefs_cache is None
|
|
96
|
+
or self.settings.order <= 0
|
|
97
|
+
or message.data.size <= 0
|
|
98
|
+
):
|
|
99
|
+
return message
|
|
100
|
+
|
|
101
|
+
x = message.data
|
|
102
|
+
if self.settings.coef_type == "sos":
|
|
103
|
+
y = scipy.signal.sosfiltfilt(
|
|
104
|
+
self._coefs_cache,
|
|
105
|
+
x,
|
|
106
|
+
axis=ax_idx,
|
|
107
|
+
padtype=self.settings.padtype,
|
|
108
|
+
padlen=self.settings.padlen,
|
|
109
|
+
)
|
|
110
|
+
elif self.settings.coef_type == "ba":
|
|
111
|
+
b, a = self._coefs_cache
|
|
112
|
+
y = scipy.signal.filtfilt(
|
|
113
|
+
b,
|
|
114
|
+
a,
|
|
115
|
+
x,
|
|
116
|
+
axis=ax_idx,
|
|
117
|
+
padtype=self.settings.padtype,
|
|
118
|
+
padlen=self.settings.padlen,
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
ez.logger.error("coef_type must be 'sos' or 'ba'.")
|
|
122
|
+
raise ValueError("coef_type must be 'sos' or 'ba'.")
|
|
123
|
+
|
|
124
|
+
return replace(message, data=y)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ButterworthZeroPhase(
|
|
128
|
+
BaseFilterByDesignTransformerUnit[
|
|
129
|
+
ButterworthZeroPhaseSettings, ButterworthZeroPhaseTransformer
|
|
130
|
+
]
|
|
131
|
+
):
|
|
132
|
+
SETTINGS = ButterworthZeroPhaseSettings
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
import ezmsg.core as ez
|
|
5
|
+
import numpy as np
|
|
6
|
+
import scipy.signal as sps
|
|
7
|
+
from ezmsg.sigproc.base import BaseStatefulTransformer, processor_state
|
|
8
|
+
from ezmsg.sigproc.filter import (
|
|
9
|
+
BACoeffs,
|
|
10
|
+
BaseFilterByDesignTransformerUnit,
|
|
11
|
+
BaseTransformerUnit,
|
|
12
|
+
FilterBaseSettings,
|
|
13
|
+
FilterByDesignTransformer,
|
|
14
|
+
)
|
|
15
|
+
from ezmsg.util.messages.axisarray import AxisArray
|
|
16
|
+
from ezmsg.util.messages.util import replace
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FIRHilbertFilterSettings(FilterBaseSettings):
|
|
20
|
+
"""Settings for :obj:`FIRHilbertFilter`."""
|
|
21
|
+
|
|
22
|
+
# axis inherited from FilterBaseSettings
|
|
23
|
+
|
|
24
|
+
coef_type: str = "ba"
|
|
25
|
+
"""
|
|
26
|
+
Coefficient type. Must be 'ba' for FIR.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
order: int = 170
|
|
30
|
+
"""
|
|
31
|
+
Filter order (taps = order + 1).
|
|
32
|
+
Hilbert (type-III) filters require even order (odd taps).
|
|
33
|
+
If odd order (even taps), order will be incremented by 1.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
f_lo: float = 1.0
|
|
37
|
+
"""
|
|
38
|
+
Lower corner of Hilbert “pass” band (Hz).
|
|
39
|
+
Transition starts at f_lo.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
f_hi: float | None = None
|
|
43
|
+
"""
|
|
44
|
+
Upper corner of Hilbert “pass” band (Hz).
|
|
45
|
+
Transition starts at f_hi.
|
|
46
|
+
If None, highpass from f_lo to Nyquist.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
trans_lo: float = 1.0
|
|
50
|
+
"""
|
|
51
|
+
Transition width (Hz) below f_lo.
|
|
52
|
+
Decrease to sharpen transition.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
trans_hi: float = 1.0
|
|
56
|
+
"""
|
|
57
|
+
Transition width (Hz) at high end.
|
|
58
|
+
Decrease to sharpen transition.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
weight_pass: float = 1.0
|
|
62
|
+
"""
|
|
63
|
+
Weight for Hilbert pass region.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
weight_stop_lo: float = 1.0
|
|
67
|
+
"""
|
|
68
|
+
Weight for low stop band.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
weight_stop_hi: float = 1.0
|
|
72
|
+
"""
|
|
73
|
+
Weight for high stop band.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
norm_band: tuple[float, float] | None = None
|
|
77
|
+
"""
|
|
78
|
+
Optional normalization band (f_lo, f_hi) in Hz for gain normalization.
|
|
79
|
+
If None, no normalization is applied.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
norm_freq: float | None = None
|
|
83
|
+
"""
|
|
84
|
+
Optional normalization frequency in Hz for gain normalization.
|
|
85
|
+
If None, no normalization is applied.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def fir_hilbert_design_fun(
|
|
90
|
+
fs: float,
|
|
91
|
+
order: int = 170,
|
|
92
|
+
f_lo: float = 1.0,
|
|
93
|
+
f_hi: float | None = None,
|
|
94
|
+
trans_lo: float = 1.0,
|
|
95
|
+
trans_hi: float = 1.0,
|
|
96
|
+
weight_pass: float = 1.0,
|
|
97
|
+
weight_stop_lo: float = 1.0,
|
|
98
|
+
weight_stop_hi: float = 1.0,
|
|
99
|
+
norm_band: tuple[float, float] | None = None,
|
|
100
|
+
norm_freq: float | None = None,
|
|
101
|
+
) -> BACoeffs | None:
|
|
102
|
+
"""
|
|
103
|
+
Hilbert FIR filter design using the Remez exchange algorithm.
|
|
104
|
+
Design an `order`th-order FIR Hilbert filter and return the filter coefficients.
|
|
105
|
+
See :obj:`FIRHilbertFilterSettings` for argument description.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
The filter coefficients as a tuple of (b, a).
|
|
109
|
+
"""
|
|
110
|
+
if order <= 0:
|
|
111
|
+
return None
|
|
112
|
+
if order % 2 == 1:
|
|
113
|
+
order += 1
|
|
114
|
+
nyq = fs / 2.0
|
|
115
|
+
taps = order + 1
|
|
116
|
+
f1 = max(f_lo, 0.0) + trans_lo
|
|
117
|
+
f2 = (nyq - trans_hi) if (f_hi is None) else min(f_hi, nyq - trans_hi)
|
|
118
|
+
if not (0.0 < f1 < f2 < nyq):
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"Hilbert passband collapsed or invalid: "
|
|
121
|
+
f"f_lo={f_lo}, f_hi={f_hi}, trans_lo={trans_lo}, trans_hi={trans_hi}, fs={fs}"
|
|
122
|
+
)
|
|
123
|
+
# Bands: [0, f1-trans_lo] stop ; [f1, f2] pass (Hilbert) ; [f2+trans_hi, nyq] stop
|
|
124
|
+
bands = [0.0, max(f1 - trans_lo, 0.0), f1, f2, min(f2 + trans_hi, nyq), nyq]
|
|
125
|
+
desired = [0.0, 1.0, 0.0]
|
|
126
|
+
weight = [max(weight_stop_lo, 0.0), max(weight_pass, 0.0), max(weight_stop_hi, 0.0)]
|
|
127
|
+
for i in range(1, len(bands) - 1):
|
|
128
|
+
if bands[i] <= bands[i - 1]:
|
|
129
|
+
bands[i] = np.nextafter(bands[i - 1], np.inf)
|
|
130
|
+
if bands[-2] >= nyq:
|
|
131
|
+
ez.logger.warning(
|
|
132
|
+
"Hilbert upper stopband collapsed; using 2-band (stop/pass) design."
|
|
133
|
+
)
|
|
134
|
+
bands = bands[:-3] + [nyq]
|
|
135
|
+
desired = desired[:-1]
|
|
136
|
+
weight = weight[:-1]
|
|
137
|
+
b = sps.remez(taps, bands, desired, weight=weight, type="hilbert", fs=fs)
|
|
138
|
+
a = np.array([1.0])
|
|
139
|
+
g = None
|
|
140
|
+
if norm_freq is not None:
|
|
141
|
+
if norm_freq < f1 or norm_freq > f2:
|
|
142
|
+
ez.logger.warning(
|
|
143
|
+
"Invalid normalization frequency specifications. Skipping normalization."
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
f0 = float(norm_freq)
|
|
147
|
+
w = 2.0 * np.pi * (np.asarray([f0], dtype=np.float64) / fs)
|
|
148
|
+
_, H = sps.freqz(b, a, worN=w)
|
|
149
|
+
g = float(np.abs(H[0]))
|
|
150
|
+
elif norm_band is not None:
|
|
151
|
+
lo, hi = norm_band
|
|
152
|
+
if lo < f1 or hi > f2:
|
|
153
|
+
lo = max(lo, f1)
|
|
154
|
+
hi = min(hi, f2)
|
|
155
|
+
ez.logger.warning(
|
|
156
|
+
"Normalization band outside passband. Clipping to passband for normalization."
|
|
157
|
+
)
|
|
158
|
+
if lo >= hi:
|
|
159
|
+
ez.logger.warning(
|
|
160
|
+
"Invalid normalization band specifications. Skipping normalization."
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
freqs = np.linspace(lo, hi, 2048, dtype=np.float64)
|
|
164
|
+
w = 2.0 * np.pi * (np.asarray(freqs, dtype=np.float64) / fs)
|
|
165
|
+
_, H = sps.freqz(b, a, worN=w)
|
|
166
|
+
g = float(np.median(np.abs(H)))
|
|
167
|
+
if g is not None and g > 0:
|
|
168
|
+
b = b / g
|
|
169
|
+
return (b, a)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class FIRHilbertFilterTransformer(
|
|
173
|
+
FilterByDesignTransformer[FIRHilbertFilterSettings, BACoeffs]
|
|
174
|
+
):
|
|
175
|
+
def get_design_function(self) -> typing.Callable[[float], BACoeffs | None]:
|
|
176
|
+
if self.settings.coef_type != "ba":
|
|
177
|
+
ez.logger.error("FIRHilbert only supports coef_type='ba'.")
|
|
178
|
+
raise ValueError("FIRHilbert only supports coef_type='ba'.")
|
|
179
|
+
|
|
180
|
+
return functools.partial(
|
|
181
|
+
fir_hilbert_design_fun,
|
|
182
|
+
order=self.settings.order,
|
|
183
|
+
f_lo=self.settings.f_lo,
|
|
184
|
+
f_hi=self.settings.f_hi,
|
|
185
|
+
trans_lo=self.settings.trans_lo,
|
|
186
|
+
trans_hi=self.settings.trans_hi,
|
|
187
|
+
weight_pass=self.settings.weight_pass,
|
|
188
|
+
weight_stop_lo=self.settings.weight_stop_lo,
|
|
189
|
+
weight_stop_hi=self.settings.weight_stop_hi,
|
|
190
|
+
norm_band=self.settings.norm_band,
|
|
191
|
+
norm_freq=self.settings.norm_freq,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def get_taps(self) -> int | None:
|
|
195
|
+
if self._state.filter is None:
|
|
196
|
+
return None
|
|
197
|
+
b, _ = self._state.filter.settings.coefs
|
|
198
|
+
return b.size if b is not None else None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class FIRHilbertFilterUnit(
|
|
202
|
+
BaseFilterByDesignTransformerUnit[
|
|
203
|
+
FIRHilbertFilterSettings, FIRHilbertFilterTransformer
|
|
204
|
+
]
|
|
205
|
+
):
|
|
206
|
+
SETTINGS = FIRHilbertFilterSettings
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@processor_state
|
|
210
|
+
class FIRHilbertEnvelopeState:
|
|
211
|
+
filter: FIRHilbertFilterTransformer | None = None
|
|
212
|
+
delay_buf: np.ndarray | None = None
|
|
213
|
+
dly: int | None = None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class FIRHilbertEnvelopeTransformer(
|
|
217
|
+
BaseStatefulTransformer[
|
|
218
|
+
FIRHilbertFilterSettings, AxisArray, AxisArray, FIRHilbertEnvelopeState
|
|
219
|
+
]
|
|
220
|
+
):
|
|
221
|
+
"""
|
|
222
|
+
Processor for computing the envelope of a signal using the Hilbert transform.
|
|
223
|
+
|
|
224
|
+
This processor applies a Hilbert FIR filter to the input signal to obtain the analytic signal, from which the
|
|
225
|
+
envelope is computed.
|
|
226
|
+
|
|
227
|
+
The processor expects and outputs `AxisArray` messages with a `"time"` (time) axis.
|
|
228
|
+
|
|
229
|
+
Settings:
|
|
230
|
+
---------
|
|
231
|
+
order : int
|
|
232
|
+
Filter order (taps = order + 1).
|
|
233
|
+
Hilbert (type-III) filters require even order (odd taps).
|
|
234
|
+
If odd order (even taps), order will be incremented by 1.
|
|
235
|
+
f_lo : float
|
|
236
|
+
Lower corner of Hilbert “pass” band (Hz).
|
|
237
|
+
Transition starts at f_lo.
|
|
238
|
+
f_hi : float, optional
|
|
239
|
+
Upper corner of Hilbert “pass” band (Hz).
|
|
240
|
+
Transition starts at f_hi.
|
|
241
|
+
If None, highpass from f_lo to Nyquist.
|
|
242
|
+
trans_lo : float
|
|
243
|
+
Transition width (Hz) below f_lo.
|
|
244
|
+
Decrease to sharpen transition.
|
|
245
|
+
trans_hi : float
|
|
246
|
+
Transition width (Hz) above f_hi.
|
|
247
|
+
Decrease to sharpen transition.
|
|
248
|
+
weight_pass : float
|
|
249
|
+
Weight for Hilbert pass region.
|
|
250
|
+
weight_stop_lo : float
|
|
251
|
+
Weight for low stop band.
|
|
252
|
+
weight_stop_hi : float
|
|
253
|
+
Weight for high stop band.
|
|
254
|
+
norm_band : tuple(float, float), optional
|
|
255
|
+
Optional normalization band (f_lo, f_hi) in Hz for gain normalization.
|
|
256
|
+
If None, no normalization is applied.
|
|
257
|
+
norm_freq : float, optional
|
|
258
|
+
Optional normalization frequency in Hz for gain normalization.
|
|
259
|
+
If None, no normalization is applied.
|
|
260
|
+
|
|
261
|
+
Example:
|
|
262
|
+
-----------------------------
|
|
263
|
+
```python
|
|
264
|
+
processor = FIRHilbertEnvelopeTransformer(
|
|
265
|
+
settings=FIRHilbertFilterSettings(
|
|
266
|
+
order=170,
|
|
267
|
+
f_lo=1.0,
|
|
268
|
+
f_hi=50.0,
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
def _hash_message(self, message: AxisArray) -> int:
|
|
276
|
+
axis = self.settings.axis or message.dims[0]
|
|
277
|
+
gain = getattr(self._state.filter, "gain", 0.0)
|
|
278
|
+
axis_idx = message.get_axis_idx(axis)
|
|
279
|
+
samp_shape = message.data.shape[:axis_idx] + message.data.shape[axis_idx + 1 :]
|
|
280
|
+
return hash((message.key, samp_shape, gain))
|
|
281
|
+
|
|
282
|
+
def _reset_state(self, message: AxisArray) -> None:
|
|
283
|
+
self._state.filter = FIRHilbertFilterTransformer(settings=self.settings)
|
|
284
|
+
self._state.delay_buf = None
|
|
285
|
+
self._state.dly = None
|
|
286
|
+
|
|
287
|
+
def _process(self, message: AxisArray) -> AxisArray:
|
|
288
|
+
y_imag_msg = self._state.filter(message)
|
|
289
|
+
y_imag = y_imag_msg.data
|
|
290
|
+
|
|
291
|
+
axis_name = self.settings.axis or message.dims[0]
|
|
292
|
+
axis_idx = message.get_axis_idx(axis_name)
|
|
293
|
+
if self._state.dly is None:
|
|
294
|
+
taps = self._state.filter.get_taps()
|
|
295
|
+
self._state.dly = (taps - 1) // 2
|
|
296
|
+
|
|
297
|
+
x = message.data
|
|
298
|
+
|
|
299
|
+
move_axis = False
|
|
300
|
+
if axis_idx != x.ndim - 1:
|
|
301
|
+
x = np.moveaxis(x, axis_idx, -1)
|
|
302
|
+
y_imag = np.moveaxis(y_imag, axis_idx, -1)
|
|
303
|
+
move_axis = True
|
|
304
|
+
|
|
305
|
+
if self._state.delay_buf is None:
|
|
306
|
+
lead_shape = x.shape[:-1]
|
|
307
|
+
self._state.delay_buf = np.zeros(
|
|
308
|
+
lead_shape + (self._state.dly,), dtype=x.dtype
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
x_cat = np.concatenate([self._state.delay_buf, x], axis=-1)
|
|
312
|
+
x_delayed_full = x_cat[..., : -self._state.dly]
|
|
313
|
+
y_real = x_delayed_full[..., -x.shape[-1] :]
|
|
314
|
+
|
|
315
|
+
self._state.delay_buf = x_cat[..., -self._state.dly :].copy()
|
|
316
|
+
|
|
317
|
+
analytic = y_real.astype(np.complex64) + 1j * y_imag.astype(np.complex64)
|
|
318
|
+
out = np.abs(analytic)
|
|
319
|
+
|
|
320
|
+
if move_axis:
|
|
321
|
+
out = np.moveaxis(out, -1, axis_idx)
|
|
322
|
+
|
|
323
|
+
return replace(message, data=out, axes=message.axes)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class FIRHilbertEnvelopeUnit(
|
|
327
|
+
BaseTransformerUnit[
|
|
328
|
+
FIRHilbertFilterSettings,
|
|
329
|
+
AxisArray,
|
|
330
|
+
AxisArray,
|
|
331
|
+
FIRHilbertEnvelopeTransformer,
|
|
332
|
+
]
|
|
333
|
+
):
|
|
334
|
+
"""
|
|
335
|
+
Unit wrapper for the `FIRHilbertEnvelopeTransformer`.
|
|
336
|
+
|
|
337
|
+
This unit provides a plug-and-play interface for calculating the envelope using the FIR Hilbert transform on a
|
|
338
|
+
signal in an ezmsg graph-based system. It takes in `AxisArray` inputs and outputs processed data in the same format.
|
|
339
|
+
|
|
340
|
+
Example:
|
|
341
|
+
--------
|
|
342
|
+
```python
|
|
343
|
+
unit = FIRHilbertEnvelopeUnit(
|
|
344
|
+
settings=FIRHilbertFilterSettings(
|
|
345
|
+
order=170,
|
|
346
|
+
f_lo=1.0,
|
|
347
|
+
f_hi=50.0,
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
```
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
SETTINGS = FIRHilbertFilterSettings
|