PySDKit 0.4.45__tar.gz → 0.4.47__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 (193) hide show
  1. {pysdkit-0.4.45 → pysdkit-0.4.47}/PKG-INFO +3 -2
  2. {pysdkit-0.4.45 → pysdkit-0.4.47}/PySDKit.egg-info/PKG-INFO +3 -2
  3. {pysdkit-0.4.45 → pysdkit-0.4.47}/PySDKit.egg-info/SOURCES.txt +9 -0
  4. {pysdkit-0.4.45 → pysdkit-0.4.47}/README.md +2 -1
  5. {pysdkit-0.4.45 → pysdkit-0.4.47}/pyproject.toml +2 -0
  6. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/__init__.py +14 -2
  7. pysdkit-0.4.47/pysdkit/_tfa/__init__.py +41 -0
  8. pysdkit-0.4.47/pysdkit/_tfa/data/__init__.py +2 -0
  9. pysdkit-0.4.47/pysdkit/_tfa/data/dual_signal_noise.npy +0 -0
  10. pysdkit-0.4.47/pysdkit/_tfa/data/map2.npy +0 -0
  11. pysdkit-0.4.47/pysdkit/_tfa/data/single_nsignal.npy +0 -0
  12. pysdkit-0.4.47/pysdkit/_tfa/vtfmtd.py +439 -0
  13. pysdkit-0.4.47/pysdkit/_vncmd/__init__.py +44 -0
  14. pysdkit-0.4.47/pysdkit/_vncmd/stnbmd.py +515 -0
  15. pysdkit-0.4.47/pysdkit/tests/test_stnbmd.py +243 -0
  16. pysdkit-0.4.47/pysdkit/tests/test_vtfmtd.py +273 -0
  17. pysdkit-0.4.45/pysdkit/_vncmd/__init__.py +0 -10
  18. {pysdkit-0.4.45 → pysdkit-0.4.47}/LICENSE +0 -0
  19. {pysdkit-0.4.45 → pysdkit-0.4.47}/PySDKit.egg-info/dependency_links.txt +0 -0
  20. {pysdkit-0.4.45 → pysdkit-0.4.47}/PySDKit.egg-info/requires.txt +0 -0
  21. {pysdkit-0.4.45 → pysdkit-0.4.47}/PySDKit.egg-info/top_level.txt +0 -0
  22. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/__init__.py +0 -0
  23. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/_find_extrema.py +0 -0
  24. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/_prepare_points.py +0 -0
  25. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/_splines.py +0 -0
  26. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/ceemdan.py +0 -0
  27. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/eemd.py +0 -0
  28. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/efd.py +0 -0
  29. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/emd.py +0 -0
  30. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/esmd.py +0 -0
  31. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/hht/__init__.py +0 -0
  32. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/hht/frequency.py +0 -0
  33. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/hht/hht.py +0 -0
  34. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/memd.py +0 -0
  35. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/remd.py +0 -0
  36. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/semd.py +0 -0
  37. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd/tvf_emd.py +0 -0
  38. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd2d/__init__.py +0 -0
  39. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd2d/bemd.py +0 -0
  40. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd2d/bmemd.py +0 -0
  41. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_emd2d/emd2d.py +0 -0
  42. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_ewt/__init__.py +0 -0
  43. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_ewt/ewt.py +0 -0
  44. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_ewt/ewt2d.py +0 -0
  45. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_faemd/__init__.py +0 -0
  46. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_faemd/extrema.py +0 -0
  47. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_faemd/faemd.py +0 -0
  48. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_faemd/faemd2d.py +0 -0
  49. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_faemd/faemd3d.py +0 -0
  50. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_faemd/filter.py +0 -0
  51. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_fmd/__init__.py +0 -0
  52. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_fmd/fmd.py +0 -0
  53. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_fmd/nfmd.py +0 -0
  54. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_gdmd/__init__.py +0 -0
  55. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_gdmd/agncmd.py +0 -0
  56. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_gdmd/gdmd.py +0 -0
  57. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_gdmd/ivgnmd.py +0 -0
  58. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_gdmd/vgnmd.py +0 -0
  59. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_imd/__init__.py +0 -0
  60. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_imd/apmd.py +0 -0
  61. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_imd/data/gearbox_fault_snippet.npy +0 -0
  62. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_imd/data/input_sig.npy +0 -0
  63. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_imd/imd.py +0 -0
  64. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_jmd/__init__.py +0 -0
  65. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_jmd/jmd.py +0 -0
  66. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_jmd/mjmd.py +0 -0
  67. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_jmd/sjmd.py +0 -0
  68. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_lmd/__init__.py +0 -0
  69. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_lmd/lmd.py +0 -0
  70. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_lmd/rlmd.py +0 -0
  71. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_osd/__init__.py +0 -0
  72. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_osd/components.py +0 -0
  73. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_osd/osd.py +0 -0
  74. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_osd/swd.py +0 -0
  75. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_tid/__init__.py +0 -0
  76. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_tid/alif.py +0 -0
  77. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_tid/data/__init__.py +0 -0
  78. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_tid/data/prefixed_double_filter.npy +0 -0
  79. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_tid/hvd.py +0 -0
  80. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_tid/itd.py +0 -0
  81. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_tid/iterative_filtering.py +0 -0
  82. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_tid/ssa.py +0 -0
  83. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/__init__.py +0 -0
  84. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/acmd.py +0 -0
  85. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/avmd.py +0 -0
  86. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/ba_acmd.py +0 -0
  87. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/base.py +0 -0
  88. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/mvmd.py +0 -0
  89. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/ovmd.py +0 -0
  90. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/stvmd.py +0 -0
  91. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/svmd.py +0 -0
  92. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/vmd_c.py +0 -0
  93. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/vmd_f.py +0 -0
  94. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd/vme.py +0 -0
  95. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd2d/__init__.py +0 -0
  96. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd2d/cvmd2d.py +0 -0
  97. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vmd2d/vmd2d.py +0 -0
  98. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vncmd/avncmd.py +0 -0
  99. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vncmd/incmd.py +0 -0
  100. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/_vncmd/vncmd.py +0 -0
  101. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/data/__init__.py +0 -0
  102. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/data/_add_noise.py +0 -0
  103. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/data/_cube.py +0 -0
  104. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/data/_generator.py +0 -0
  105. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/data/_image.py +0 -0
  106. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/data/_models.py +0 -0
  107. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/data/_test_univariate.py +0 -0
  108. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/data/_time_series.py +0 -0
  109. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/data/fmd_demo.npy +0 -0
  110. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/data/texture.txt +0 -0
  111. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/data/vmd_example.npy +0 -0
  112. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/entropy/__init__.py +0 -0
  113. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
  114. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/entropy/_permutation_entropy.py +0 -0
  115. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/entropy/_sample_entropy.py +0 -0
  116. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/models/__init__.py +0 -0
  117. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/models/_base.py +0 -0
  118. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/models/_kmeans.py +0 -0
  119. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/models/_knn.py +0 -0
  120. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/models/_pca.py +0 -0
  121. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/plot/__init__.py +0 -0
  122. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/plot/_fourier_spectra.py +0 -0
  123. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/plot/_functions.py +0 -0
  124. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/plot/_plot_images.py +0 -0
  125. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/plot/_plot_imfs.py +0 -0
  126. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/plot/_plot_signal.py +0 -0
  127. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/__init__.py +0 -0
  128. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/data/__init__.py +0 -0
  129. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/data/test_generator_cube.py +0 -0
  130. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/data/test_generator_image.py +0 -0
  131. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/data/test_generator_signal.py +0 -0
  132. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
  133. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/models/__init__.py +0 -0
  134. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/models/test_knn.py +0 -0
  135. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/models/test_pca.py +0 -0
  136. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/run_all.py +0 -0
  137. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_agncmd.py +0 -0
  138. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_alif.py +0 -0
  139. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_apmd.py +0 -0
  140. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_avncmd.py +0 -0
  141. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_bmemd.py +0 -0
  142. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_ceemdan.py +0 -0
  143. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_cvmd2d.py +0 -0
  144. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_eemd.py +0 -0
  145. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_emd.py +0 -0
  146. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_esmd.py +0 -0
  147. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_ewt.py +0 -0
  148. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_ewt2d.py +0 -0
  149. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_faemd.py +0 -0
  150. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_faemd2d.py +0 -0
  151. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_faemd3d.py +0 -0
  152. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_fmd.py +0 -0
  153. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_gdmd.py +0 -0
  154. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_hvd.py +0 -0
  155. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_imd.py +0 -0
  156. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_itd.py +0 -0
  157. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_ivgnmd.py +0 -0
  158. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_jmd.py +0 -0
  159. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_lmd.py +0 -0
  160. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_moving_decomp.py +0 -0
  161. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_mstl.py +0 -0
  162. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_osd.py +0 -0
  163. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_ovmd.py +0 -0
  164. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_rlmd.py +0 -0
  165. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_semd.py +0 -0
  166. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_sjmd.py +0 -0
  167. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_ssa.py +0 -0
  168. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_stl.py +0 -0
  169. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_stvmd.py +0 -0
  170. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_svmd.py +0 -0
  171. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_swd.py +0 -0
  172. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_vgnmd.py +0 -0
  173. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_vmd.py +0 -0
  174. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_vmd2d.py +0 -0
  175. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tests/test_vncmd.py +0 -0
  176. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tsa/__init__.py +0 -0
  177. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tsa/_dtw.py +0 -0
  178. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tsa/_moving_decomp.py +0 -0
  179. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tsa/_mstl.py +0 -0
  180. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/tsa/_stl.py +0 -0
  181. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/__init__.py +0 -0
  182. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/_correlation.py +0 -0
  183. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/_diagnalization.py +0 -0
  184. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/_differ.py +0 -0
  185. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/_fft.py +0 -0
  186. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/_function.py +0 -0
  187. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/_hilbert.py +0 -0
  188. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/_instantaneous.py +0 -0
  189. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/_kernel_matrix.py +0 -0
  190. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/_mirror.py +0 -0
  191. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/_process.py +0 -0
  192. {pysdkit-0.4.45 → pysdkit-0.4.47}/pysdkit/utils/_smooth1d.py +0 -0
  193. {pysdkit-0.4.45 → pysdkit-0.4.47}/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.47
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>
@@ -199,7 +199,8 @@ plot_IMFs(signal, IMFs) # per-channel panels
199
199
  | [`MJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/mjmd.py) (Multivariate Jump Plus AM-FM Mode Decomposition) | [[paper]](https://arxiv.org/abs/2407.07800) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/169393-multivariate-jump-plus-am-fm-mode-decomposition-mjmd?s_tid=prof_contriblnk) | ✔️ |
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
- | [`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) | ✖️ |
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.47
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>
@@ -199,7 +199,8 @@ plot_IMFs(signal, IMFs) # per-channel panels
199
199
  | [`MJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/mjmd.py) (Multivariate Jump Plus AM-FM Mode Decomposition) | [[paper]](https://arxiv.org/abs/2407.07800) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/169393-multivariate-jump-plus-am-fm-mode-decomposition-mjmd?s_tid=prof_contriblnk) | ✔️ |
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
- | [`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) | ✖️ |
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
@@ -86,6 +92,7 @@ pysdkit/_vmd2d/vmd2d.py
86
92
  pysdkit/_vncmd/__init__.py
