PySDKit 0.4.35__tar.gz → 0.4.36__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 (173) hide show
  1. {pysdkit-0.4.35 → pysdkit-0.4.36}/PKG-INFO +1 -1
  2. {pysdkit-0.4.35 → pysdkit-0.4.36}/PySDKit.egg-info/PKG-INFO +1 -1
  3. {pysdkit-0.4.35 → pysdkit-0.4.36}/PySDKit.egg-info/SOURCES.txt +2 -0
  4. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/__init__.py +11 -2
  5. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/__init__.py +2 -0
  6. pysdkit-0.4.36/pysdkit/_emd/semd.py +338 -0
  7. pysdkit-0.4.36/pysdkit/_faemd/faemd.py +204 -0
  8. pysdkit-0.4.36/pysdkit/_faemd/faemd2d.py +155 -0
  9. pysdkit-0.4.36/pysdkit/_faemd/faemd3d.py +152 -0
  10. pysdkit-0.4.36/pysdkit/_faemd/filter.py +326 -0
  11. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_faemd.py +15 -0
  12. pysdkit-0.4.36/pysdkit/tests/test_faemd2d.py +68 -0
  13. pysdkit-0.4.36/pysdkit/tests/test_faemd3d.py +60 -0
  14. pysdkit-0.4.36/pysdkit/tests/test_semd.py +143 -0
  15. pysdkit-0.4.35/pysdkit/_faemd/faemd.py +0 -461
  16. pysdkit-0.4.35/pysdkit/_faemd/faemd2d.py +0 -36
  17. pysdkit-0.4.35/pysdkit/_faemd/faemd3d.py +0 -33
  18. pysdkit-0.4.35/pysdkit/_faemd/filter.py +0 -46
  19. pysdkit-0.4.35/pysdkit/tests/test_faemd2d.py +0 -11
  20. pysdkit-0.4.35/pysdkit/tests/test_faemd3d.py +0 -11
  21. {pysdkit-0.4.35 → pysdkit-0.4.36}/LICENSE +0 -0
  22. {pysdkit-0.4.35 → pysdkit-0.4.36}/PySDKit.egg-info/dependency_links.txt +0 -0
  23. {pysdkit-0.4.35 → pysdkit-0.4.36}/PySDKit.egg-info/requires.txt +0 -0
  24. {pysdkit-0.4.35 → pysdkit-0.4.36}/PySDKit.egg-info/top_level.txt +0 -0
  25. {pysdkit-0.4.35 → pysdkit-0.4.36}/README.md +0 -0
  26. {pysdkit-0.4.35 → pysdkit-0.4.36}/pyproject.toml +0 -0
  27. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_alif/__init__.py +0 -0
  28. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_alif/alif.py +0 -0
  29. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_alif/data/__init__.py +0 -0
  30. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_alif/data/prefixed_double_filter.npy +0 -0
  31. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_alif/iterative_filtering.py +0 -0
  32. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_apmd/__init__.py +0 -0
  33. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_apmd/apmd.py +0 -0
  34. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/_find_extrema.py +0 -0
  35. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/_prepare_points.py +0 -0
  36. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/_splines.py +0 -0
  37. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/ceemdan.py +0 -0
  38. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/eemd.py +0 -0
  39. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/efd.py +0 -0
  40. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/emd.py +0 -0
  41. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/hht/__init__.py +0 -0
  42. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/hht/frequency.py +0 -0
  43. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/hht/hht.py +0 -0
  44. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/memd.py +0 -0
  45. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/remd.py +0 -0
  46. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd/tvf_emd.py +0 -0
  47. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd2d/__init__.py +0 -0
  48. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd2d/bemd.py +0 -0
  49. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd2d/bmemd.py +0 -0
  50. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_emd2d/emd2d.py +0 -0
  51. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_esmd/__init__.py +0 -0
  52. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_esmd/esmd.py +0 -0
  53. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_ewt/__init__.py +0 -0
  54. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_ewt/ewt.py +0 -0
  55. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_ewt/ewt2d.py +0 -0
  56. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_faemd/__init__.py +0 -0
  57. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_faemd/extrema.py +0 -0
  58. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_fmd/__init__.py +0 -0
  59. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_fmd/fmd.py +0 -0
  60. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_fmd/nfmd.py +0 -0
  61. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_gdmd/__init__.py +0 -0
  62. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_gdmd/gdmd.py +0 -0
  63. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_hvd/__init__.py +0 -0
  64. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_hvd/hvd.py +0 -0
  65. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_itd/__init__.py +0 -0
  66. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_itd/itd.py +0 -0
  67. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_jmd/__init__.py +0 -0
  68. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_jmd/jmd.py +0 -0
  69. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_jmd/mjmd.py +0 -0
  70. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_jmd/sjmd.py +0 -0
  71. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_lmd/__init__.py +0 -0
  72. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_lmd/lmd.py +0 -0
  73. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_lmd/rlmd.py +0 -0
  74. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_osd/__init__.py +0 -0
  75. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_osd/swd.py +0 -0
  76. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_ssa/__init__.py +0 -0
  77. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_ssa/ssa.py +0 -0
  78. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/__init__.py +0 -0
  79. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/acmd.py +0 -0
  80. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/avmd.py +0 -0
  81. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/ba_acmd.py +0 -0
  82. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/base.py +0 -0
  83. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/mvmd.py +0 -0
  84. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/ovmd.py +0 -0
  85. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/stvmd.py +0 -0
  86. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/svmd.py +0 -0
  87. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/vmd_c.py +0 -0
  88. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/vmd_f.py +0 -0
  89. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd/vme.py +0 -0
  90. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd2d/__init__.py +0 -0
  91. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd2d/cvmd2d.py +0 -0
  92. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vmd2d/vmd2d.py +0 -0
  93. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vncmd/__init__.py +0 -0
  94. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vncmd/avncmd.py +0 -0
  95. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vncmd/incmd.py +0 -0
  96. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/_vncmd/vncmd.py +0 -0
  97. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/data/__init__.py +0 -0
  98. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/data/_add_noise.py +0 -0
  99. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/data/_cube.py +0 -0
  100. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/data/_generator.py +0 -0
  101. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/data/_image.py +0 -0
  102. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/data/_models.py +0 -0
  103. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/data/_test_univariate.py +0 -0
  104. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/data/_time_series.py +0 -0
  105. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/data/texture.txt +0 -0
  106. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/entropy/__init__.py +0 -0
  107. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
  108. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/entropy/_permutation_entropy.py +0 -0
  109. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/entropy/_sample_entropy.py +0 -0
  110. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/models/__init__.py +0 -0
  111. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/models/_base.py +0 -0
  112. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/models/_kmeans.py +0 -0
  113. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/models/_knn.py +0 -0
  114. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/models/_pca.py +0 -0
  115. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/plot/__init__.py +0 -0
  116. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/plot/_fourier_spectra.py +0 -0
  117. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/plot/_functions.py +0 -0
  118. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/plot/_plot_images.py +0 -0
  119. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/plot/_plot_imfs.py +0 -0
  120. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/plot/_plot_signal.py +0 -0
  121. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/__init__.py +0 -0
  122. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/data/__init__.py +0 -0
  123. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/data/test_generator_cube.py +0 -0
  124. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/data/test_generator_image.py +0 -0
  125. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/data/test_generator_signal.py +0 -0
  126. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
  127. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/models/__init__.py +0 -0
  128. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/models/test_knn.py +0 -0
  129. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/models/test_pca.py +0 -0
  130. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/run_all.py +0 -0
  131. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_alif.py +0 -0
  132. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_apmd.py +0 -0
  133. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_avncmd.py +0 -0
  134. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_ceemdan.py +0 -0
  135. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_emd.py +0 -0
  136. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_ewt.py +0 -0
  137. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_ewt2d.py +0 -0
  138. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_gdmd.py +0 -0
  139. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_itd.py +0 -0
  140. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_jmd.py +0 -0
  141. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_lmd.py +0 -0
  142. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_moving_decomp.py +0 -0
  143. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_mstl.py +0 -0
  144. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_ovmd.py +0 -0
  145. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_rlmd.py +0 -0
  146. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_sjmd.py +0 -0
  147. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_ssa.py +0 -0
  148. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_stl.py +0 -0
  149. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_stvmd.py +0 -0
  150. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_svmd.py +0 -0
  151. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_swd.py +0 -0
  152. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_vmd.py +0 -0
  153. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_vmd2d.py +0 -0
  154. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tests/test_vncmd.py +0 -0
  155. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tsa/__init__.py +0 -0
  156. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tsa/_dtw.py +0 -0
  157. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tsa/_moving_decomp.py +0 -0
  158. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tsa/_mstl.py +0 -0
  159. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/tsa/_stl.py +0 -0
  160. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/__init__.py +0 -0
  161. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_cite.py +0 -0
  162. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_correlation.py +0 -0
  163. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_diagnalization.py +0 -0
  164. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_differ.py +0 -0
  165. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_fft.py +0 -0
  166. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_function.py +0 -0
  167. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_hilbert.py +0 -0
  168. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_instantaneous.py +0 -0
  169. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_kernel_matrix.py +0 -0
  170. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_mirror.py +0 -0
  171. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_process.py +0 -0
  172. {pysdkit-0.4.35 → pysdkit-0.4.36}/pysdkit/utils/_smooth1d.py +0 -0
  173. {pysdkit-0.4.35 → pysdkit-0.4.36}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySDKit
