PySDKit 0.4.45__tar.gz → 0.4.46__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 (190) hide show
  1. {pysdkit-0.4.45 → pysdkit-0.4.46}/PKG-INFO +2 -1
  2. {pysdkit-0.4.45 → pysdkit-0.4.46}/PySDKit.egg-info/PKG-INFO +2 -1
  3. {pysdkit-0.4.45 → pysdkit-0.4.46}/PySDKit.egg-info/SOURCES.txt +7 -0
  4. {pysdkit-0.4.45 → pysdkit-0.4.46}/README.md +1 -0
  5. {pysdkit-0.4.45 → pysdkit-0.4.46}/pyproject.toml +2 -0
  6. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/__init__.py +8 -2
  7. pysdkit-0.4.46/pysdkit/_tfa/__init__.py +41 -0
  8. pysdkit-0.4.46/pysdkit/_tfa/data/__init__.py +2 -0
  9. pysdkit-0.4.46/pysdkit/_tfa/data/dual_signal_noise.npy +0 -0
  10. pysdkit-0.4.46/pysdkit/_tfa/data/map2.npy +0 -0
  11. pysdkit-0.4.46/pysdkit/_tfa/data/single_nsignal.npy +0 -0
  12. pysdkit-0.4.46/pysdkit/_tfa/vtfmtd.py +439 -0
  13. pysdkit-0.4.46/pysdkit/tests/test_vtfmtd.py +273 -0
  14. {pysdkit-0.4.45 → pysdkit-0.4.46}/LICENSE +0 -0
  15. {pysdkit-0.4.45 → pysdkit-0.4.46}/PySDKit.egg-info/dependency_links.txt +0 -0
  16. {pysdkit-0.4.45 → pysdkit-0.4.46}/PySDKit.egg-info/requires.txt +0 -0
  17. {pysdkit-0.4.45 → pysdkit-0.4.46}/PySDKit.egg-info/top_level.txt +0 -0
  18. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/__init__.py +0 -0
  19. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/_find_extrema.py +0 -0
  20. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/_prepare_points.py +0 -0
  21. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/_splines.py +0 -0
  22. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/ceemdan.py +0 -0
  23. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/eemd.py +0 -0
  24. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/efd.py +0 -0
  25. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/emd.py +0 -0
  26. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/esmd.py +0 -0
  27. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/hht/__init__.py +0 -0
  28. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/hht/frequency.py +0 -0
  29. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/hht/hht.py +0 -0
  30. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/memd.py +0 -0
  31. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/remd.py +0 -0
  32. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/semd.py +0 -0
  33. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/tvf_emd.py +0 -0
  34. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd2d/__init__.py +0 -0
  35. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd2d/bemd.py +0 -0
  36. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd2d/bmemd.py +0 -0
  37. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd2d/emd2d.py +0 -0
  38. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_ewt/__init__.py +0 -0
  39. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_ewt/ewt.py +0 -0
  40. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_ewt/ewt2d.py +0 -0
  41. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/__init__.py +0 -0
  42. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/extrema.py +0 -0
  43. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/faemd.py +0 -0
  44. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/faemd2d.py +0 -0
  45. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/faemd3d.py +0 -0
  46. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/filter.py +0 -0
  47. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_fmd/__init__.py +0 -0
  48. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_fmd/fmd.py +0 -0
  49. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_fmd/nfmd.py +0 -0
  50. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_gdmd/__init__.py +0 -0
  51. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_gdmd/agncmd.py +0 -0
  52. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_gdmd/gdmd.py +0 -0
  53. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_gdmd/ivgnmd.py +0 -0
  54. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_gdmd/vgnmd.py +0 -0
  55. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_imd/__init__.py +0 -0
  56. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_imd/apmd.py +0 -0
  57. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_imd/data/gearbox_fault_snippet.npy +0 -0
  58. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_imd/data/input_sig.npy +0 -0
  59. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_imd/imd.py +0 -0
  60. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_jmd/__init__.py +0 -0
  61. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_jmd/jmd.py +0 -0
  62. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_jmd/mjmd.py +0 -0
  63. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_jmd/sjmd.py +0 -0
  64. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_lmd/__init__.py +0 -0
  65. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_lmd/lmd.py +0 -0
  66. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_lmd/rlmd.py +0 -0
  67. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_osd/__init__.py +0 -0
  68. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_osd/components.py +0 -0
  69. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_osd/osd.py +0 -0
  70. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_osd/swd.py +0 -0
  71. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/__init__.py +0 -0
  72. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/alif.py +0 -0
  73. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/data/__init__.py +0 -0
  74. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/data/prefixed_double_filter.npy +0 -0
  75. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/hvd.py +0 -0
  76. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/itd.py +0 -0
  77. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/iterative_filtering.py +0 -0
  78. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/ssa.py +0 -0
  79. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/__init__.py +0 -0
  80. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/acmd.py +0 -0
  81. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/avmd.py +0 -0
  82. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/ba_acmd.py +0 -0
  83. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/base.py +0 -0
  84. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/mvmd.py +0 -0
  85. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/ovmd.py +0 -0
  86. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/stvmd.py +0 -0
  87. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/svmd.py +0 -0
  88. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/vmd_c.py +0 -0
  89. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/vmd_f.py +0 -0
  90. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/vme.py +0 -0
  91. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd2d/__init__.py +0 -0
  92. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd2d/cvmd2d.py +0 -0
  93. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd2d/vmd2d.py +0 -0
  94. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vncmd/__init__.py +0 -0
  95. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vncmd/avncmd.py +0 -0
  96. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vncmd/incmd.py +0 -0
  97. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vncmd/vncmd.py +0 -0
  98. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/__init__.py +0 -0
  99. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_add_noise.py +0 -0
  100. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_cube.py +0 -0
  101. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_generator.py +0 -0
  102. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_image.py +0 -0
  103. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_models.py +0 -0
  104. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_test_univariate.py +0 -0
  105. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_time_series.py +0 -0
  106. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/fmd_demo.npy +0 -0
  107. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/texture.txt +0 -0
  108. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/vmd_example.npy +0 -0
  109. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/entropy/__init__.py +0 -0
  110. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
  111. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/entropy/_permutation_entropy.py +0 -0
  112. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/entropy/_sample_entropy.py +0 -0
  113. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/models/__init__.py +0 -0
  114. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/models/_base.py +0 -0
  115. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/models/_kmeans.py +0 -0
  116. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/models/_knn.py +0 -0
  117. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/models/_pca.py +0 -0
  118. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/__init__.py +0 -0
  119. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/_fourier_spectra.py +0 -0
  120. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/_functions.py +0 -0
  121. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/_plot_images.py +0 -0
  122. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/_plot_imfs.py +0 -0
  123. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/_plot_signal.py +0 -0
  124. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/__init__.py +0 -0
  125. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/data/__init__.py +0 -0
  126. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/data/test_generator_cube.py +0 -0
  127. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/data/test_generator_image.py +0 -0
  128. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/data/test_generator_signal.py +0 -0
  129. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
  130. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/models/__init__.py +0 -0
  131. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/models/test_knn.py +0 -0
  132. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/models/test_pca.py +0 -0
  133. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/run_all.py +0 -0
  134. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_agncmd.py +0 -0
  135. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_alif.py +0 -0
  136. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_apmd.py +0 -0
  137. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_avncmd.py +0 -0
  138. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_bmemd.py +0 -0
  139. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ceemdan.py +0 -0
  140. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_cvmd2d.py +0 -0
  141. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_eemd.py +0 -0
  142. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_emd.py +0 -0
  143. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_esmd.py +0 -0
  144. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ewt.py +0 -0
  145. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ewt2d.py +0 -0
  146. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_faemd.py +0 -0
  147. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_faemd2d.py +0 -0
  148. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_faemd3d.py +0 -0
  149. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_fmd.py +0 -0
  150. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_gdmd.py +0 -0
  151. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_hvd.py +0 -0
  152. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_imd.py +0 -0
  153. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_itd.py +0 -0
  154. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ivgnmd.py +0 -0
  155. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_jmd.py +0 -0
  156. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_lmd.py +0 -0
  157. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_moving_decomp.py +0 -0
  158. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_mstl.py +0 -0
  159. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_osd.py +0 -0
  160. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ovmd.py +0 -0
  161. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_rlmd.py +0 -0
  162. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_semd.py +0 -0
  163. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_sjmd.py +0 -0
  164. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ssa.py +0 -0
  165. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_stl.py +0 -0
  166. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_stvmd.py +0 -0
  167. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_svmd.py +0 -0
  168. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_swd.py +0 -0
  169. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_vgnmd.py +0 -0
  170. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_vmd.py +0 -0
  171. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_vmd2d.py +0 -0
  172. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_vncmd.py +0 -0
  173. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tsa/__init__.py +0 -0
  174. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tsa/_dtw.py +0 -0
  175. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tsa/_moving_decomp.py +0 -0
  176. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tsa/_mstl.py +0 -0
  177. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tsa/_stl.py +0 -0
  178. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/__init__.py +0 -0
  179. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_correlation.py +0 -0
  180. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_diagnalization.py +0 -0
  181. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_differ.py +0 -0
  182. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_fft.py +0 -0
  183. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_function.py +0 -0
  184. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_hilbert.py +0 -0
  185. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_instantaneous.py +0 -0
  186. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_kernel_matrix.py +0 -0
  187. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_mirror.py +0 -0
  188. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_process.py +0 -0
  189. {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_smooth1d.py +0 -0
  190. {pysdkit-0.4.45 → pysdkit-0.4.46}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySDKit
3
- Version: 0.4.45
3
+ Version: 0.4.46
4
4
  Summary: A Python library for signal decomposition algorithms with a unified interface.
5
5
  Author: Whenxuan Wang, RuiZhe Wang, Rongkun Zhu, Kai Wu, Lei Wang, josefinez, Deeksha Manjunath, Yuan Feng, WenTong Zhao, JacktheFowler
6
6
  Maintainer-email: Whenxuan Wang <wwhenxuan@gmail.com>, RuiZhe Wang <3133986068@qq.com>
@@ -200,6 +200,7 @@ plot_IMFs(signal, IMFs) # per-channel panels
200
200
  | [`SJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/sjmd.py) / `SMJMD` (Successive Jump and Mode Decomposition) | [[paper]](https://arxiv.org/abs/2504.08453) | [[code]]() | ✔️ |
201
201
  | [`ESMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/esmd.py) (Extreme-Point Symmetric Mode Decomposition) | [[paper]](https://arxiv.org/abs/1303.6540) | [[code]](https://github.com/WuShichao/esmd) | ✔️ |
202
202
  | [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
203
+ | [`VTFMTD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_tfa/vftmtd.py) (Variational TF Mode Tracking Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0165168426001179) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/183389-variational-time-frequency-mode-tracking-decomposition) | ✔️ |
203
204
  | [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
204
205
  | [`OSD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/osd.py) (Optimization-based Signal Decomposition) | [[paper]](https://web.stanford.edu/~boyd/papers/sig_decomp_mprox.html) | [[code]](https://github.com/cvxgrp/signal-decomposition) | ✔️ |
205
206
  | [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySDKit
3
- Version: 0.4.45
3
+ Version: 0.4.46
4
4
  Summary: A Python library for signal decomposition algorithms with a unified interface.
5
5
  Author: Whenxuan Wang, RuiZhe Wang, Rongkun Zhu, Kai Wu, Lei Wang, josefinez, Deeksha Manjunath, Yuan Feng, WenTong Zhao, JacktheFowler
6
6
  Maintainer-email: Whenxuan Wang <wwhenxuan@gmail.com>, RuiZhe Wang <3133986068@qq.com>
@@ -200,6 +200,7 @@ plot_IMFs(signal, IMFs) # per-channel panels
200
200
  | [`SJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/sjmd.py) / `SMJMD` (Successive Jump and Mode Decomposition) | [[paper]](https://arxiv.org/abs/2504.08453) | [[code]]() | ✔️ |
201
201
  | [`ESMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/esmd.py) (Extreme-Point Symmetric Mode Decomposition) | [[paper]](https://arxiv.org/abs/1303.6540) | [[code]](https://github.com/WuShichao/esmd) | ✔️ |
202
202
  | [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
203
+ | [`VTFMTD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_tfa/vftmtd.py) (Variational TF Mode Tracking Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0165168426001179) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/183389-variational-time-frequency-mode-tracking-decomposition) | ✔️ |
203
204
  | [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
204
205
  | [`OSD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/osd.py) (Optimization-based Signal Decomposition) | [[paper]](https://web.stanford.edu/~boyd/papers/sig_decomp_mprox.html) | [[code]](https://github.com/cvxgrp/signal-decomposition) | ✔️ |
205
206
  | [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
@@ -60,6 +60,12 @@ pysdkit/_osd/__init__.py
60
60
  pysdkit/_osd/components.py
61
61
  pysdkit/_osd/osd.py
62
62
  pysdkit/_osd/swd.py
63
+ pysdkit/_tfa/__init__.py
64
+ pysdkit/_tfa/vtfmtd.py
65
+ pysdkit/_tfa/data/__init__.py
66
+ pysdkit/_tfa/data/dual_signal_noise.npy
67
+ pysdkit/_tfa/data/map2.npy
68
+ pysdkit/_tfa/data/single_nsignal.npy
63
69
  pysdkit/_tid/__init__.py
64
70
  pysdkit/_tid/alif.py
65
71
  pysdkit/_tid/hvd.py
@@ -154,6 +160,7 @@ pysdkit/tests/test_vgnmd.py
154
160
  pysdkit/tests/test_vmd.py
155
161
  pysdkit/tests/test_vmd2d.py
156
162
  pysdkit/tests/test_vncmd.py
163
+ pysdkit/tests/test_vtfmtd.py
157
164
  pysdkit/tests/data/__init__.py
158
165
  pysdkit/tests/data/test_generator_cube.py
159
166
  pysdkit/tests/data/test_generator_image.py
@@ -147,6 +147,7 @@ plot_IMFs(signal, IMFs) # per-channel panels
147
147
  | [`SJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/sjmd.py) / `SMJMD` (Successive Jump and Mode Decomposition) | [[paper]](https://arxiv.org/abs/2504.08453) | [[code]]() | ✔️ |
148
148
  | [`ESMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/esmd.py) (Extreme-Point Symmetric Mode Decomposition) | [[paper]](https://arxiv.org/abs/1303.6540) | [[code]](https://github.com/WuShichao/esmd) | ✔️ |
149
149
  | [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
150
+ | [`VTFMTD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_tfa/vftmtd.py) (Variational TF Mode Tracking Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0165168426001179) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/183389-variational-time-frequency-mode-tracking-decomposition) | ✔️ |
150
151
  | [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
151
152
  | [`OSD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/osd.py) (Optimization-based Signal Decomposition) | [[paper]](https://web.stanford.edu/~boyd/papers/sig_decomp_mprox.html) | [[code]](https://github.com/cvxgrp/signal-decomposition) | ✔️ |
152
153
  | [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
@@ -73,3 +73,5 @@ include = ["pysdkit*"]
73
73
  "pysdkit._tid.data" = ["*.npy"]
74
74
  "pysdkit._imd" = ["data/*.npy"]
75
75
  "pysdkit._imd.data" = ["*.npy"]
76
+ "pysdkit._tfa" = ["data/*.npy"]
77
+ "pysdkit._tfa.data" = ["*.npy"]
@@ -2,7 +2,7 @@
2
2
  A Python library for signal decomposition algorithms.
3
3
  """
4
4
 
5
- __version__ = "0.4.45"
5
+ __version__ = "0.4.46"
6
6
 
7
7
  # Empirical Mode Decomposition
8
8
  from ._emd import EMD
@@ -112,6 +112,9 @@ from ._vncmd import AVNCMD
112
112
  # Adaptive Local Iterative Filtering (time iterative decomposition)
113
113
  from ._tid import ALIF
114
114
 
115
+ # Variational Time-Frequency Mode Tracking Decomposition (TFA)
116
+ from ._tfa import VTFMTD, vtfmtd
117
+
115
118
  # Adaptive Polymorphic Mode Decomposition / Impulsive Mode Decomposition
116
119
  from ._imd import APMD, IMD, imd
117
120
 
@@ -193,7 +196,7 @@ Optimization-based Signal Decomposition | OSD
193
196
  Generalized Dispersion Mode Decomposition | GDMD
194
197
  Variational Generalized Nonlinear Mode Dec. | VGNMD
195
198
  Improved VGNMD (crossed chirp / dispersive) | IVGNMD
196
- Adaptive Generalized Dispersive Mode Dec. | AGNCMD / AGDMD
199
+ Adaptive Generalized Dispersive Mode Dec. | AGNCMD / AGDMD
197
200
  Variational Mode Decomposition | VMD
198
201
  Multivariate Variational Mode Decomposition | MVMD
199
202
  Variational Mode Extraction | VME
@@ -206,6 +209,7 @@ Variational Nonlinear Chirp Mode Decomposition | VNCMD
206
209
  Iterative Nonlinear Chirp Mode Decomposition | INCMD
207
210
  Adaptive Variational Nonlinear Chirp Mode Dec. | AVNCMD
208
211
  Adaptive Local Iterative Filtering | ALIF
212
+ Variational TF Mode Tracking Decomposition | VTFMTD
209
213
  Adaptive Polymorphic Mode Decomposition | APMD
210
214
  Impulsive Mode Decomposition | IMD
211
215
  Empirical Wavelet Transform | EWT
@@ -273,6 +277,8 @@ __all__ = [
273
277
  "INCMD",
274
278
  "AVNCMD",
275
279
  "ALIF",
280
+ "VTFMTD",
281
+ "vtfmtd",
276
282
  "APMD",
277
283
  "IMD",
278
284
  "imd",
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Time-Frequency Analysis (TFA).
4
+
5
+ Methods that operate primarily on time-frequency representations
6
+ (e.g. STFT) for mode separation and instantaneous-frequency tracking.
7
+ """
8
+
9
+ from .vtfmtd import (
10
+ VTFMTD,
11
+ vtfmtd,
12
+ stft,
13
+ frequency_axis,
14
+ bin_index_grid,
15
+ expand_omega_init,
16
+ first_difference_gram,
17
+ estimate_if_centroid,
18
+ smooth_if,
19
+ moving_average_if,
20
+ omega_bins_to_hz,
21
+ load_dual_signal_noise,
22
+ load_single_nsignal,
23
+ load_map2,
24
+ )
25
+
26
+ __all__ = [
27
+ "VTFMTD",
28
+ "vtfmtd",
29
+ "stft",
30
+ "frequency_axis",
31
+ "bin_index_grid",
32
+ "expand_omega_init",
33
+ "first_difference_gram",
34
+ "estimate_if_centroid",
35
+ "smooth_if",
36
+ "moving_average_if",
37
+ "omega_bins_to_hz",
38
+ "load_dual_signal_noise",
39
+ "load_single_nsignal",
40
+ "load_map2",
41
+ ]
@@ -0,0 +1,2 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Packaged demo arrays for VTFMTD examples and tests."""
@@ -0,0 +1,439 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Variational Time-Frequency Mode Tracking Decomposition (VTFMTD).
4
+
5
+ Dong, H., Shan, T., Yu, G., Shi, Y., Chen, Y.
6
+ Variational time-frequency mode tracking for micro-Doppler signature extraction.
7
+ Signal Processing, 246:110603, 2026.
8
+ https://doi.org/10.1016/j.sigpro.2026.110603
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+ from typing import Dict, Optional, Tuple, Union
15
+
16
+ import numpy as np
17
+ from scipy import sparse
18
+ from scipy.sparse.linalg import spsolve
19
+
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Packaged demo data
23
+ # ---------------------------------------------------------------------------
24
+
25
+ _DATA_DIR = Path(__file__).resolve().parent / "data"
26
+
27
+
28
+ def _load_complex_npy(name: str) -> np.ndarray:
29
+ path = _DATA_DIR / name
30
+ if not path.is_file():
31
+ raise FileNotFoundError(
32
+ f"Missing VTFMTD demo data: {path}. "
33
+ "Reinstall PySDKit or restore pysdkit/_tfa/data/"
34
+ )
35
+ return np.asarray(np.load(path))
36
+
37
+
38
+ def load_dual_signal_noise() -> Dict[str, Union[np.ndarray, float]]:
39
+ """
40
+ Load the packaged dual-component noisy complex demo
41
+ (MATLAB ``Dual_signal_noise.mat``).
42
+
43
+ Sampling rate is ``fs = 3000`` Hz and length is 3000 samples (1 s),
44
+ matching ``test1.m``.
45
+ """
46
+ signal = _load_complex_npy("dual_signal_noise.npy").astype(np.complex128).ravel()
47
+ fs = 3000.0
48
+ t = np.arange(1, signal.size + 1, dtype=float) / fs
49
+ return {"signal": signal, "fs": fs, "t": t, "K": 2}
50
+
51
+
52
+ def load_single_nsignal() -> Dict[str, Union[np.ndarray, float]]:
53
+ """
54
+ Load the packaged single-component noisy micro-Doppler demo
55
+ (MATLAB ``Single_nsignal.mat``).
56
+
57
+ Sampling rate is ``fs = 8011`` Hz and length is 8011 samples (1 s),
58
+ matching ``test2.m``.
59
+ """
60
+ signal = _load_complex_npy("single_nsignal.npy").astype(np.complex128).ravel()
61
+ fs = 8011.0
62
+ t = np.arange(signal.size, dtype=float) / fs
63
+ return {"signal": signal, "fs": fs, "t": t, "K": 1}
64
+
65
+
66
+ def load_map2() -> np.ndarray:
67
+ """Load the packaged MATLAB ``map2`` colormap (shape ``(64, 3)``)."""
68
+ return np.asarray(np.load(_DATA_DIR / "map2.npy"), dtype=float)
69
+
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # STFT (MATLAB STFT.m)
73
+ # ---------------------------------------------------------------------------
74
+
75
+
76
+ def stft(signal: np.ndarray, hlength: Optional[int] = None) -> np.ndarray:
77
+ """
78
+ Short-time Fourier transform used by VTFMTD (MATLAB ``STFT.m``).
79
+
80
+ Uses a Gaussian analysis window and hop size 1 (one column per sample).
81
+ Only the positive-frequency half of the FFT is retained.
82
+
83
+ :param signal: 1D array (real or complex), length ``N``
84
+ :param hlength: window length; defaults to ``round(N / 8)``. Odd length
85
+ is enforced as in MATLAB (``hlength + 1 - rem(hlength, 2)``).
86
+ :return: STFT matrix of shape ``(F, N)`` with ``F = round(N / 2)``
87
+ """
88
+ x = np.asarray(signal).ravel()
89
+ n = int(x.size)
90
+ if n < 2:
91
+ raise ValueError("signal length must be >= 2")
92
+
93
+ if hlength is None:
94
+ hlength = int(round(n / 8.0))
95
+ hlength = int(hlength)
96
+ hlength = hlength + 1 - (hlength % 2)
97
+
98
+ ht = np.linspace(-0.5, 0.5, hlength)
99
+ h = np.exp(-np.pi / (0.32**2) * ht**2)
100
+ lh = (h.size - 1) // 2
101
+
102
+ f_bins = int(round(n / 2.0))
103
+ tfr = np.zeros((n, n), dtype=np.complex128)
104
+
105
+ # Vectorized over time for each lag tau (equivalent to MATLAB STFT.m)
106
+ half_n = n // 2 - 1
107
+ for tau_i, w in enumerate(h):
108
+ tau = int(tau_i - lh)
109
+ if abs(tau) > half_n:
110
+ continue
111
+ row = int(np.mod(n + tau, n))
112
+ # columns ti with valid sample index ti + tau
113
+ ti_lo = max(0, -tau)
114
+ ti_hi = min(n - 1, n - 1 - tau)
115
+ if ti_lo > ti_hi:
116
+ continue
117
+ cols = np.arange(ti_lo, ti_hi + 1)
118
+ tfr[row, cols] = x[cols + tau] * np.conj(w)
119
+
120
+ tfr = np.fft.fft(tfr, axis=0)
121
+ tfr = tfr[:f_bins, :]
122
+ return tfr / n * 2.0
123
+
124
+
125
+ def frequency_axis(n: int, fs: float) -> np.ndarray:
126
+ """
127
+ Frequency axis (Hz) matching MATLAB demos:
128
+ ``f = (0 : round(N/2)-1) * fs / N``.
129
+ """
130
+ n = int(n)
131
+ f_bins = int(round(n / 2.0))
132
+ return np.arange(f_bins, dtype=float) * (float(fs) / n)
133
+
134
+
135
+ def bin_index_grid(f_bins: int, n_time: int) -> np.ndarray:
136
+ """
137
+ MATLAB ``omega = repmat((1:F)', 1, T)`` — 1-based STFT bin indices
138
+ broadcast to shape ``(F, T)``.
139
+ """
140
+ return np.tile(np.arange(1, f_bins + 1, dtype=float)[:, None], (1, n_time))
141
+
142
+
143
+ def expand_omega_init(
144
+ omega_init: np.ndarray,
145
+ f_bins: int,
146
+ n_time: int,
147
+ k_modes: int,
148
+ ) -> np.ndarray:
149
+ """
150
+ Broadcast initial IFs to shape ``(F, T, K)``.
151
+
152
+ Accepted forms:
153
+ - ``(F, T, K)`` — used as-is
154
+ - ``(K,)`` or ``(K, 1)`` — constant IF (bin index) per mode
155
+ - ``(T, K)`` or ``(K, T)`` — time-varying IF per mode (no frequency axis)
156
+ """
157
+ w = np.asarray(omega_init, dtype=float)
158
+ if w.shape == (f_bins, n_time, k_modes):
159
+ return w.copy()
160
+
161
+ out = np.zeros((f_bins, n_time, k_modes), dtype=float)
162
+ if w.ndim == 1 and w.size == k_modes:
163
+ for k in range(k_modes):
164
+ out[:, :, k] = w[k]
165
+ return out
166
+ if w.shape == (k_modes, 1):
167
+ for k in range(k_modes):
168
+ out[:, :, k] = float(w[k, 0])
169
+ return out
170
+ if w.shape == (n_time, k_modes):
171
+ for k in range(k_modes):
172
+ out[:, :, k] = w[:, k][None, :]
173
+ return out
174
+ if w.shape == (k_modes, n_time):
175
+ for k in range(k_modes):
176
+ out[:, :, k] = w[k, :][None, :]
177
+ return out
178
+ raise ValueError(
179
+ "omega_init must have shape (F,T,K), (K,), (T,K) or (K,T); "
180
+ f"got {w.shape} for F={f_bins}, T={n_time}, K={k_modes}"
181
+ )
182
+
183
+
184
+ def first_difference_gram(n_time: int) -> sparse.csc_matrix:
185
+ """
186
+ Build ``D.T @ D`` for the first-order difference operator in MATLAB
187
+ ``VTFMTD.m`` (``spdiags`` with ``D(1,1)=1``).
188
+ """
189
+ t = int(n_time)
190
+ # D: diagonal 1, sub-diagonal -1
191
+ d0 = np.ones(t)
192
+ d_m1 = -np.ones(t - 1)
193
+ d = sparse.diags([d_m1, d0], [-1, 0], shape=(t, t), format="csc")
194
+ return (d.T @ d).tocsc()
195
+
196
+
197
+ def estimate_if_centroid(
198
+ mode_stft: np.ndarray,
199
+ bin_grid: np.ndarray,
200
+ eps: float = np.finfo(float).eps,
201
+ ) -> np.ndarray:
202
+ """
203
+ Centroid IF estimate (MATLAB ``omega_est`` update).
204
+
205
+ :param mode_stft: complex STFT of one mode, shape ``(F, T)``
206
+ :param bin_grid: frequency-bin indices, shape ``(F, T)``
207
+ :return: IF field shape ``(F, T)`` (constant along frequency)
208
+ """
209
+ power = np.abs(mode_stft) ** 2
210
+ numerator = np.sum(bin_grid * power, axis=0)
211
+ denominator = np.sum(power, axis=0)
212
+ if_traj = np.zeros(denominator.shape, dtype=float)
213
+ ok = denominator >= eps
214
+ if_traj[ok] = numerator[ok] / denominator[ok]
215
+ return np.tile(if_traj[None, :], (mode_stft.shape[0], 1))
216
+
217
+
218
+ def smooth_if(
219
+ omega_est: np.ndarray,
220
+ gram: sparse.csc_matrix,
221
+ beta: float,
222
+ ) -> np.ndarray:
223
+ """
224
+ Smooth IF trajectories (MATLAB ``omega_smooth`` update).
225
+
226
+ Solves ``(2/beta * D'D + I) x = omega_est`` once per mode row pattern.
227
+ Because centroids are constant along frequency, only one solve per
228
+ time-series is required, then broadcast.
229
+ """
230
+ f_bins, n_time = omega_est.shape
231
+ beta = float(beta)
232
+ eye = sparse.eye(n_time, format="csc")
233
+ system = ((2.0 / beta) * gram + eye).tocsc()
234
+ # all frequency rows share the same target trajectory
235
+ target = omega_est[0, :].astype(float)
236
+ smoothed = spsolve(system, target)
237
+ return np.tile(np.asarray(smoothed, dtype=float)[None, :], (f_bins, 1))
238
+
239
+
240
+ def moving_average_if(if_traj: np.ndarray, win: int = 30) -> np.ndarray:
241
+ """
242
+ Post-smoothing used in MATLAB demos (``Lth`` moving average on IF).
243
+
244
+ :param if_traj: 1D IF trajectory
245
+ :param win: odd/even window length (``Lth`` in the demos)
246
+ """
247
+ x = np.asarray(if_traj, dtype=float).ravel()
248
+ m = x.size
249
+ half = int(round(win / 2.0))
250
+ out = np.empty(m, dtype=float)
251
+ for i in range(m):
252
+ lo = max(0, i - half)
253
+ hi = min(m, i + half + 1)
254
+ out[i] = np.mean(x[lo:hi])
255
+ return out
256
+
257
+
258
+ def omega_bins_to_hz(omega_bins: np.ndarray, fs: float, n: int) -> np.ndarray:
259
+ """
260
+ Convert MATLAB-style 1-based bin IF values to Hertz.
261
+
262
+ STFT row ``j`` (1-based) corresponds to frequency ``(j - 1) * fs / n``.
263
+ """
264
+ return (np.asarray(omega_bins, dtype=float) - 1.0) * (float(fs) / float(n))
265
+
266
+
267
+ # ---------------------------------------------------------------------------
268
+ # Core algorithm
269
+ # ---------------------------------------------------------------------------
270
+
271
+
272
+ def vtfmtd(
273
+ signal: np.ndarray,
274
+ hlength: int,
275
+ K: int,
276
+ omega_init: np.ndarray,
277
+ alpha: float = 1e-5,
278
+ sigma: float = 1e-2,
279
+ beta: float = 1.0,
280
+ max_iter: int = 100,
281
+ epsilon: float = 1e-3,
282
+ ) -> Tuple[np.ndarray, np.ndarray]:
283
+ """
284
+ Functional VTFMTD interface (MATLAB ``VTFMTD``).
285
+
286
+ :param signal: 1D real/complex signal
287
+ :param hlength: STFT window length
288
+ :param K: number of modes
289
+ :param omega_init: initial IF field / centers (see ``expand_omega_init``)
290
+ :param alpha: bandwidth penalty (larger -> narrower TF support)
291
+ :param sigma: dual-ascent step size
292
+ :param beta: IF smoothness penalty (smaller -> smoother IF)
293
+ :param max_iter: maximum ADMM iterations
294
+ :param epsilon: relative convergence tolerance on mode STFTs
295
+ :return: ``(Gk, omega_smooth)`` with shapes ``(F, T, K)`` and ``(F, T, K)``
296
+ """
297
+ x = np.asarray(signal).ravel()
298
+ g = stft(x, hlength=hlength)
299
+ f_bins, n_time = g.shape
300
+ k_modes = int(K)
301
+
302
+ omega_smooth = expand_omega_init(omega_init, f_bins, n_time, k_modes)
303
+ gk = np.zeros((f_bins, n_time, k_modes), dtype=np.complex128)
304
+ lam = np.zeros((f_bins, n_time), dtype=np.complex128)
305
+ bin_grid = bin_index_grid(f_bins, n_time)
306
+ gram = first_difference_gram(n_time)
307
+
308
+ alpha = float(alpha)
309
+ sigma = float(sigma)
310
+ beta = float(beta)
311
+ epsilon = float(epsilon)
312
+
313
+ for it in range(int(max_iter)):
314
+ gk_prev = gk.copy()
315
+
316
+ # ----- update mode STFTs (TF Wiener filtering) -----
317
+ for k in range(k_modes):
318
+ sum_others = np.sum(gk, axis=2) - gk[:, :, k]
319
+ denom = 1.0 + alpha * (bin_grid - omega_smooth[:, :, k]) ** 2
320
+ gk[:, :, k] = (g - sum_others - lam / 2.0) / denom
321
+
322
+ # ----- centroid IF + smoothing -----
323
+ for k in range(k_modes):
324
+ omega_est = estimate_if_centroid(gk[:, :, k], bin_grid)
325
+ omega_smooth[:, :, k] = smooth_if(omega_est, gram, beta=beta)
326
+
327
+ # ----- dual ascent -----
328
+ lam = lam + sigma * (g - np.sum(gk, axis=2))
329
+
330
+ if it > 0:
331
+ diff = np.sum(np.abs(gk - gk_prev))
332
+ norm = np.sum(np.abs(gk_prev)) + np.finfo(float).eps
333
+ if diff < epsilon * norm:
334
+ break
335
+
336
+ return gk, omega_smooth
337
+
338
+
339
+ class VTFMTD(object):
340
+ """
341
+ Variational Time-Frequency Mode Tracking Decomposition.
342
+
343
+ Decomposes the STFT of a (possibly complex) 1D signal into ``K`` mode
344
+ STFTs while tracking instantaneous-frequency ridges via ADMM, following
345
+ Dong et al., Signal Processing 246:110603, 2026.
346
+ """
347
+
348
+ def __init__(
349
+ self,
350
+ hlength: int = 30,
351
+ K: int = 2,
352
+ alpha: float = 1e-5,
353
+ sigma: float = 1e-2,
354
+ beta: float = 1.0,
355
+ max_iter: int = 100,
356
+ epsilon: float = 1e-3,
357
+ ) -> None:
358
+ """
359
+ :param hlength: STFT window length
360
+ :param K: number of TF modes
361
+ :param alpha: TF bandwidth penalty
362
+ :param sigma: Lagrange dual step
363
+ :param beta: IF smoothness penalty
364
+ :param max_iter: maximum iterations
365
+ :param epsilon: convergence tolerance
366
+ """
367
+ self.hlength = int(hlength)
368
+ self.K = int(K)
369
+ self.alpha = float(alpha)
370
+ self.sigma = float(sigma)
371
+ self.beta = float(beta)
372
+ self.max_iter = int(max_iter)
373
+ self.epsilon = float(epsilon)
374
+
375
+ self.signal: Optional[np.ndarray] = None
376
+ self.stft_signal: Optional[np.ndarray] = None
377
+ self.Gk: Optional[np.ndarray] = None
378
+ self.omega: Optional[np.ndarray] = None
379
+
380
+ def __str__(self) -> str:
381
+ return "Variational Time-Frequency Mode Tracking Decomposition (VTFMTD)"
382
+
383
+ def __call__(
384
+ self,
385
+ signal: np.ndarray,
386
+ omega_init: np.ndarray,
387
+ return_all: bool = False,
388
+ ):
389
+ return self.fit_transform(
390
+ signal=signal, omega_init=omega_init, return_all=return_all
391
+ )
392
+
393
+ def fit_transform(
394
+ self,
395
+ signal: np.ndarray,
396
+ omega_init: np.ndarray,
397
+ return_all: bool = False,
398
+ ) -> Union[
399
+ Tuple[np.ndarray, np.ndarray], Tuple[np.ndarray, np.ndarray, np.ndarray]
400
+ ]:
401
+ """
402
+ Run VTFMTD.
403
+
404
+ :param signal: 1D real/complex input
405
+ :param omega_init: initial IFs (bin units), see ``expand_omega_init``
406
+ :param return_all: if True, also return the composite STFT
407
+ :return: ``(Gk, omega)`` or ``(Gk, omega, G)``
408
+ """
409
+ x = np.asarray(signal).ravel()
410
+ gk, omega = vtfmtd(
411
+ signal=x,
412
+ hlength=self.hlength,
413
+ K=self.K,
414
+ omega_init=omega_init,
415
+ alpha=self.alpha,
416
+ sigma=self.sigma,
417
+ beta=self.beta,
418
+ max_iter=self.max_iter,
419
+ epsilon=self.epsilon,
420
+ )
421
+ g = stft(x, hlength=self.hlength)
422
+
423
+ self.signal = x
424
+ self.stft_signal = g
425
+ self.Gk = gk
426
+ self.omega = omega
427
+
428
+ if return_all:
429
+ return gk, omega, g
430
+ return gk, omega
431
+
432
+ def if_trajectories(self) -> np.ndarray:
433
+ """
434
+ Return IF trajectories with shape ``(K, T)`` (bin units),
435
+ taken from the first frequency row of ``omega`` (MATLAB convention).
436
+ """
437
+ if self.omega is None:
438
+ raise ValueError("Call fit_transform before requesting IF trajectories.")
439
+ return np.asarray(self.omega[0, :, :], dtype=float).T.copy()