87
93
  pysdkit/_vncmd/avncmd.py
88
94
  pysdkit/_vncmd/incmd.py
95
+ pysdkit/_vncmd/stnbmd.py
89
96
  pysdkit/_vncmd/vncmd.py
90
97
  pysdkit/data/__init__.py
91
98
  pysdkit/data/_add_noise.py
@@ -147,6 +154,7 @@ pysdkit/tests/test_semd.py
147
154
  pysdkit/tests/test_sjmd.py
148
155
  pysdkit/tests/test_ssa.py
149
156
  pysdkit/tests/test_stl.py
157
+ pysdkit/tests/test_stnbmd.py
150
158
  pysdkit/tests/test_stvmd.py
151
159
  pysdkit/tests/test_svmd.py
152
160
  pysdkit/tests/test_swd.py
@@ -154,6 +162,7 @@ pysdkit/tests/test_vgnmd.py
154
162
  pysdkit/tests/test_vmd.py
155
163
  pysdkit/tests/test_vmd2d.py
156
164
  pysdkit/tests/test_vncmd.py
165
+ pysdkit/tests/test_vtfmtd.py
157
166
  pysdkit/tests/data/__init__.py
158
167
  pysdkit/tests/data/test_generator_cube.py
159
168
  pysdkit/tests/data/test_generator_image.py