3
- Version: 0.4.35
3
+ Version: 0.4.36
4
4
  Summary: A Python library for signal decomposition algorithms with a unified interface.
5
5
  Author: josefinez, Deeksha Manjunath, Yuan Feng, JacktheFowler
6
6
  Author-email: Whenxuan Wang <wwhenxuan@gmail.com>, RuiZhe Wang <3133986068@qq.com>, WenTong Zhao <23049200290@stu.xidian.edu.cn>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySDKit
3
- Version: 0.4.35
3
+ Version: 0.4.36
4
4
  Summary: A Python library for signal decomposition algorithms with a unified interface.
5
5
  Author: josefinez, Deeksha Manjunath, Yuan Feng, JacktheFowler
6
6
  Author-email: Whenxuan Wang <wwhenxuan@gmail.com>, RuiZhe Wang <3133986068@qq.com>, WenTong Zhao <23049200290@stu.xidian.edu.cn>
@@ -24,6 +24,7 @@ pysdkit/_emd/efd.py
24
24
  pysdkit/_emd/emd.py
25
25
  pysdkit/_emd/memd.py
26
26
  pysdkit/_emd/remd.py
27
+ pysdkit/_emd/semd.py
27
28
  pysdkit/_emd/tvf_emd.py
28
29
  pysdkit/_emd/hht/__init__.py
