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.
Files changed (153) hide show
  1. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/PKG-INFO +2 -2
  2. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/pyproject.toml +1 -0
  3. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/__version__.py +2 -2
  4. ezmsg_sigproc-2.5.0/src/ezmsg/sigproc/butterworthzerophase.py +132 -0
  5. ezmsg_sigproc-2.5.0/src/ezmsg/sigproc/fir_hilbert.py +353 -0
  6. ezmsg_sigproc-2.5.0/src/ezmsg/sigproc/fir_pmc.py +214 -0
  7. ezmsg_sigproc-2.5.0/src/ezmsg/sigproc/rollingscaler.py +257 -0
  8. ezmsg_sigproc-2.5.0/tests/integration/ezmsg/test_butterworthzerophase_system.py +51 -0
  9. ezmsg_sigproc-2.5.0/tests/integration/ezmsg/test_fir_hilbert_system.py +48 -0
  10. ezmsg_sigproc-2.5.0/tests/integration/ezmsg/test_fir_pmc_system.py +51 -0
  11. ezmsg_sigproc-2.5.0/tests/integration/ezmsg/test_rollingscaler_system.py +48 -0
  12. ezmsg_sigproc-2.5.0/tests/unit/test_butterworthzerophase.py +178 -0
  13. ezmsg_sigproc-2.5.0/tests/unit/test_fir_hilbert.py +271 -0
  14. ezmsg_sigproc-2.5.0/tests/unit/test_fir_pmc.py +93 -0
  15. ezmsg_sigproc-2.5.0/tests/unit/test_rollingscaler.py +155 -0
  16. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/.github/workflows/docs.yml +0 -0
  17. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/.github/workflows/python-publish-ezmsg-sigproc.yml +0 -0
  18. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/.github/workflows/python-tests.yml +0 -0
  19. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/.gitignore +0 -0
  20. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/.pre-commit-config.yaml +0 -0
  21. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/LICENSE.txt +0 -0
  22. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/README.md +0 -0
  23. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/Makefile +0 -0
  24. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/make.bat +0 -0
  25. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/_templates/autosummary/module.rst +0 -0
  26. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/api/index.rst +0 -0
  27. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/conf.py +0 -0
  28. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/HybridBuffer.md +0 -0
  29. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/ProcessorsBase.md +0 -0
  30. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/explanations/sigproc.rst +0 -0
  31. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/adaptive.rst +0 -0
  32. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/checkpoint.rst +0 -0
  33. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/composite.rst +0 -0
  34. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/content-signalprocessing.rst +0 -0
  35. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/processor.rst +0 -0
  36. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/standalone.rst +0 -0
  37. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/stateful.rst +0 -0
  38. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/how-tos/signalprocessing/unit.rst +0 -0
  39. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/img/HybridBufferBasic.svg +0 -0
  40. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/img/HybridBufferOverflow.svg +0 -0
  41. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/sigproc/base.rst +0 -0
  42. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/sigproc/content-sigproc.rst +0 -0
  43. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/sigproc/processors.rst +0 -0
  44. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/sigproc/units.rst +0 -0
  45. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/guides/tutorials/signalprocessing.rst +0 -0
  46. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/docs/source/index.rst +0 -0
  47. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/__init__.py +0 -0
  48. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/activation.py +0 -0
  49. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/adaptive_lattice_notch.py +0 -0
  50. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/affinetransform.py +0 -0
  51. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/aggregate.py +0 -0
  52. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/bandpower.py +0 -0
  53. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/base.py +0 -0
  54. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/butterworthfilter.py +0 -0
  55. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/cheby.py +0 -0
  56. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/combfilter.py +0 -0
  57. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/decimate.py +0 -0
  58. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/denormalize.py +0 -0
  59. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/detrend.py +0 -0
  60. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/diff.py +0 -0
  61. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/downsample.py +0 -0
  62. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/ewma.py +0 -0
  63. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/ewmfilter.py +0 -0
  64. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/extract_axis.py +0 -0
  65. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/fbcca.py +0 -0
  66. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/filter.py +0 -0
  67. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/filterbank.py +0 -0
  68. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/filterbankdesign.py +0 -0
  69. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/firfilter.py +0 -0
  70. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/gaussiansmoothing.py +0 -0
  71. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/kaiser.py +0 -0
  72. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/__init__.py +0 -0
  73. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/abs.py +0 -0
  74. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/clip.py +0 -0
  75. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/difference.py +0 -0
  76. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/invert.py +0 -0
  77. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/log.py +0 -0
  78. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/math/scale.py +0 -0
  79. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/messages.py +0 -0
  80. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/quantize.py +0 -0
  81. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/resample.py +0 -0
  82. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/sampler.py +0 -0
  83. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/scaler.py +0 -0
  84. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/signalinjector.py +0 -0
  85. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/slicer.py +0 -0
  86. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/spectral.py +0 -0
  87. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/spectrogram.py +0 -0
  88. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/spectrum.py +0 -0
  89. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/synth.py +0 -0
  90. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/transpose.py +0 -0
  91. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/__init__.py +0 -0
  92. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/asio.py +0 -0
  93. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/axisarray_buffer.py +0 -0
  94. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/buffer.py +0 -0
  95. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/message.py +0 -0
  96. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/profile.py +0 -0
  97. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/sparse.py +0 -0
  98. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/util/typeresolution.py +0 -0
  99. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/wavelets.py +0 -0
  100. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/src/ezmsg/sigproc/window.py +0 -0
  101. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/__init__.py +0 -0
  102. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/conftest.py +0 -0
  103. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/helpers/__init__.py +0 -0
  104. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/helpers/util.py +0 -0
  105. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/bytewax/test_spectrum_bytewax.py +0 -0
  106. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/bytewax/test_window_bytewax.py +0 -0
  107. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_butterworth_system.py +0 -0
  108. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_decimate_system.py +0 -0
  109. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_downsample_system.py +0 -0
  110. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_filter_system.py +0 -0
  111. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_sampler_system.py +0 -0
  112. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_scaler_system.py +0 -0
  113. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_spectrum_system.py +0 -0
  114. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_synth_system.py +0 -0
  115. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/integration/ezmsg/test_window_system.py +0 -0
  116. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/resources/xform.csv +0 -0
  117. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/test_profile.py +0 -0
  118. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/buffer/test_axisarray_buffer.py +0 -0
  119. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/buffer/test_buffer.py +0 -0
  120. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/buffer/test_buffer_overflow.py +0 -0
  121. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_activation.py +0 -0
  122. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_adaptive_lattice_notch.py +0 -0
  123. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_affine_transform.py +0 -0
  124. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_aggregate.py +0 -0
  125. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_bandpower.py +0 -0
  126. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_base.py +0 -0
  127. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_butter.py +0 -0
  128. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_combfilter.py +0 -0
  129. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_denormalize.py +0 -0
  130. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_diff.py +0 -0
  131. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_downsample.py +0 -0
  132. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_ewma.py +0 -0
  133. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_extract_axis.py +0 -0
  134. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_fbcca.py +0 -0
  135. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_filter.py +0 -0
  136. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_filterbank.py +0 -0
  137. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_filterbankdesign.py +0 -0
  138. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_firfilter.py +0 -0
  139. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_gaussian_smoothing_filter.py +0 -0
  140. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_kaiser.py +0 -0
  141. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_math.py +0 -0
  142. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_quantize.py +0 -0
  143. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_resample.py +0 -0
  144. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_sampler.py +0 -0
  145. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_scaler.py +0 -0
  146. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_slicer.py +0 -0
  147. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_spectrogram.py +0 -0
  148. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_spectrum.py +0 -0
  149. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_synth.py +0 -0
  150. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_transpose.py +0 -0
  151. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_util.py +0 -0
  152. {ezmsg_sigproc-2.4.1 → ezmsg_sigproc-2.5.0}/tests/unit/test_wavelets.py +0 -0
  153. {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.4.1
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.4.1'
32
- __version_tuple__ = version_tuple = (2, 4, 1)
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