PySDKit 0.4.36__tar.gz → 0.4.38__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.36 → pysdkit-0.4.38}/PKG-INFO +3 -3
  2. {pysdkit-0.4.36 → pysdkit-0.4.38}/PySDKit.egg-info/PKG-INFO +3 -3
  3. {pysdkit-0.4.36 → pysdkit-0.4.38}/PySDKit.egg-info/SOURCES.txt +4 -0
  4. {pysdkit-0.4.36 → pysdkit-0.4.38}/README.md +2 -2
  5. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/__init__.py +12 -1
  6. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/semd.py +8 -2
  7. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd2d/__init__.py +2 -0
  8. pysdkit-0.4.38/pysdkit/_emd2d/bmemd.py +413 -0
  9. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_faemd/faemd.py +2 -6
  10. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_faemd/faemd2d.py +2 -6
  11. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_faemd/faemd3d.py +2 -6
  12. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_faemd/filter.py +4 -12
  13. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_gdmd/__init__.py +21 -1
  14. pysdkit-0.4.38/pysdkit/_gdmd/vgnmd.py +704 -0
  15. pysdkit-0.4.38/pysdkit/tests/test_bmemd.py +102 -0
  16. pysdkit-0.4.38/pysdkit/tests/test_ceemdan.py +202 -0
  17. pysdkit-0.4.38/pysdkit/tests/test_eemd.py +175 -0
  18. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_faemd.py +0 -1
  19. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_mstl.py +8 -4
  20. pysdkit-0.4.38/pysdkit/tests/test_vgnmd.py +161 -0
  21. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tsa/_mstl.py +3 -1
  22. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tsa/_stl.py +11 -4
  23. pysdkit-0.4.36/pysdkit/_emd2d/bmemd.py +0 -24
  24. pysdkit-0.4.36/pysdkit/tests/test_ceemdan.py +0 -286
  25. {pysdkit-0.4.36 → pysdkit-0.4.38}/LICENSE +0 -0
  26. {pysdkit-0.4.36 → pysdkit-0.4.38}/PySDKit.egg-info/dependency_links.txt +0 -0
  27. {pysdkit-0.4.36 → pysdkit-0.4.38}/PySDKit.egg-info/requires.txt +0 -0
  28. {pysdkit-0.4.36 → pysdkit-0.4.38}/PySDKit.egg-info/top_level.txt +0 -0
  29. {pysdkit-0.4.36 → pysdkit-0.4.38}/pyproject.toml +0 -0
  30. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_alif/__init__.py +0 -0
  31. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_alif/alif.py +0 -0
  32. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_alif/data/__init__.py +0 -0
  33. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_alif/data/prefixed_double_filter.npy +0 -0
  34. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_alif/iterative_filtering.py +0 -0
  35. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_apmd/__init__.py +0 -0
  36. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_apmd/apmd.py +0 -0
  37. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/__init__.py +0 -0
  38. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/_find_extrema.py +0 -0
  39. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/_prepare_points.py +0 -0
  40. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/_splines.py +0 -0
  41. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/ceemdan.py +0 -0
  42. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/eemd.py +0 -0
  43. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/efd.py +0 -0
  44. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/emd.py +0 -0
  45. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/hht/__init__.py +0 -0
  46. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/hht/frequency.py +0 -0
  47. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/hht/hht.py +0 -0
  48. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/memd.py +0 -0
  49. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/remd.py +0 -0
  50. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd/tvf_emd.py +0 -0
  51. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd2d/bemd.py +0 -0
  52. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_emd2d/emd2d.py +0 -0
  53. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_esmd/__init__.py +0 -0
  54. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_esmd/esmd.py +0 -0
  55. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_ewt/__init__.py +0 -0
  56. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_ewt/ewt.py +0 -0
  57. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_ewt/ewt2d.py +0 -0
  58. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_faemd/__init__.py +0 -0
  59. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_faemd/extrema.py +0 -0
  60. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_fmd/__init__.py +0 -0
  61. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_fmd/fmd.py +0 -0
  62. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_fmd/nfmd.py +0 -0
  63. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_gdmd/gdmd.py +0 -0
  64. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_hvd/__init__.py +0 -0
  65. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_hvd/hvd.py +0 -0
  66. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_itd/__init__.py +0 -0
  67. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_itd/itd.py +0 -0
  68. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_jmd/__init__.py +0 -0
  69. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_jmd/jmd.py +0 -0
  70. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_jmd/mjmd.py +0 -0
  71. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_jmd/sjmd.py +0 -0
  72. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_lmd/__init__.py +0 -0
  73. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_lmd/lmd.py +0 -0
  74. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_lmd/rlmd.py +0 -0
  75. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_osd/__init__.py +0 -0
  76. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_osd/swd.py +0 -0
  77. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_ssa/__init__.py +0 -0
  78. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_ssa/ssa.py +0 -0
  79. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/__init__.py +0 -0
  80. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/acmd.py +0 -0
  81. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/avmd.py +0 -0
  82. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/ba_acmd.py +0 -0
  83. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/base.py +0 -0
  84. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/mvmd.py +0 -0
  85. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/ovmd.py +0 -0
  86. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/stvmd.py +0 -0
  87. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/svmd.py +0 -0
  88. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/vmd_c.py +0 -0
  89. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/vmd_f.py +0 -0
  90. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd/vme.py +0 -0
  91. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd2d/__init__.py +0 -0
  92. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd2d/cvmd2d.py +0 -0
  93. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vmd2d/vmd2d.py +0 -0
  94. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vncmd/__init__.py +0 -0
  95. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vncmd/avncmd.py +0 -0
  96. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vncmd/incmd.py +0 -0
  97. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/_vncmd/vncmd.py +0 -0
  98. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/data/__init__.py +0 -0
  99. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/data/_add_noise.py +0 -0
  100. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/data/_cube.py +0 -0
  101. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/data/_generator.py +0 -0
  102. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/data/_image.py +0 -0
  103. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/data/_models.py +0 -0
  104. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/data/_test_univariate.py +0 -0
  105. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/data/_time_series.py +0 -0
  106. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/data/texture.txt +0 -0
  107. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/entropy/__init__.py +0 -0
  108. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
  109. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/entropy/_permutation_entropy.py +0 -0
  110. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/entropy/_sample_entropy.py +0 -0
  111. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/models/__init__.py +0 -0
  112. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/models/_base.py +0 -0
  113. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/models/_kmeans.py +0 -0
  114. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/models/_knn.py +0 -0
  115. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/models/_pca.py +0 -0
  116. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/plot/__init__.py +0 -0
  117. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/plot/_fourier_spectra.py +0 -0
  118. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/plot/_functions.py +0 -0
  119. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/plot/_plot_images.py +0 -0
  120. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/plot/_plot_imfs.py +0 -0
  121. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/plot/_plot_signal.py +0 -0
  122. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/__init__.py +0 -0
  123. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/data/__init__.py +0 -0
  124. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/data/test_generator_cube.py +0 -0
  125. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/data/test_generator_image.py +0 -0
  126. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/data/test_generator_signal.py +0 -0
  127. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
  128. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/models/__init__.py +0 -0
  129. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/models/test_knn.py +0 -0
  130. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/models/test_pca.py +0 -0
  131. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/run_all.py +0 -0
  132. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_alif.py +0 -0
  133. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_apmd.py +0 -0
  134. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_avncmd.py +0 -0
  135. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_emd.py +0 -0
  136. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_ewt.py +0 -0
  137. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_ewt2d.py +0 -0
  138. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_faemd2d.py +0 -0
  139. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_faemd3d.py +0 -0
  140. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_gdmd.py +0 -0
  141. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_itd.py +0 -0
  142. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_jmd.py +0 -0
  143. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_lmd.py +0 -0
  144. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_moving_decomp.py +0 -0
  145. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_ovmd.py +0 -0
  146. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_rlmd.py +0 -0
  147. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_semd.py +0 -0
  148. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_sjmd.py +0 -0
  149. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_ssa.py +0 -0
  150. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_stl.py +0 -0
  151. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_stvmd.py +0 -0
  152. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_svmd.py +0 -0
  153. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_swd.py +0 -0
  154. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_vmd.py +0 -0
  155. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_vmd2d.py +0 -0
  156. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tests/test_vncmd.py +0 -0
  157. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tsa/__init__.py +0 -0
  158. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tsa/_dtw.py +0 -0
  159. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/tsa/_moving_decomp.py +0 -0
  160. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/__init__.py +0 -0
  161. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_cite.py +0 -0
  162. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_correlation.py +0 -0
  163. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_diagnalization.py +0 -0
  164. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_differ.py +0 -0
  165. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_fft.py +0 -0
  166. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_function.py +0 -0
  167. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_hilbert.py +0 -0
  168. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_instantaneous.py +0 -0
  169. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_kernel_matrix.py +0 -0
  170. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_mirror.py +0 -0
  171. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_process.py +0 -0
  172. {pysdkit-0.4.36 → pysdkit-0.4.38}/pysdkit/utils/_smooth1d.py +0 -0
  173. {pysdkit-0.4.36 → pysdkit-0.4.38}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySDKit
3
- Version: 0.4.36
3
+ Version: 0.4.38
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>
@@ -155,8 +155,8 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
155
155
  | [`TVF_EMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/tvf_emd.py) (Time Varying Filter Based EMD) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0165168417301135) | [[code]](https://github.com/stfbnc/pytvfemd/tree/master) | ✔️ |
156
156
  | [`EFD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/efd.py) (Empirical Fourier Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/abs/pii/S0888327021005355) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/97747-empirical-fourier-decomposition-efd) | ✔️ |
157
157
  | [`FAEMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd.py) (Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✔️ |
158
- | [`FAEMD2D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd2d.py) (Two-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✖️ |
159
- | [`FAEMD3D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd3d.py) (Three-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✖️ |
158
+ | [`FAEMD2D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd2d.py) (Two-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✔️ |
159
+ | [`FAEMD3D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd3d.py) (Three-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✔️ |
160
160
  | [`HVD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_hvd/hvd.py) (Hilbert Vibration Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X06001556) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/178804-hilbert-vibration-decomposition?s_tid=FX_rc1_behav) | ✔️ |
161
161
  | [`ITD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_itd/itd.py) (Intrinsic Time-Scale Decomposition) | [[paper]](https://royalsocietypublishing.org/doi/10.1098/rspa.2006.1761) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/69380-intrinsic-time-scale-decomposition-itd) | ✔️ |
162
162
  | [`ALIF`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_alif/alif.py) (Adaptive Local Iterative Filtering) | [[paper]](https://arxiv.org/abs/1411.6051) | [[code]](https://github.com/Cicone/ALIF) | ✔️ |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySDKit
3
- Version: 0.4.36
3
+ Version: 0.4.38
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>
@@ -155,8 +155,8 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
155
155
  | [`TVF_EMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/tvf_emd.py) (Time Varying Filter Based EMD) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0165168417301135) | [[code]](https://github.com/stfbnc/pytvfemd/tree/master) | ✔️ |
156
156
  | [`EFD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/efd.py) (Empirical Fourier Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/abs/pii/S0888327021005355) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/97747-empirical-fourier-decomposition-efd) | ✔️ |
157
157
  | [`FAEMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd.py) (Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✔️ |
158
- | [`FAEMD2D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd2d.py) (Two-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✖️ |
159
- | [`FAEMD3D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd3d.py) (Three-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✖️ |
158
+ | [`FAEMD2D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd2d.py) (Two-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✔️ |
159
+ | [`FAEMD3D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd3d.py) (Three-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✔️ |
160
160
  | [`HVD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_hvd/hvd.py) (Hilbert Vibration Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X06001556) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/178804-hilbert-vibration-decomposition?s_tid=FX_rc1_behav) | ✔️ |
161
161
  | [`ITD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_itd/itd.py) (Intrinsic Time-Scale Decomposition) | [[paper]](https://royalsocietypublishing.org/doi/10.1098/rspa.2006.1761) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/69380-intrinsic-time-scale-decomposition-itd) | ✔️ |
162
162
  | [`ALIF`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_alif/alif.py) (Adaptive Local Iterative Filtering) | [[paper]](https://arxiv.org/abs/1411.6051) | [[code]](https://github.com/Cicone/ALIF) | ✔️ |
@@ -49,6 +49,7 @@ pysdkit/_fmd/fmd.py
49
49
  pysdkit/_fmd/nfmd.py
50
50
  pysdkit/_gdmd/__init__.py
51
51
  pysdkit/_gdmd/gdmd.py
52
+ pysdkit/_gdmd/vgnmd.py
52
53
  pysdkit/_hvd/__init__.py
53
54
  pysdkit/_hvd/hvd.py
54
55
  pysdkit/_itd/__init__.py
@@ -112,7 +113,9 @@ pysdkit/tests/run_all.py
112
113
  pysdkit/tests/test_alif.py
113
114
  pysdkit/tests/test_apmd.py
114
115
  pysdkit/tests/test_avncmd.py
116
+ pysdkit/tests/test_bmemd.py
115
117
  pysdkit/tests/test_ceemdan.py
118
+ pysdkit/tests/test_eemd.py
116
119
  pysdkit/tests/test_emd.py
117
120
  pysdkit/tests/test_ewt.py
118
121
  pysdkit/tests/test_ewt2d.py
@@ -134,6 +137,7 @@ pysdkit/tests/test_stl.py
134
137
  pysdkit/tests/test_stvmd.py
135
138
  pysdkit/tests/test_svmd.py
136
139
  pysdkit/tests/test_swd.py
140
+ pysdkit/tests/test_vgnmd.py
137
141
  pysdkit/tests/test_vmd.py
138
142
  pysdkit/tests/test_vmd2d.py
139
143
  pysdkit/tests/test_vncmd.py
@@ -101,8 +101,8 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
101
101
  | [`TVF_EMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/tvf_emd.py) (Time Varying Filter Based EMD) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0165168417301135) | [[code]](https://github.com/stfbnc/pytvfemd/tree/master) | ✔️ |
102
102
  | [`EFD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/efd.py) (Empirical Fourier Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/abs/pii/S0888327021005355) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/97747-empirical-fourier-decomposition-efd) | ✔️ |
103
103
  | [`FAEMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd.py) (Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✔️ |
104
- | [`FAEMD2D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd2d.py) (Two-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✖️ |
105
- | [`FAEMD3D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd3d.py) (Three-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✖️ |
104
+ | [`FAEMD2D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd2d.py) (Two-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✔️ |
105
+ | [`FAEMD3D`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_faemd/faemd3d.py) (Three-Dimensional Fast and Adaptive EMD) | [[paper]](https://ieeexplore.ieee.org/document/8447300) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/71270-fast-and-adaptive-multivariate-and-multidimensional-emd) | ✔️ |
106
106
  | [`HVD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_hvd/hvd.py) (Hilbert Vibration Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X06001556) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/178804-hilbert-vibration-decomposition?s_tid=FX_rc1_behav) | ✔️ |
107
107
  | [`ITD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_itd/itd.py) (Intrinsic Time-Scale Decomposition) | [[paper]](https://royalsocietypublishing.org/doi/10.1098/rspa.2006.1761) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/69380-intrinsic-time-scale-decomposition-itd) | ✔️ |
108
108
  | [`ALIF`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_alif/alif.py) (Adaptive Local Iterative Filtering) | [[paper]](https://arxiv.org/abs/1411.6051) | [[code]](https://github.com/Cicone/ALIF) | ✔️ |
@@ -2,7 +2,7 @@
2
2
  A Python library for signal decomposition algorithms.
3
3
  """
4
4
 
5
- __version__ = "0.4.36"
5
+ __version__ = "0.4.38"
6
6
 
7
7
  # Empirical Mode Decomposition
8
8
  from ._emd import EMD
@@ -34,6 +34,9 @@ from ._faemd import FAEMD, FAEMD2D, FAEMD3D
34
34
  # Empirical Mode Decomposition 2D for images
35
35
  from ._emd2d import EMD2D
36
36
 
37
+ # Bidimensional Multivariate Empirical Mode Decomposition
38
+ from ._emd2d import BMEMD
39
+
37
40
  # Hilbert Vibration Decomposition
38
41
  from ._hvd import HVD
39
42
 
@@ -55,6 +58,9 @@ from ._osd import SWD, swd
55
58
  # Generalized Dispersion Mode Decomposition
56
59
  from ._gdmd import GDMD, gdmd
57
60
 
61
+ # Variational Generalized Nonlinear Mode Decomposition
62
+ from ._gdmd import VGNMD, vgnmd
63
+
58
64
  # Variational Mode Decomposition
59
65
  from ._vmd import vmd, VMD
60
66
 
@@ -163,6 +169,7 @@ Fast and Adaptive Empirical Mode Decomposition | FAEMD
163
169
  Bidimensional FAEMD | FAEMD2D
164
170
  Tridimensional FAEMD | FAEMD3D
165
171
  Empirical Mode Decomposition 2D for images | EMD2D
172
+ Bidimensional Multivariate EMD | BMEMD
166
173
  Hilbert Vibration Decomposition | HVD
167
174
  Intrinsic Time-Scale Decomposition | ITD
168
175
  Local Mean Decomposition | LMD
@@ -170,6 +177,7 @@ Robust Local Mean Decomposition | RLMD
170
177
  Singular Spectral Analysis | SSA
171
178
  Swarm Decomposition | SWD
172
179
  Generalized Dispersion Mode Decomposition | GDMD
180
+ Variational Generalized Nonlinear Mode Dec. | VGNMD
173
181
  Variational Mode Decomposition | VMD
174
182
  Multivariate Variational Mode Decomposition | MVMD
175
183
  Variational Mode Extraction | VME
@@ -211,6 +219,7 @@ __all__ = [
211
219
  "FAEMD2D",
212
220
  "FAEMD3D",
213
221
  "EMD2D",
222
+ "BMEMD",
214
223
  "HVD",
215
224
  "ITD",
216
225
  "LMD",
@@ -220,6 +229,8 @@ __all__ = [
220
229
  "swd",
221
230
  "GDMD",
222
231
  "gdmd",
232
+ "VGNMD",
233
+ "vgnmd",
223
234
  "vmd",
224
235
  "VMD",
225
236
  "ACMD",
@@ -37,7 +37,9 @@ def concatenate_signals(matrix_x: np.ndarray, num_interval: int) -> np.ndarray:
37
37
  """
38
38
  matrix_x = np.asarray(matrix_x, dtype=float)
39
39
  if matrix_x.ndim != 2:
40
- raise ValueError("matrix_x must be a 2-D array of shape (n_samples, n_channels)")
40
+ raise ValueError(
41
+ "matrix_x must be a 2-D array of shape (n_samples, n_channels)"
42
+ )
41
43
 
42
44
  n_length, n_signal = matrix_x.shape
43
45
  if n_signal < 1:
@@ -276,7 +278,11 @@ class SEMD(object):
276
278
  if max_imfs is None:
277
279
  max_imfs = self.max_imfs
278
280
  # 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)}
281
+ emd_kwargs = (
282
+ {}
283
+ if (max_imfs is None or int(max_imfs) < 0)
284
+ else {"max_imfs": int(max_imfs)}
285
+ )
280
286
 
281
287
  if x.ndim == 1:
282
288
  imfs = self.emd.fit_transform(x, **emd_kwargs)
@@ -6,3 +6,5 @@ Created on 2025/02/03 18:36:49
6
6
  """
7
7
 
8
8
  from .emd2d import EMD2D
9
+
10
+ from .bmemd import BMEMD, local_var_img, fuse_images
@@ -0,0 +1,413 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2025/02/05 13:18:18
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+
7
+ Bidimensional Multivariate Empirical Mode Decomposition (BMEMD)
8
+
9
+ Xia, Y., Zhang, B., Pei, W., and Mandic, D. P. (2019).
10
+ Bidimensional Multivariate Empirical Mode Decomposition with Applications
11
+ in Multi-Scale Image Fusion. IEEE Access, 7:114261–114270.
12
+
13
+ MATLAB reference:
14
+ https://github.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import List, Optional, Sequence, Tuple, Union
20
+
21
+ import numpy as np
22
+ from scipy.interpolate import CloughTocher2DInterpolator, NearestNDInterpolator
23
+ from scipy.ndimage import maximum_filter, minimum_filter, uniform_filter
24
+
25
+ from pysdkit._emd.memd import hamm, nth_prime
26
+
27
+
28
+ class BMEMD(object):
29
+ """
30
+ Bidimensional Multivariate Empirical Mode Decomposition (BMEMD)
31
+
32
+ Projects a multi-channel image onto direction vectors on the
33
+ ``(n-1)``-sphere, finds 2-D extrema of each projected surface, builds
34
+ multivariate envelopes by Delaunay-based surface interpolation, and
35
+ sifts until a MEMD-style stop criterion is met. All channels share the
36
+ same BIMF count, which enables multi-scale image fusion.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ n_dir: int = 8,
42
+ max_imfs: int = 4,
43
+ stop_crit: str = "stop",
44
+ stop_vec: Optional[Sequence[float]] = None,
45
+ stop_cnt: int = 2,
46
+ max_iter: int = 1000,
47
+ max_sift: int = 50,
48
+ ) -> None:
49
+ """
50
+ :param n_dir: Number of projection directions (``>= 6``; MATLAB default 8)
51
+ :param max_imfs: Maximum number of oscillatory BIMFs before the residue
52
+ :param stop_crit: ``"stop"`` (sd / sd2 / tol) or ``"fix_h"``
53
+ :param stop_vec: ``[sd, sd2, tol]`` when ``stop_crit="stop"``;
54
+ default ``[0.01, 0.1, 0.01]`` (MATLAB BMEMD default)
55
+ :param stop_cnt: Fixed sifting count when ``stop_crit="fix_h"``
56
+ :param max_iter: Hard cap on outer BIMF extraction iterations
57
+ :param max_sift: Hard cap on inner sifting iterations per BIMF
58
+ """
59
+ if not isinstance(n_dir, (int, np.integer)) or n_dir < 6:
60
+ raise ValueError("n_dir must be an integer >= 6")
61
+ if not isinstance(max_imfs, (int, np.integer)) or max_imfs < 1:
62
+ raise ValueError("max_imfs must be a positive integer")
63
+ if stop_crit not in ("stop", "fix_h"):
64
+ raise ValueError("stop_crit must be 'stop' or 'fix_h'")
65
+
66
+ self.n_dir = int(n_dir)
67
+ self.max_imfs = int(max_imfs)
68
+ self.stop_crit = stop_crit
69
+ if stop_vec is None:
70
+ stop_vec = (0.01, 0.1, 0.01)
71
+ if len(stop_vec) != 3:
72
+ raise ValueError("stop_vec must contain three elements [sd, sd2, tol]")
73
+ self.sd, self.sd2, self.tol = map(float, stop_vec)
74
+ self.stop_cnt = int(stop_cnt)
75
+ self.max_iter = int(max_iter)
76
+ self.max_sift = int(max_sift)
77
+
78
+ self.imfs: Optional[np.ndarray] = None
79
+ self.residue: Optional[np.ndarray] = None
80
+
81
+ def __str__(self) -> str:
82
+ return "Bidimensional Multivariate Empirical Mode Decomposition (BMEMD)"
83
+
84
+ def __call__(
85
+ self, images: np.ndarray, max_imfs: Optional[int] = None
86
+ ) -> np.ndarray:
87
+ return self.fit_transform(images, max_imfs=max_imfs)
88
+
89
+ # ------------------------------------------------------------------ #
90
+ # Public API
91
+ # ------------------------------------------------------------------ #
92
+ def fit_transform(
93
+ self, images: np.ndarray, max_imfs: Optional[int] = None
94
+ ) -> np.ndarray:
95
+ """
96
+ Decompose a multi-channel image stack.
97
+
98
+ :param images: Array of shape ``(n_channels, H, W)`` with
99
+ ``2 <= n_channels <= 16``
100
+ :param max_imfs: Optional override for the number of oscillatory BIMFs
101
+ :return: ``(K, n_channels, H, W)`` where the last slice is the residue
102
+ """
103
+ x = self._check_input(images)
104
+ n_ch, height, width = x.shape
105
+ max_imfs = self.max_imfs if max_imfs is None else int(max_imfs)
106
+
107
+ directions = self._direction_vectors(n_ch)
108
+ residue = x.astype(float, copy=True)
109
+ modes: List[np.ndarray] = []
110
+
111
+ for _ in range(max_imfs):
112
+ if self._stop_emd(residue, directions):
113
+ break
114
+
115
+ mode = residue.copy()
116
+ sift_i = 0
117
+ n_h = 0 # consecutive OK counts for fix_h
118
+
119
+ while sift_i < self.max_sift:
120
+ sift_i += 1
121
+ try:
122
+ env_mean, nem, amp = self._envelope_mean(mode, directions)
123
+ except Exception:
124
+ env_mean = np.zeros_like(mode)
125
+ break
126
+
127
+ if self.stop_crit == "stop":
128
+ if self._stop_sifting(env_mean, amp, nem):
129
+ break
130
+ else: # fix_h
131
+ # MEMD-style: count successive siftings with enough extrema
132
+ if nem > 9:
133
+ n_h += 1
134
+ else:
135
+ n_h = 0
136
+ if n_h >= self.stop_cnt:
137
+ break
138
+
139
+ mode = mode - env_mean
140
+
141
+ modes.append(mode)
142
+ residue = residue - mode
143
+ if sift_i >= self.max_sift:
144
+ break
145
+
146
+ modes.append(residue)
147
+ imfs = np.stack(modes, axis=0)
148
+ self.imfs = imfs[:-1]
149
+ self.residue = residue
150
+ return imfs
151
+
152
+ def fuse(
153
+ self,
154
+ images: np.ndarray,
155
+ imfs: Optional[np.ndarray] = None,
156
+ var_window: int = 5,
157
+ ) -> np.ndarray:
158
+ """
159
+ Multi-scale image fusion via local-variance weights (Xia et al.).
160
+
161
+ For each oscillatory BIMF, channel weights are proportional to the
162
+ local squared deviation from a moving mean (MATLAB ``local_var_img``).
163
+ The residue is fused by intensity proportions.
164
+
165
+ :param images: ``(n_channels, H, W)`` input stack (used if ``imfs`` is None)
166
+ :param imfs: Optional precomputed BIMFs ``(K, n_channels, H, W)``
167
+ :param var_window: Odd window size for local variance (default 5)
168
+ :return: Fused grayscale image ``(H, W)``
169
+ """
170
+ if imfs is None:
171
+ imfs = self.fit_transform(images)
172
+ imfs = np.asarray(imfs, dtype=float)
173
+ if imfs.ndim != 4:
174
+ raise ValueError("imfs must have shape (K, n_channels, H, W)")
175
+
176
+ k_modes, n_ch, height, width = imfs.shape
177
+ fused = np.zeros((height, width), dtype=float)
178
+
179
+ for q in range(k_modes):
180
+ bimf = imfs[q] # (C, H, W)
181
+ if q < k_modes - 1:
182
+ var = local_var_img(bimf, var_window) # (C, H, W)
183
+ denom = np.sum(var, axis=0, keepdims=True) + 1e-12
184
+ weights = var / denom
185
+ else:
186
+ denom = np.sum(bimf, axis=0, keepdims=True)
187
+ # Avoid division by zero on flat residue
188
+ denom = np.where(np.abs(denom) < 1e-12, 1.0, denom)
189
+ weights = bimf / denom
190
+ fused = fused + np.sum(bimf * weights, axis=0)
191
+
192
+ return fused
193
+
194
+ # ------------------------------------------------------------------ #
195
+ # Internals
196
+ # ------------------------------------------------------------------ #
197
+ @staticmethod
198
+ def _check_input(images: np.ndarray) -> np.ndarray:
199
+ x = np.asarray(images, dtype=float)
200
+ if x.ndim != 3:
201
+ raise ValueError("BMEMD expects a 3-D array of shape (n_channels, H, W)")
202
+ n_ch, height, width = x.shape
203
+ if n_ch < 2 or n_ch > 16:
204
+ raise ValueError("n_channels must satisfy 2 <= n_channels <= 16")
205
+ if height < 3 or width < 3:
206
+ raise ValueError("Each spatial dimension must be >= 3")
207
+ return x
208
+
209
+ def _direction_vectors(self, n_dim: int) -> np.ndarray:
210
+ """
211
+ Unit directions of shape ``(n_dir, n_dim)``.
212
+
213
+ Matches MATLAB ``get_dir`` / Hammersley construction in ``bmemd.m``.
214
+ """
215
+ ndir = self.n_dir
216
+ dirs = np.zeros((ndir, n_dim), dtype=float)
217
+
218
+ if n_dim == 2:
219
+ # Uniform samples on the circle (MATLAB 1-based index)
220
+ for it in range(1, ndir + 1):
221
+ dirs[it - 1, 0] = np.cos(2.0 * np.pi * it / ndir)
222
+ dirs[it - 1, 1] = np.sin(2.0 * np.pi * it / ndir)
223
+ return dirs
224
+
225
+ # Hammersley low-discrepancy sequence
226
+ if n_dim == 3:
227
+ base = [-ndir, 2]
228
+ seq = np.zeros((2, ndir))
229
+ for it in range(2):
230
+ seq[it, :] = np.asarray(hamm(ndir, base[it])).ravel()
231
+ for it in range(ndir):
232
+ tt = float(np.clip(2.0 * seq[0, it] - 1.0, -1.0, 1.0))
233
+ phirad = float(seq[1, it] * 2.0 * np.pi)
234
+ st = np.sqrt(max(1.0 - tt * tt, 0.0))
235
+ dirs[it, 0] = st * np.cos(phirad)
236
+ dirs[it, 1] = st * np.sin(phirad)
237
+ dirs[it, 2] = tt
238
+ return dirs
239
+
240
+ # n_dim > 3 (same construction as MEMD / MATLAB bmemd.m)
241
+ primes = nth_prime(n_dim - 1)
242
+ base = [-ndir] + list(primes[: n_dim - 1])
243
+ seq = np.zeros((n_dim, ndir))
244
+ for it in range(n_dim):
245
+ seq[it, :] = np.asarray(hamm(ndir, base[it])).ravel()
246
+
247
+ for it in range(ndir):
248
+ b = 2.0 * seq[:, it] - 1.0
249
+ # atan2(sqrt(flipud(cumsum(b(end:-1:2).^2))), b(1:end-1))
250
+ tht = np.arctan2(
251
+ np.sqrt(np.flipud(np.cumsum(b[:0:-1] ** 2))), b[: n_dim - 1]
252
+ )
253
+ dir_t = np.cumprod(np.concatenate(([1.0], np.sin(tht))))
254
+ dir_t = np.asarray(dir_t[:n_dim], dtype=float)
255
+ dir_t[: n_dim - 1] = np.cos(tht) * dir_t[: n_dim - 1]
256
+ dirs[it] = dir_t
257
+ return dirs
258
+
259
+ @staticmethod
260
+ def _project(images: np.ndarray, direction: np.ndarray) -> np.ndarray:
261
+ """Pixel-wise projection ``Σ_c I_c * u_c`` → shape ``(H, W)``."""
262
+ return np.tensordot(direction, images, axes=(0, 0))
263
+
264
+ @staticmethod
265
+ def _regional_extrema(surface: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
266
+ """Boolean maps of regional maxima / minima (MATLAB ``imregional*``)."""
267
+ maxima = maximum_filter(surface, size=3) == surface
268
+ minima = minimum_filter(surface, size=3) == surface
269
+ return maxima, minima
270
+
271
+ def _stop_emd(self, residue: np.ndarray, directions: np.ndarray) -> bool:
272
+ """Stop if any projection has fewer than 3 maxima or minima."""
273
+ for d in directions:
274
+ y = self._project(residue, d)
275
+ maxima, minima = self._regional_extrema(y)
276
+ if maxima.sum() < 3 or minima.sum() < 3:
277
+ return True
278
+ return False
279
+
280
+ def _envelope_mean(
281
+ self, mode: np.ndarray, directions: np.ndarray
282
+ ) -> Tuple[np.ndarray, int, np.ndarray]:
283
+ """
284
+ Average multivariate envelopes over all projection directions.
285
+
286
+ :return: ``(env_mean, nem_last, amp)`` where ``amp`` is the summed
287
+ envelope amplitude map used by the stop criterion.
288
+ """
289
+ n_ch, height, width = mode.shape
290
+ env_mean = np.zeros_like(mode)
291
+ amp = np.zeros((height, width), dtype=float)
292
+ nem = 0
293
+
294
+ yy_grid, xx_grid = np.mgrid[0:height, 0:width]
295
+
296
+ for d in directions:
297
+ y = self._project(mode, d)
298
+ maxima, minima = self._regional_extrema(y)
299
+ nem = int(maxima.sum() + minima.sum())
300
+
301
+ max_r, max_c = np.nonzero(maxima)
302
+ min_r, min_c = np.nonzero(minima)
303
+ if max_r.size < 3 or min_r.size < 3:
304
+ raise RuntimeError("Insufficient extrema for envelope fitting")
305
+
306
+ env_max = np.zeros_like(mode)
307
+ env_min = np.zeros_like(mode)
308
+ for c in range(n_ch):
309
+ env_max[c] = _surface_from_points(
310
+ max_c, max_r, mode[c, max_r, max_c], xx_grid, yy_grid
311
+ )
312
+ env_min[c] = _surface_from_points(
313
+ min_c, min_r, mode[c, min_r, min_c], xx_grid, yy_grid
314
+ )
315
+
316
+ amp = amp + np.sqrt(np.sum((env_max - env_min) ** 2, axis=0))
317
+ env_mean = env_mean + 0.5 * (env_max + env_min)
318
+
319
+ env_mean = env_mean / float(len(directions))
320
+ return env_mean, nem, amp
321
+
322
+ def _stop_sifting(self, env_mean: np.ndarray, amp: np.ndarray, nem: int) -> bool:
323
+ """Return True if sifting should stop (MATLAB ``stop_sifting``)."""
324
+ sx = np.sqrt(np.sum(env_mean**2, axis=0))
325
+ if np.any(amp):
326
+ sx = sx / (amp + 1e-12)
327
+ continue_sift = (
328
+ (np.mean(sx > self.sd) > self.tol) or np.any(sx > self.sd2)
329
+ ) and (nem > 9)
330
+ return not continue_sift
331
+
332
+
333
+ def _surface_from_points(
334
+ x: np.ndarray,
335
+ y: np.ndarray,
336
+ z: np.ndarray,
337
+ xx_grid: np.ndarray,
338
+ yy_grid: np.ndarray,
339
+ ) -> np.ndarray:
340
+ """
341
+ Interpolate scattered extrema onto the full image grid.
342
+
343
+ Uses Clough–Tocher (Delaunay-based) interpolation as recommended in the
344
+ BMEMD paper, with nearest-neighbour fill for exterior / degenerate regions.
345
+ """
346
+ points = np.column_stack([x.astype(float), y.astype(float)])
347
+ values = z.astype(float)
348
+
349
+ # Deduplicate coincident extrema (keep mean value)
350
+ _, unique_idx = np.unique(points, axis=0, return_index=True)
351
+ points = points[unique_idx]
352
+ values = values[unique_idx]
353
+
354
+ if points.shape[0] < 3:
355
+ return np.full(xx_grid.shape, float(np.mean(values)))
356
+
357
+ try:
358
+ interp = CloughTocher2DInterpolator(points, values, fill_value=np.nan)
359
+ surface = interp(xx_grid, yy_grid)
360
+ except Exception:
361
+ surface = np.full(xx_grid.shape, np.nan, dtype=float)
362
+
363
+ if np.any(~np.isfinite(surface)):
364
+ nearest = NearestNDInterpolator(points, values)
365
+ nan_mask = ~np.isfinite(surface)
366
+ surface[nan_mask] = nearest(xx_grid[nan_mask], yy_grid[nan_mask])
367
+
368
+ return surface
369
+
370
+
371
+ def local_var_img(images: np.ndarray, window: int = 5) -> np.ndarray:
372
+ """
373
+ Local squared deviation from a moving mean (MATLAB ``local_var_img``).
374
+
375
+ :param images: ``(H, W)`` or ``(C, H, W)``
376
+ :param window: Odd filter size
377
+ :return: Same shape as ``images``
378
+ """
379
+ x = np.asarray(images, dtype=float)
380
+ w = int(window)
381
+ if w < 1:
382
+ raise ValueError("window must be a positive integer")
383
+
384
+ if x.ndim == 2:
385
+ mean = uniform_filter(x, size=w, mode="nearest")
386
+ return (x - mean) ** 2
387
+
388
+ if x.ndim == 3:
389
+ out = np.empty_like(x)
390
+ for i in range(x.shape[0]):
391
+ mean = uniform_filter(x[i], size=w, mode="nearest")
392
+ out[i] = (x[i] - mean) ** 2
393
+ return out
394
+
395
+ raise ValueError("images must be 2-D or 3-D")
396
+
397
+
398
+ def fuse_images(
399
+ images: np.ndarray,
400
+ n_dir: int = 8,
401
+ max_imfs: int = 4,
402
+ var_window: int = 5,
403
+ **bmemd_kwargs,
404
+ ) -> Tuple[np.ndarray, np.ndarray]:
405
+ """
406
+ Convenience wrapper: BMEMD decomposition + variance-weighted fusion.
407
+
408
+ :return: ``(fused_image, imfs)``
409
+ """
410
+ bmemd = BMEMD(n_dir=n_dir, max_imfs=max_imfs, **bmemd_kwargs)
411
+ imfs = bmemd.fit_transform(images)
412
+ fused = bmemd.fuse(images, imfs=imfs, var_window=var_window)
413
+ return fused, imfs
@@ -163,9 +163,7 @@ class FAEMD(object):
163
163
 
164
164
  def get_imfs_and_residue(self) -> Tuple[np.ndarray, np.ndarray]:
165
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
- )
166
+ raise ValueError("No IMF found. Please run `fit_transform` method first.")
169
167
  return self.imfs, self.residue
170
168
 
171
169
 
@@ -178,9 +176,7 @@ def check_inputs(signal: np.ndarray) -> Tuple[np.ndarray, Tuple]:
178
176
  elif signal.ndim == 2:
179
177
  pass
180
178
  else:
181
- raise ValueError(
182
- "signal must have shape [seq_len] or [n_channels, seq_len]"
183
- )
179
+ raise ValueError("signal must have shape [seq_len] or [n_channels, seq_len]")
184
180
  return signal.T, inputs_shape
185
181
 
186
182
 
@@ -60,9 +60,7 @@ class FAEMD2D(object):
60
60
  self.residue: Optional[np.ndarray] = None
61
61
 
62
62
  def __str__(self) -> str:
63
- return (
64
- "Bidimensional Fast and Adaptive Empirical Mode Decomposition (FAEMD2D)"
65
- )
63
+ return "Bidimensional Fast and Adaptive Empirical Mode Decomposition (FAEMD2D)"
66
64
 
67
65
  def __call__(
68
66
  self, signal: np.ndarray, max_imfs: Optional[int] = None
@@ -81,9 +79,7 @@ class FAEMD2D(object):
81
79
  return x[np.newaxis, ...], True
82
80
  if x.ndim == 3:
83
81
  return x, False
84
- raise ValueError(
85
- "FAEMD2D expects shape (H, W) or (n_channels, H, W)"
86
- )
82
+ raise ValueError("FAEMD2D expects shape (H, W) or (n_channels, H, W)")
87
83
 
88
84
  def fit_transform(
89
85
  self,
@@ -58,9 +58,7 @@ class FAEMD3D(object):
58
58
  self.residue: Optional[np.ndarray] = None
59
59
 
60
60
  def __str__(self) -> str:
61
- return (
62
- "Tridimensional Fast and Adaptive Empirical Mode Decomposition (FAEMD3D)"
63
- )
61
+ return "Tridimensional Fast and Adaptive Empirical Mode Decomposition (FAEMD3D)"
64
62
 
65
63
  def __call__(
66
64
  self, signal: np.ndarray, max_imfs: Optional[int] = None
@@ -79,9 +77,7 @@ class FAEMD3D(object):
79
77
  if min(x.shape[1:]) < 3:
80
78
  raise ValueError("Each spatial dimension must be >= 3")
81
79
  return x, False
82
- raise ValueError(
83
- "FAEMD3D expects shape (X, Y, Z) or (n_channels, X, Y, Z)"
84
- )
80
+ raise ValueError("FAEMD3D expects shape (X, Y, Z) or (n_channels, X, Y, Z)")
85
81
 
86
82
  def fit_transform(
87
83
  self,