29
30
  pysdkit/_emd/hht/frequency.py
@@ -126,6 +127,7 @@ pysdkit/tests/test_moving_decomp.py
126
127
  pysdkit/tests/test_mstl.py
127
128
  pysdkit/tests/test_ovmd.py
128
129
  pysdkit/tests/test_rlmd.py
130
+ pysdkit/tests/test_semd.py
129
131
  pysdkit/tests/test_sjmd.py
130
132
  pysdkit/tests/test_ssa.py
131
133
  pysdkit/tests/test_stl.py
@@ -2,7 +2,7 @@
2
2
  A Python library for signal decomposition algorithms.
3
3
  """
4
4
 
5
- __version__ = "0.4.35"
5
+ __version__ = "0.4.36"
6
6
 
7
7
  # Empirical Mode Decomposition
8
8
  from ._emd import EMD
@@ -19,6 +19,9 @@ from ._emd import REMD
19
19
  # Multivariate Empirical Mode Decomposition
20
20
  from ._emd import MEMD
21
21
 
22
+ # Serial Empirical Mode Decomposition
23
+ from ._emd import SEMD
24
+
22
25
  # Time Varying Filter based Empirical Mode Decomposition
23
26
  from ._emd import TVF_EMD
24
27
 
@@ -26,7 +29,7 @@ from ._emd import TVF_EMD
26
29
  from ._emd import EFD
27
30
 
28
31
  # Fast and Adaptive Empirical Mode Decomposition
29
- from ._faemd import FAEMD
32
+ from ._faemd import FAEMD, FAEMD2D, FAEMD3D
30
33
 
31
34
  # Empirical Mode Decomposition 2D for images
32
35
  from ._emd2d import EMD2D
@@ -153,9 +156,12 @@ Ensemble Empirical Mode Decomposition | EEMD
153
156
  Complete Ensemble EMD with Adaptive Noise | CEEMDAN
154
157
  Robust Empirical Mode Decomposition | REMD
155
158
  Multivariate Empirical Mode Decomposition | MEMD
159
+ Serial Empirical Mode Decomposition | SEMD
156
160
  Time Varying Filter based EMD | TVF_EMD
157
161
  Empirical Fourier Decomposition | EFD
158
162
  Fast and Adaptive Empirical Mode Decomposition | FAEMD
163
+ Bidimensional FAEMD | FAEMD2D
164
+ Tridimensional FAEMD | FAEMD3D
159
165
  Empirical Mode Decomposition 2D for images | EMD2D
160
166
  Hilbert Vibration Decomposition | HVD
161
167
  Intrinsic Time-Scale Decomposition | ITD
@@ -198,9 +204,12 @@ __all__ = [
198
204
  "CEEMDAN",
199
205
  "REMD",
200
206
  "MEMD",
207
+ "SEMD",
201
208
  "TVF_EMD",
202
209
  "EFD",
203
210
  "FAEMD",
211
+ "FAEMD2D",
212
+ "FAEMD3D",
204
213
  "EMD2D",
205
214
  "HVD",
206
215
  "ITD",
@@ -26,3 +26,5 @@ from .tvf_emd import TVF_EMD
26
26
  from .efd import EFD
27
27
 
28
28
  from .hht import HHT
29
+
30
+ from .semd import SEMD, concatenate_signals, deconcatenate_imfs, transition_bridge
@@ -0,0 +1,338 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2026/08/01
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+
7
+ Serial-EMD (SEMD): fast multi-signal EMD via 1-D serialization.
8
+
9
+ Zhang, J., Feng, F., Marti-Puig, P., Caiafa, C. F., Sun, Z., Duan, F.,
10
+ and Solé-Casals, J. (2021).
11
+ Serial-EMD: Fast Empirical Mode Decomposition Method for Multi-dimensional
12
+ Signals Based on Serialization. Information Sciences.
13
+ https://doi.org/10.1016/j.ins.2021.09.033
14
+
15
+ Reference code: https://github.com/ffbear1993/serial-emd
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import Optional, Tuple, Union
21
+
22
+ import numpy as np
23
+
24
+ from .emd import EMD
25
+
26
+
27
+ def concatenate_signals(matrix_x: np.ndarray, num_interval: int) -> np.ndarray:
28
+ """
29
+ Serialize multi-channel signals with smooth transition bridges.
30
+
31
+ Follows Zhang et al. (2021) / the official ``serial-emd`` Python reference.
32
+
33
+ :param matrix_x: Array of shape ``(M, N)`` — ``M`` samples (rows),
34
+ ``N`` channels (columns).
35
+ :param num_interval: Transition length ``D`` (``1 <= D < M``).
36
+ :return: 1-D serialized signal of length ``M*N + D*(N-1)``.
37
+ """
38
+ matrix_x = np.asarray(matrix_x, dtype=float)
39
+ if matrix_x.ndim != 2:
40
+ raise ValueError("matrix_x must be a 2-D array of shape (n_samples, n_channels)")
41
+
42
+ n_length, n_signal = matrix_x.shape
43
+ if n_signal < 1:
44
+ raise ValueError("matrix_x must contain at least one channel")
45
+ if n_signal == 1:
46
+ return matrix_x[:, 0].copy()
47
+
48
+ d = int(num_interval)
49
+ if d < 1 or d >= n_length:
50
+ raise ValueError(
51
+ f"num_interval must satisfy 1 <= D < M; got D={d}, M={n_length}"
52
+ )
53
+
54
+ # Heads of channels 2..N and tails of channels 1..N-1
55
+ matrix_a = matrix_x[:d, 1:]
56
+ matrix_b = matrix_x[-d:, :-1]
57
+
58
+ # Ramp weights a_i = i / (D+1), i = 1..D (endpoints 0 and 1 excluded)
59
+ vector_a = np.linspace(0.0, 1.0, d + 2)[1:-1].reshape(-1, 1)
60
+ vector_u = np.ones((n_signal - 1, 1))
61
+
62
+ # Transition: flip(head_{i+1}) ⊙ a + flip(tail_i) ⊙ flip(a)
63
+ matrix_t_a = np.flipud(matrix_a) * (vector_a @ vector_u.T)
64
+ matrix_t_b = np.flipud(matrix_b) * (np.flipud(vector_a) @ vector_u.T)
65
+ matrix_t = matrix_t_a + matrix_t_b
66
+
67
+ # Append a dummy zero column so Fortran flattening yields the desired layout
68
+ matrix_z = np.zeros((d, 1))
69
+ matrix_t = np.concatenate([matrix_t, matrix_z], axis=1)
70
+
71
+ # Stack transitions under the original block, then column-major vectorize
72
+ matrix_r = np.concatenate([matrix_x, matrix_t], axis=0)
73
+ matrix_r = matrix_r.flatten(order="F")
74
+ return matrix_r[:-d]
75
+
76
+
77
+ def deconcatenate_imfs(
78
+ matrix_r: np.ndarray,
79
+ num_interval: int,
80
+ num_signal: int,
81
+ num_length: Optional[int] = None,
82
+ ) -> np.ndarray:
83
+ """
84
+ Split serialized IMFs back into per-channel IMF tensors.
85
+
86
+ :param matrix_r: Serialized IMFs of shape ``(L, K)`` (or ``(L,)`` for one IMF).
87
+ :param num_interval: Transition length ``D`` used during concatenation.
88
+ :param num_signal: Number of original channels ``N``.
89
+ :param num_length: Original per-channel length ``M``. Inferred from ``L``
90
+ when omitted via ``L = M*N + D*(N-1)``.
91
+ :return: Array of shape ``(M, K, N)``.
92
+ """
93
+ matrix_r = np.asarray(matrix_r, dtype=float)
94
+ if matrix_r.ndim == 1:
95
+ matrix_r = matrix_r.reshape(-1, 1)
96
+ if matrix_r.ndim != 2:
97
+ raise ValueError("matrix_r must have shape (L, K)")
98
+
99
+ d = int(num_interval)
100
+ n_signal = int(num_signal)
101
+ if n_signal < 1:
102
+ raise ValueError("num_signal must be >= 1")
103
+
104
+ length_ser, num_mode = matrix_r.shape
105
+ if n_signal == 1:
106
+ m = length_ser if num_length is None else int(num_length)
107
+ if m != length_ser:
108
+ raise ValueError("num_length does not match serialized length for N=1")
109
+ return matrix_r.reshape(m, num_mode, 1)
110
+
111
+ if d < 1:
112
+ raise ValueError("num_interval must be >= 1")
113
+
114
+ if num_length is None:
115
+ # L = M*N + D*(N-1) ⇒ M = (L - D*(N-1)) / N
116
+ numer = length_ser - d * (n_signal - 1)
117
+ if numer % n_signal != 0:
118
+ raise ValueError(
119
+ "Cannot infer num_length from serialized IMF length; "
120
+ "please pass num_length explicitly"
121
+ )
122
+ m = numer // n_signal
123
+ else:
124
+ m = int(num_length)
125
+
126
+ expected = m * n_signal + d * (n_signal - 1)
127
+ if length_ser != expected:
128
+ raise ValueError(
129
+ f"Serialized length {length_ser} incompatible with "
130
+ f"M={m}, N={n_signal}, D={d} (expected {expected})"
131
+ )
132
+
133
+ # Pad D zeros, reshape Fortran-order to (M+D, N, K), drop transitions
134
+ matrix_z = np.zeros((d, num_mode))
135
+ matrix_pad = np.concatenate([matrix_r, matrix_z], axis=0)
136
+ matrix_imf = matrix_pad.reshape([-1, n_signal, num_mode], order="F")
137
+ matrix_imf = matrix_imf[:-d, :, :]
138
+ return matrix_imf.transpose((0, 2, 1))
139
+
140
+
141
+ def transition_bridge(
142
+ tail: np.ndarray, head: np.ndarray, num_interval: Optional[int] = None
143
+ ) -> np.ndarray:
144
+ """
145
+ Build the linear cross-fade bridge between two adjacent channels.
146
+
147
+ Useful for visualizing how SEMD constructs the transition that keeps the
148
+ mean envelope continuous across channel joins.
149
+
150
+ :param tail: Last ``D`` samples of channel ``i`` (or a longer suffix).
151
+ :param head: First ``D`` samples of channel ``i+1`` (or a longer prefix).
152
+ :param num_interval: Bridge length; defaults to ``min(len(tail), len(head))``.
153
+ :return: Bridge segment of length ``D``.
154
+ """
155
+ tail = np.asarray(tail, dtype=float).ravel()
156
+ head = np.asarray(head, dtype=float).ravel()
157
+ d = int(num_interval) if num_interval is not None else min(len(tail), len(head))
158
+ if d < 1 or d > len(tail) or d > len(head):
159
+ raise ValueError("num_interval exceeds available head/tail length")
160
+
161
+ a = np.linspace(0.0, 1.0, d + 2)[1:-1]
162
+ return np.flipud(head[:d]) * a + np.flipud(tail[-d:]) * np.flipud(a)
163
+
164
+
165
+ class SEMD(object):
166
+ """
167
+ Serial Empirical Mode Decomposition (Serial-EMD / SEMD)
168
+
169
+ Zhang et al., Information Sciences, 2021.
170
+
171
+ SEMD concatenates multi-channel signals into one long 1-D series with
172
+ smooth transition bridges, runs a standard 1-D EMD (or a compatible
173
+ variant), then splits the IMFs back to each original channel. This
174
+ avoids expensive multivariate envelope interpolation (MEMD / BEMD)
175
+ while reusing any existing univariate EMD backend.
176
+
177
+ Input layout (PySDKit convention)
178
+ ---------------------------------
179
+ - univariate: ``(seq_len,)``
180
+ - multivariate: ``(n_channels, seq_len)``
181
+
182
+ Output layout
183
+ -------------
184
+ - univariate: ``(K, seq_len)``
185
+ - multivariate: ``(K, seq_len, n_channels)``
186
+ """
187
+
188
+ def __init__(
189
+ self,
190
+ num_interval: Optional[int] = None,
191
+ interval_ratio: float = 0.2,
192
+ max_imfs: int = -1,
193
+ emd: Optional[EMD] = None,
194
+ **emd_kwargs,
195
+ ) -> None:
196
+ """
197
+ :param num_interval: Transition length ``D``. If ``None``, uses
198
+ ``max(1, round(interval_ratio * seq_len))``.
199
+ :param interval_ratio: Fraction of each channel length used for ``D``
200
+ when ``num_interval`` is not given (paper default ≈ 0.2).
201
+ :param max_imfs: Maximum number of IMFs forwarded to the EMD backend
202
+ (``-1`` means no hard limit).
203
+ :param emd: Optional pre-configured univariate decomposer. Must
204
+ expose ``fit_transform(signal, max_imfs=...)`` returning
205
+ ``(K, L)``. Defaults to :class:`pysdkit._emd.emd.EMD`.
206
+ :param emd_kwargs: Extra keyword arguments used when constructing the
207
+ default :class:`EMD` instance.
208
+ """
209
+ if num_interval is not None and int(num_interval) < 1:
210
+ raise ValueError("num_interval must be a positive integer or None")
211
+ if not (0.0 < float(interval_ratio) <= 1.0):
212
+ raise ValueError("interval_ratio must lie in (0, 1]")
213
+
214
+ self.num_interval = None if num_interval is None else int(num_interval)
215
+ self.interval_ratio = float(interval_ratio)
216
+ self.max_imfs = int(max_imfs)
217
+ self.emd = emd if emd is not None else EMD(max_imfs=self.max_imfs, **emd_kwargs)
218
+
219
+ # Cached intermediates for inspection / plotting
220
+ self.serialized_signal: Optional[np.ndarray] = None
221
+ self.serialized_imfs: Optional[np.ndarray] = None
222
+ self.imfs: Optional[np.ndarray] = None
223
+ self._last_shape: Optional[Tuple[int, int]] = None # (N, M)
224
+ self._last_D: Optional[int] = None
225
+
226
+ def __str__(self) -> str:
227
+ return "Serial Empirical Mode Decomposition (SEMD)"
228
+
229
+ def __call__(
230
+ self, signal: np.ndarray, max_imfs: Optional[int] = None
231
+ ) -> np.ndarray:
232
+ return self.fit_transform(signal=signal, max_imfs=max_imfs)
233
+
234
+ def resolve_interval(self, seq_len: int) -> int:
235
+ """Resolve the transition length ``D`` for a given channel length."""
236
+ if self.num_interval is not None:
237
+ d = self.num_interval
238
+ else:
239
+ d = max(1, int(round(self.interval_ratio * seq_len)))
240
+ if d >= seq_len:
241
+ d = max(1, seq_len - 1)
242
+ return d
243
+
244
+ def serialize(self, signal: np.ndarray) -> Tuple[np.ndarray, int]:
245
+ """
246
+ Convert a PySDKit multivariate array into the serialized 1-D series.
247
+
248
+ :param signal: ``(n_channels, seq_len)`` or ``(seq_len,)``
249
+ :return: ``(serialized_1d, D)``
250
+ """
251
+ x = np.asarray(signal, dtype=float)
252
+ if x.ndim == 1:
253
+ return x.copy(), 0
254
+ if x.ndim != 2:
255
+ raise ValueError(
256
+ "signal must be 1-D (seq_len,) or 2-D (n_channels, seq_len)"
257
+ )
258
+
259
+ n_channels, seq_len = x.shape
260
+ d = self.resolve_interval(seq_len)
261
+ # Paper / reference layout: time in rows, channels in columns
262
+ serialized = concatenate_signals(x.T, d)
263
+ return serialized, d
264
+
265
+ def fit_transform(
266
+ self, signal: np.ndarray, max_imfs: Optional[int] = None
267
+ ) -> np.ndarray:
268
+ """
269
+ Decompose uni-/multi-channel signals with Serial-EMD.
270
+
271
+ :param signal: ``(seq_len,)`` or ``(n_channels, seq_len)``
272
+ :param max_imfs: Optional override for the maximum number of IMFs
273
+ :return: IMFs with shape ``(K, seq_len)`` or ``(K, seq_len, n_channels)``
274
+ """
275
+ x = np.asarray(signal, dtype=float)
276
+ if max_imfs is None:
277
+ max_imfs = self.max_imfs
278
+ # Only forward a hard cap when it is positive; otherwise let the backend decide
279
+ emd_kwargs = {} if (max_imfs is None or int(max_imfs) < 0) else {"max_imfs": int(max_imfs)}
280
+
281
+ if x.ndim == 1:
282
+ imfs = self.emd.fit_transform(x, **emd_kwargs)
283
+ self.serialized_signal = x.copy()
284
+ self.serialized_imfs = np.asarray(imfs).T # (L, K)
285
+ self.imfs = np.asarray(imfs)
286
+ self._last_shape = (1, x.size)
287
+ self._last_D = 0
288
+ return self.imfs
289
+
290
+ if x.ndim != 2:
291
+ raise ValueError(
292
+ "signal must be 1-D (seq_len,) or 2-D (n_channels, seq_len)"
293
+ )
294
+
295
+ n_channels, seq_len = x.shape
296
+ if n_channels < 1 or seq_len < 2:
297
+ raise ValueError("Invalid multivariate signal shape")
298
+
299
+ d = self.resolve_interval(seq_len)
300
+ serialized = concatenate_signals(x.T, d)
301
+ self.serialized_signal = serialized
302
+ self._last_shape = (n_channels, seq_len)
303
+ self._last_D = d
304
+
305
+ # Univariate EMD on the long series → (K, L)
306
+ ser_imfs = np.asarray(self.emd.fit_transform(serialized, **emd_kwargs))
307
+ if ser_imfs.ndim != 2:
308
+ raise RuntimeError("EMD backend must return a 2-D IMF array (K, L)")
309
+ self.serialized_imfs = ser_imfs.T # (L, K)
310
+
311
+ # (M, K, N)
312
+ imfs_mkn = deconcatenate_imfs(
313
+ self.serialized_imfs,
314
+ num_interval=d,
315
+ num_signal=n_channels,
316
+ num_length=seq_len,
317
+ )
318
+ # PySDKit multivariate layout: (K, seq_len, n_channels)
319
+ self.imfs = np.transpose(imfs_mkn, (1, 0, 2))
320
+ return self.imfs
321
+
322
+ def reconstruct(self, imfs: Optional[np.ndarray] = None) -> np.ndarray:
323
+ """
324
+ Sum IMFs to reconstruct the original channel(s).
325
+
326
+ :param imfs: Optional IMF tensor; defaults to the last ``fit_transform`` result.
327
+ :return: ``(seq_len,)`` or ``(n_channels, seq_len)``
328
+ """
329
+ if imfs is None:
330
+ if self.imfs is None:
331
+ raise RuntimeError("Call fit_transform before reconstruct()")
332
+ imfs = self.imfs
333
+ imfs = np.asarray(imfs)
334
+ if imfs.ndim == 2:
335
+ return np.sum(imfs, axis=0)
336
+ if imfs.ndim == 3:
337
+ return np.sum(imfs, axis=0).T # (N, M)
338
+ raise ValueError("imfs must be 2-D or 3-D")
@@ -0,0 +1,204 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2025/02/01 22:30:40
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Optional, Tuple, Union
11
+
12
+ import numpy as np
13
+
14
+ from pysdkit._faemd.extrema import extrema
15
+ from pysdkit._faemd.filter import filter_size1D, immse, mean_envelope_1d
16
+
17
+
18
+ class FAEMD(object):
19
+ """
20
+ Fast and Adaptive Empirical Mode Decomposition (1-D / multivariate 1-D)
21
+
22
+ Thirumalaisamy, Mruthun R., and Phillip J. Ansell.
23
+ “Fast and Adaptive Empirical Mode Decomposition for Multidimensional,
24
+ Multivariate Signals.” IEEE Signal Processing Letters, 25(10):1550–1554, 2018.
25
+
26
+ MATLAB: https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd
27
+
28
+ Replaces cubic-spline envelopes with order-statistics filters whose window
29
+ length is adapted from extrema spacings (Bhuiyan / FA-MVEMD).
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ max_imfs: int = 3,
35
+ tol: Optional[float] = None,
36
+ window_type: int = 0,
37
+ ) -> None:
38
+ """
39
+ :param max_imfs: Number of modes returned (includes the final residue)
40
+ :param tol: Sifting MSE tolerance; default ``min(RMS) * 0.001``
41
+ :param window_type: Adaptive window selector in ``{0,...,6}``
42
+ (MATLAB ``type`` 1..7). Default ``0`` → smallest spacing ``d1``.
43
+ """
44
+ if not isinstance(max_imfs, (int, np.integer)) or max_imfs < 1:
45
+ raise ValueError("`max_imfs` must be a positive integer")
46
+ if window_type not in range(7):
47
+ raise ValueError("`window_type` must be an integer in 0..6")
48
+
49
+ self.max_imfs = int(max_imfs)
50
+ self.tol = tol
51
+ self.window_type = int(window_type)
52
+
53
+ self.imfs: Optional[np.ndarray] = None
54
+ self.residue: Optional[np.ndarray] = None
55
+
56
+ def __call__(
57
+ self,
58
+ signal: np.ndarray,
59
+ return_all: bool = False,
60
+ max_imfs: Optional[int] = None,
61
+ ):
62
+ return self.fit_transform(
63
+ signal=signal, return_all=return_all, max_imfs=max_imfs
64
+ )
65
+
66
+ def __str__(self) -> str:
67
+ return "Fast and Adaptive Empirical Mode Decomposition (FAEMD)"
68
+
69
+ def _get_tol(self, signal: np.ndarray) -> float:
70
+ if self.tol is not None:
71
+ return float(self.tol)
72
+ # MATLAB EMD1DNV: min(rms(u)) * 0.001
73
+ rms = np.sqrt(np.mean(np.asarray(signal, dtype=float) ** 2, axis=0))
74
+ return float(np.min(rms) * 0.001)
75
+
76
+ def sift(self, h: np.ndarray, w_sz: float) -> np.ndarray:
77
+ """One FA-EMD sifting step on a single channel."""
78
+ mean_env = mean_envelope_1d(h, int(w_sz))
79
+ return h - mean_env
80
+
81
+ def fit_transform(
82
+ self,
83
+ signal: np.ndarray,
84
+ return_all: bool = False,
85
+ max_imfs: Optional[int] = None,
86
+ ) -> Union[Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray], np.ndarray]:
87
+ """
88
+ Decompose a uni-/multivariate 1-D signal.
89
+
90
+ :param signal: ``(seq_len,)`` or ``(n_channels, seq_len)``
91
+ :param return_all: Also return residue, window table and sift counts
92
+ :param max_imfs: Override for the number of returned modes
93
+ :return: IMFs of shape ``(K, seq_len)`` or ``(K, seq_len, n_channels)``
94
+ (the last mode is the residue)
95
+ """
96
+ data, inputs_shape = check_inputs(signal)
97
+ seq_len, num_vars = data.shape
98
+ max_imfs = self.max_imfs if max_imfs is None else int(max_imfs)
99
+ if max_imfs < 1:
100
+ raise ValueError("max_imfs must be a positive integer")
101
+
102
+ imfs = np.zeros((seq_len, num_vars, max_imfs), dtype=float)
103
+ h1 = np.zeros((seq_len, num_vars), dtype=float)
104
+ mse = np.zeros(num_vars, dtype=float)
105
+ windows = np.zeros((7, max_imfs), dtype=float)
106
+ sift_count = np.zeros(max_imfs, dtype=int)
107
+
108
+ residue = data.copy()
109
+ tol = self._get_tol(data)
110
+ imf_idx = 0
111
+
112
+ # Extract up to max_imfs-1 oscillatory modes; last slot stores residue
113
+ while imf_idx < max_imfs - 1:
114
+ h = residue.copy()
115
+ combined = np.sum(h / np.sqrt(num_vars), axis=1)
116
+
117
+ ext = extrema(combined)
118
+ if ext[0] is None:
119
+ break
120
+ maxima, max_pos, minima, min_pos = ext
121
+ max_pos = np.atleast_1d(np.asarray(max_pos, dtype=float))
122
+ min_pos = np.atleast_1d(np.asarray(min_pos, dtype=float))
123
+ if max_pos.size < 3 or min_pos.size < 3:
124
+ break
125
+
126
+ windows[:, imf_idx] = filter_size1D(
127
+ imax=max_pos, imin=min_pos, window_type=self.window_type
128
+ )
129
+ w_sz = int(windows[self.window_type, imf_idx])
130
+
131
+ sift_stop = False
132
+ while not sift_stop:
133
+ sift_count[imf_idx] += 1
134
+ for i in range(num_vars):
135
+ h1[:, i] = self.sift(h[:, i], w_sz=w_sz)
136
+ mse[i] = immse(h1[:, i], h[:, i])
137
+
138
+ if np.all(mse < tol) and sift_count[imf_idx] != 1:
139
+ sift_stop = True
140
+ h = h1.copy()
141
+
142
+ imfs[:, :, imf_idx] = h
143
+ residue = residue - h
144
+ imf_idx += 1
145
+
146
+ if np.any(sift_count >= 5):
147
+ selected = windows[self.window_type, :imf_idx]
148
+ if selected.size >= 2 and np.any(np.diff(selected) <= 0):
149
+ print(
150
+ "Decomposition may be oversifted; "
151
+ "filter window size does not increase monotonically."
152
+ )
153
+
154
+ imfs[:, :, -1] = residue
155
+ out = check_outputs(imfs, inputs_shape)
156
+
157
+ self.imfs = out[:-1] if out.ndim >= 2 else out
158
+ self.residue = residue.T if len(inputs_shape) == 2 else residue[:, 0]
159
+
160
+ if return_all:
161
+ return out, self.residue, windows, sift_count
162
+ return out
163
+
164
+ def get_imfs_and_residue(self) -> Tuple[np.ndarray, np.ndarray]:
165
+ if self.imfs is None or self.residue is None:
166
+ raise ValueError(
167
+ "No IMF found. Please run `fit_transform` method first."
168
+ )
169
+ return self.imfs, self.residue
170
+
171
+
172
+ def check_inputs(signal: np.ndarray) -> Tuple[np.ndarray, Tuple]:
173
+ """Normalize to internal layout ``(seq_len, n_channels)``."""
174
+ signal = np.asarray(signal, dtype=float)
175
+ inputs_shape = signal.shape
176
+ if signal.ndim == 1:
177
+ signal = signal[np.newaxis, :]
178
+ elif signal.ndim == 2:
179
+ pass
180
+ else:
181
+ raise ValueError(
182
+ "signal must have shape [seq_len] or [n_channels, seq_len]"
183
+ )
184
+ return signal.T, inputs_shape
185
+
186
+
187
+ def check_outputs(imfs: np.ndarray, inputs_shape: Tuple) -> np.ndarray:
188
+ """Map ``(seq_len, n_channels, K)`` back to PySDKit layout."""
189
+ if len(inputs_shape) == 1:
190
+ return np.transpose(imfs[:, 0, :], (1, 0))
191
+ return np.transpose(imfs, (2, 0, 1))
192
+
193
+
194
+ if __name__ == "__main__":
195
+ from matplotlib import pyplot as plt
196
+
197
+ from pysdkit.data import test_emd, test_multivariate_signal
198
+ from pysdkit.plot import plot_IMFs
199
+
200
+ faemd = FAEMD(max_imfs=3)
201
+ _, signal = test_emd()
202
+ imfs = faemd.fit_transform(signal)
203
+ plot_IMFs(signal, imfs)
204
+ plt.show()