@@ -146,7 +146,8 @@ plot_IMFs(signal, IMFs) # per-channel panels
146
146
  | [`MJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/mjmd.py) (Multivariate Jump Plus AM-FM Mode Decomposition) | [[paper]](https://arxiv.org/abs/2407.07800) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/169393-multivariate-jump-plus-am-fm-mode-decomposition-mjmd?s_tid=prof_contriblnk) | ✔️ |
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
- | [`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) | ✖️ |
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.47"
6
6
 
7
7
  # Empirical Mode Decomposition
8
8
  from ._emd import EMD
@@ -109,9 +109,15 @@ from ._vncmd import INCMD
109
109
  # Adaptive Variational Nonlinear Chirp Mode Decomposition
110
110
  from ._vncmd import AVNCMD
111
111
 
112
+ # Short-Time Narrow-Banded Mode Decomposition
113
+ from ._vncmd import STNBMD, stnbmd
114
+
112
115
  # Adaptive Local Iterative Filtering (time iterative decomposition)
113
116
  from ._tid import ALIF
114
117
 
118
+ # Variational Time-Frequency Mode Tracking Decomposition (TFA)
119
+ from ._tfa import VTFMTD, vtfmtd
120
+
115
121
  # Adaptive Polymorphic Mode Decomposition / Impulsive Mode Decomposition
116
122
  from ._imd import APMD, IMD, imd
117
123
 
@@ -193,7 +199,7 @@ Optimization-based Signal Decomposition | OSD
193
199
  Generalized Dispersion Mode Decomposition | GDMD
194
200
  Variational Generalized Nonlinear Mode Dec. | VGNMD
195
201
  Improved VGNMD (crossed chirp / dispersive) | IVGNMD
196
- Adaptive Generalized Dispersive Mode Dec. | AGNCMD / AGDMD
202
+ Adaptive Generalized Dispersive Mode Dec. | AGNCMD / AGDMD
197
203
  Variational Mode Decomposition | VMD
198
204
  Multivariate Variational Mode Decomposition | MVMD
199
205
  Variational Mode Extraction | VME
@@ -205,7 +211,9 @@ Compact VMD for 2D Image | CVMD2D
205
211
  Variational Nonlinear Chirp Mode Decomposition | VNCMD
206
212
  Iterative Nonlinear Chirp Mode Decomposition | INCMD
207
213
  Adaptive Variational Nonlinear Chirp Mode Dec. | AVNCMD
214
+ Short-Time Narrow-Banded Mode Decomposition | STNBMD
208
215
  Adaptive Local Iterative Filtering | ALIF
216
+ Variational TF Mode Tracking Decomposition | VTFMTD
209
217
  Adaptive Polymorphic Mode Decomposition | APMD
210
218
  Impulsive Mode Decomposition | IMD
211
219
  Empirical Wavelet Transform | EWT
@@ -272,7 +280,11 @@ __all__ = [
272
280
  "VNCMD",
273
281
  "INCMD",
274
282
  "AVNCMD",
283
+ "STNBMD",
284
+ "stnbmd",
275
285
  "ALIF",
286
+ "VTFMTD",
287
+ "vtfmtd",
276
288
  "APMD",
277
289
  "IMD",
278
290
  "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()