PySDKit 0.4.38__tar.gz → 0.4.39__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.38 → pysdkit-0.4.39}/PKG-INFO +1 -1
  2. {pysdkit-0.4.38 → pysdkit-0.4.39}/PySDKit.egg-info/PKG-INFO +1 -1
  3. {pysdkit-0.4.38 → pysdkit-0.4.39}/PySDKit.egg-info/SOURCES.txt +1 -0
  4. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/__init__.py +1 -1
  5. pysdkit-0.4.39/pysdkit/_vmd2d/cvmd2d.py +410 -0
  6. pysdkit-0.4.39/pysdkit/tests/test_cvmd2d.py +158 -0
  7. pysdkit-0.4.38/pysdkit/_vmd2d/cvmd2d.py +0 -471
  8. {pysdkit-0.4.38 → pysdkit-0.4.39}/LICENSE +0 -0
  9. {pysdkit-0.4.38 → pysdkit-0.4.39}/PySDKit.egg-info/dependency_links.txt +0 -0
  10. {pysdkit-0.4.38 → pysdkit-0.4.39}/PySDKit.egg-info/requires.txt +0 -0
  11. {pysdkit-0.4.38 → pysdkit-0.4.39}/PySDKit.egg-info/top_level.txt +0 -0
  12. {pysdkit-0.4.38 → pysdkit-0.4.39}/README.md +0 -0
  13. {pysdkit-0.4.38 → pysdkit-0.4.39}/pyproject.toml +0 -0
  14. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_alif/__init__.py +0 -0
  15. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_alif/alif.py +0 -0
  16. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_alif/data/__init__.py +0 -0
  17. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_alif/data/prefixed_double_filter.npy +0 -0
  18. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_alif/iterative_filtering.py +0 -0
  19. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_apmd/__init__.py +0 -0
  20. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_apmd/apmd.py +0 -0
  21. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/__init__.py +0 -0
  22. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/_find_extrema.py +0 -0
  23. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/_prepare_points.py +0 -0
  24. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/_splines.py +0 -0
  25. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/ceemdan.py +0 -0
  26. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/eemd.py +0 -0
  27. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/efd.py +0 -0
  28. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/emd.py +0 -0
  29. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/hht/__init__.py +0 -0
  30. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/hht/frequency.py +0 -0
  31. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/hht/hht.py +0 -0
  32. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/memd.py +0 -0
  33. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/remd.py +0 -0
  34. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/semd.py +0 -0
  35. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd/tvf_emd.py +0 -0
  36. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd2d/__init__.py +0 -0
  37. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd2d/bemd.py +0 -0
  38. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd2d/bmemd.py +0 -0
  39. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_emd2d/emd2d.py +0 -0
  40. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_esmd/__init__.py +0 -0
  41. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_esmd/esmd.py +0 -0
  42. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_ewt/__init__.py +0 -0
  43. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_ewt/ewt.py +0 -0
  44. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_ewt/ewt2d.py +0 -0
  45. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_faemd/__init__.py +0 -0
  46. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_faemd/extrema.py +0 -0
  47. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_faemd/faemd.py +0 -0
  48. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_faemd/faemd2d.py +0 -0
  49. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_faemd/faemd3d.py +0 -0
  50. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_faemd/filter.py +0 -0
  51. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_fmd/__init__.py +0 -0
  52. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_fmd/fmd.py +0 -0
  53. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_fmd/nfmd.py +0 -0
  54. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_gdmd/__init__.py +0 -0
  55. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_gdmd/gdmd.py +0 -0
  56. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_gdmd/vgnmd.py +0 -0
  57. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_hvd/__init__.py +0 -0
  58. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_hvd/hvd.py +0 -0
  59. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_itd/__init__.py +0 -0
  60. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_itd/itd.py +0 -0
  61. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_jmd/__init__.py +0 -0
  62. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_jmd/jmd.py +0 -0
  63. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_jmd/mjmd.py +0 -0
  64. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_jmd/sjmd.py +0 -0
  65. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_lmd/__init__.py +0 -0
  66. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_lmd/lmd.py +0 -0
  67. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_lmd/rlmd.py +0 -0
  68. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_osd/__init__.py +0 -0
  69. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_osd/swd.py +0 -0
  70. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_ssa/__init__.py +0 -0
  71. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_ssa/ssa.py +0 -0
  72. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/__init__.py +0 -0
  73. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/acmd.py +0 -0
  74. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/avmd.py +0 -0
  75. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/ba_acmd.py +0 -0
  76. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/base.py +0 -0
  77. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/mvmd.py +0 -0
  78. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/ovmd.py +0 -0
  79. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/stvmd.py +0 -0
  80. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/svmd.py +0 -0
  81. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/vmd_c.py +0 -0
  82. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/vmd_f.py +0 -0
  83. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd/vme.py +0 -0
  84. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd2d/__init__.py +0 -0
  85. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vmd2d/vmd2d.py +0 -0
  86. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vncmd/__init__.py +0 -0
  87. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vncmd/avncmd.py +0 -0
  88. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vncmd/incmd.py +0 -0
  89. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/_vncmd/vncmd.py +0 -0
  90. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/data/__init__.py +0 -0
  91. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/data/_add_noise.py +0 -0
  92. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/data/_cube.py +0 -0
  93. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/data/_generator.py +0 -0
  94. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/data/_image.py +0 -0
  95. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/data/_models.py +0 -0
  96. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/data/_test_univariate.py +0 -0
  97. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/data/_time_series.py +0 -0
  98. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/data/texture.txt +0 -0
  99. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/entropy/__init__.py +0 -0
  100. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
  101. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/entropy/_permutation_entropy.py +0 -0
  102. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/entropy/_sample_entropy.py +0 -0
  103. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/models/__init__.py +0 -0
  104. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/models/_base.py +0 -0
  105. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/models/_kmeans.py +0 -0
  106. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/models/_knn.py +0 -0
  107. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/models/_pca.py +0 -0
  108. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/plot/__init__.py +0 -0
  109. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/plot/_fourier_spectra.py +0 -0
  110. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/plot/_functions.py +0 -0
  111. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/plot/_plot_images.py +0 -0
  112. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/plot/_plot_imfs.py +0 -0
  113. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/plot/_plot_signal.py +0 -0
  114. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/__init__.py +0 -0
  115. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/data/__init__.py +0 -0
  116. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/data/test_generator_cube.py +0 -0
  117. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/data/test_generator_image.py +0 -0
  118. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/data/test_generator_signal.py +0 -0
  119. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
  120. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/models/__init__.py +0 -0
  121. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/models/test_knn.py +0 -0
  122. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/models/test_pca.py +0 -0
  123. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/run_all.py +0 -0
  124. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_alif.py +0 -0
  125. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_apmd.py +0 -0
  126. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_avncmd.py +0 -0
  127. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_bmemd.py +0 -0
  128. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_ceemdan.py +0 -0
  129. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_eemd.py +0 -0
  130. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_emd.py +0 -0
  131. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_ewt.py +0 -0
  132. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_ewt2d.py +0 -0
  133. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_faemd.py +0 -0
  134. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_faemd2d.py +0 -0
  135. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_faemd3d.py +0 -0
  136. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_gdmd.py +0 -0
  137. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_itd.py +0 -0
  138. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_jmd.py +0 -0
  139. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_lmd.py +0 -0
  140. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_moving_decomp.py +0 -0
  141. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_mstl.py +0 -0
  142. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_ovmd.py +0 -0
  143. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_rlmd.py +0 -0
  144. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_semd.py +0 -0
  145. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_sjmd.py +0 -0
  146. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_ssa.py +0 -0
  147. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_stl.py +0 -0
  148. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_stvmd.py +0 -0
  149. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_svmd.py +0 -0
  150. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_swd.py +0 -0
  151. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_vgnmd.py +0 -0
  152. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_vmd.py +0 -0
  153. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_vmd2d.py +0 -0
  154. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tests/test_vncmd.py +0 -0
  155. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tsa/__init__.py +0 -0
  156. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tsa/_dtw.py +0 -0
  157. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tsa/_moving_decomp.py +0 -0
  158. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tsa/_mstl.py +0 -0
  159. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/tsa/_stl.py +0 -0
  160. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/__init__.py +0 -0
  161. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_cite.py +0 -0
  162. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_correlation.py +0 -0
  163. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_diagnalization.py +0 -0
  164. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_differ.py +0 -0
  165. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_fft.py +0 -0
  166. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_function.py +0 -0
  167. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_hilbert.py +0 -0
  168. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_instantaneous.py +0 -0
  169. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_kernel_matrix.py +0 -0
  170. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_mirror.py +0 -0
  171. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_process.py +0 -0
  172. {pysdkit-0.4.38 → pysdkit-0.4.39}/pysdkit/utils/_smooth1d.py +0 -0
  173. {pysdkit-0.4.38 → pysdkit-0.4.39}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySDKit
3
- Version: 0.4.38
3
+ Version: 0.4.39
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.38
3
+ Version: 0.4.39
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>
@@ -115,6 +115,7 @@ pysdkit/tests/test_apmd.py
115
115
  pysdkit/tests/test_avncmd.py
116
116
  pysdkit/tests/test_bmemd.py
117
117
  pysdkit/tests/test_ceemdan.py
118
+ pysdkit/tests/test_cvmd2d.py
118
119
  pysdkit/tests/test_eemd.py
119
120
  pysdkit/tests/test_emd.py
120
121
  pysdkit/tests/test_ewt.py
@@ -2,7 +2,7 @@
2
2
  A Python library for signal decomposition algorithms.
3
3
  """
4
4
 
5
- __version__ = "0.4.38"
5
+ __version__ = "0.4.39"
6
6
 
7
7
  # Empirical Mode Decomposition
8
8
  from ._emd import EMD
@@ -0,0 +1,410 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2025/02/12 11:06:23
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+
7
+ Compact / Two-Dimensional TV Variational Mode Decomposition (CVMD2D / 2D-TV-VMD).
8
+
9
+ Zosso D., Dragomiretskiy K., Bertozzi A.L., Weiss P.S.
10
+ Two-Dimensional Compact Variational Mode Decomposition.
11
+ Journal of Mathematical Imaging and Vision, 58(2):294–320, 2017.
12
+ https://doi.org/10.1007/s10851-017-0710-z
13
+
14
+ MATLAB reference:
15
+ https://www.mathworks.com/matlabcentral/fileexchange/67285-two-dimensional-compact-variational-mode-decomposition-2d-tv-vmd
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from typing import Optional, Tuple, Union
20
+
21
+ import numpy as np
22
+ from numpy.linalg import norm
23
+
24
+ from pysdkit.utils import fft2d, ifft2d, fftshift, ifftshift
25
+
26
+
27
+ class CVMD2D(object):
28
+ """
29
+ Compact Variational Mode Decomposition for 2D Images (2D-TV-VMD).
30
+
31
+ Decomposes an image into spatially compact, spectrally sparse modes with
32
+ optional support segmentation (MBO / winner-takes-all) and artifact maps.
33
+
34
+ The optimisation proceeds in three scheduled phases (``A_phase = [a, b]``):
35
+
36
+ - iterations ``1 … a-1``: classical 2D VMD (no spatial support evolution)
37
+ - iterations ``a … b-1``: 2D-TV-VMD (individual MBO on supports ``A_k``)
38
+ - iterations ``b … end``: segmented 2D-TV-VMD (joint winner-takes-all)
39
+
40
+ MATLAB code: ``VMD_2D_TV.m`` (Zosso & Dragomiretskiy).
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ K: int = 5,
46
+ alpha: float = 1000,
47
+ beta: float = 0.5,
48
+ gamma: float = 500,
49
+ delta: float = np.inf,
50
+ rho: float = 10,
51
+ rho_k: float = 10,
52
+ tau: float = 0.0,
53
+ tau_k: float = 2.5,
54
+ t: float = 1.5,
55
+ DC: bool = False,
56
+ init: Union[str, int, np.ndarray] = "radially",
57
+ u_tol: float = 1e-10,
58
+ A_tol: float = 1e-4,
59
+ omega_tol: float = 1e-10,
60
+ max_iter: int = 130,
61
+ M: int = 1,
62
+ A_phase: Optional[np.ndarray] = None,
63
+ random_seed: int = 42,
64
+ ) -> None:
65
+ """
66
+ :param K: number of modes
67
+ :param alpha: spectral narrow-band / bandwidth penalty
68
+ :param beta: L1 area penalty on spatial supports ``A_k``
69
+ :param gamma: heat-diffusion weight for TV (MBO) propagation of ``A_k``
70
+ :param delta: artifact threshold on residual energy (``inf`` → disabled)
71
+ :param rho: data-fidelity weight
72
+ :param rho_k: u–v splitting weight
73
+ :param tau: dual step for data fidelity (0 → noise-slack)
74
+ :param tau_k: dual step for u–v splitting
75
+ :param t: ODE / PDE step for support updates
76
+ :param DC: keep the first mode at the DC frequency ``(0, 0)``
77
+ :param init: ``"radially"`` / ``"uniform"`` / ``0``, ``"random"`` / ``1``,
78
+ or an array of shape ``(2, K, M)`` with custom centre frequencies
79
+ :param u_tol: relative tolerance on modes ``u``
80
+ :param A_tol: absolute tolerance on supports ``A``
81
+ :param omega_tol: tolerance on centre frequencies
82
+ :param max_iter: maximum ADMM iterations ``N``
83
+ :param M: number of spectral sub-modes per spatial mode
84
+ :param A_phase: ``[a, b]`` phase schedule (default ``[100, 150]``)
85
+ :param random_seed: RNG seed for random omega initialisation
86
+ """
87
+ self.K = int(K)
88
+ self.alpha = float(alpha)
89
+ self.beta = float(beta)
90
+ self.gamma = float(gamma)
91
+ self.delta = float(delta)
92
+ self.rho = float(rho)
93
+ self.rho_k = float(rho_k)
94
+ self.tau = float(tau)
95
+ self.tau_k = float(tau_k)
96
+ self.t = float(t)
97
+ self.DC = bool(DC)
98
+ self.init = init
99
+ self.u_tol = float(u_tol)
100
+ self.A_tol = float(A_tol)
101
+ self.omega_tol = float(omega_tol)
102
+ self.max_iter = int(max_iter)
103
+ self.M = int(M)
104
+ if A_phase is None:
105
+ self.A_phase = np.array([100.0, 150.0], dtype=float)
106
+ else:
107
+ self.A_phase = np.asarray(A_phase, dtype=float).ravel()
108
+ if self.A_phase.size != 2:
109
+ raise ValueError("A_phase must be a length-2 array [a, b]")
110
+
111
+ self.rng = np.random.default_rng(seed=random_seed)
112
+
113
+ def __call__(
114
+ self, image: np.ndarray, return_all: bool = False
115
+ ) -> Union[
116
+ np.ndarray, Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]
117
+ ]:
118
+ return self.fit_transform(image=image, return_all=return_all)
119
+
120
+ def __str__(self) -> str:
121
+ return "Compact Variational Mode Decomposition for 2D Images (CVMD2D)"
122
+
123
+ def _init_omega(self) -> np.ndarray:
124
+ """Initialise centre frequencies ``omega`` (MATLAB ``init`` cases 0/1 / custom)."""
125
+ # (N+1) × 2 × K × M — extra slot so omega[n+1] is always valid
126
+ omega = np.zeros((self.max_iter + 1, 2, self.K, self.M), dtype=float)
127
+
128
+ init = self.init
129
+ # Map friendly aliases / MATLAB numeric codes
130
+ if isinstance(init, str):
131
+ key = init.lower()
132
+ if key in ("radially", "uniform", "radial"):
133
+ init = 0
134
+ elif key == "random":
135
+ init = 1
136
+ else:
137
+ raise ValueError(
138
+ "init string must be one of "
139
+ "['radially', 'uniform', 'random']; got {!r}".format(init)
140
+ )
141
+
142
+ if isinstance(init, (int, np.integer)):
143
+ if init == 0:
144
+ # Radially uniform on the half-plane
145
+ max_k = self.K - 1 if self.DC else self.K
146
+ radius = 0.3
147
+ # MATLAB: for k = DC+(1:maxK) with angle (k-1+(m-1)*maxK)
148
+ # 0-based equivalent: angle factor = (k + m*max_k)
149
+ for k in range(int(self.DC), int(self.DC) + max_k):
150
+ for m in range(self.M):
151
+ angle = np.pi * (k + m * max_k) / max_k / self.M
152
+ omega[0, 0, k, m] = radius * np.cos(angle)
153
+ omega[0, 1, k, m] = radius * np.sin(angle)
154
+ elif init == 1:
155
+ for k in range(self.K):
156
+ for m in range(self.M):
157
+ omega[0, 0, k, m] = self.rng.random() - 0.5
158
+ omega[0, 1, k, m] = self.rng.random() / 2.0
159
+ if self.DC:
160
+ omega[0, :, 0, :] = 0.0
161
+ else:
162
+ raise ValueError("numeric init must be 0 (radially) or 1 (random)")
163
+ else:
164
+ arr = np.asarray(init, dtype=float)
165
+ if arr.size != 2 * self.K * self.M:
166
+ raise ValueError(
167
+ "custom init must have size 2*K*M (shape (2, K, M)); "
168
+ "got size {}".format(arr.size)
169
+ )
170
+ arr = arr.reshape(2, self.K, self.M)
171
+ omega[0, :, :, :] = arr
172
+
173
+ return omega
174
+
175
+ def fit_transform(
176
+ self, image: np.ndarray, return_all: bool = False
177
+ ) -> Union[
178
+ np.ndarray, Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]
179
+ ]:
180
+ """
181
+ Decompose a 2D image with compact / TV variational mode decomposition.
182
+
183
+ :param image: real 2D array ``(Hy, Hx)``
184
+ :param return_all: if True, also return ``v, omega, A, X``
185
+ :return: modes ``u`` with shape ``(Hy, Hx, K, M)``, or the full tuple
186
+ """
187
+ signal = np.asarray(image, dtype=float)
188
+ if signal.ndim != 2:
189
+ raise ValueError("image must be a 2-D array")
190
+ hy, hx = signal.shape
191
+ if hy < 4 or hx < 4:
192
+ raise ValueError("image spatial size must be at least 4×4")
193
+
194
+ # Normalised spatial grid (MATLAB meshgrid)
195
+ grid_x, grid_y = np.meshgrid(
196
+ np.arange(1, hx + 1) / hx, np.arange(1, hy + 1) / hy
197
+ )
198
+
199
+ # Spectral domain discretisation — fy uses Hy (bugfix vs old Hx)
200
+ fx = 1.0 / hx
201
+ fy = 1.0 / hy
202
+ freqs_1 = grid_x - 0.5 - fx
203
+ freqs_2 = grid_y - 0.5 - fy
204
+
205
+ # Storage (Fourier modes are complex)
206
+ u_hat = np.zeros((hy, hx, self.K, self.M), dtype=complex)
207
+ u = np.zeros((hy, hx, self.K, self.M), dtype=float)
208
+ u_old = u.copy()
209
+ v = u.copy()
210
+
211
+ # Augmented Lagrangian variables
212
+ lambda_k = np.zeros_like(u) # u/v linking ~ rho_k
213
+ lambda_d = np.zeros((hy, hx), dtype=float) # data fidelity ~ rho
214
+
215
+ # Spatial supports & artifact map
216
+ A = np.ones((hy, hx, self.K), dtype=float)
217
+ A_old = A.copy()
218
+ artifact = np.zeros((hy, hx), dtype=bool)
219
+
220
+ omega = self._init_omega()
221
+
222
+ u_diff = np.inf
223
+ a_diff = np.inf
224
+ omega_diff = np.inf
225
+ sum_avk = 0.0
226
+
227
+ # Phase-schedule lower bound (handles Inf in A_phase like MATLAB)
228
+ finite_phases = self.A_phase[np.isfinite(self.A_phase)]
229
+ phase_bound = float(np.max(finite_phases)) if finite_phases.size else 0.0
230
+
231
+ # MATLAB starts n = 1; we use 0-based n and compare with (n + 1)
232
+ n = 0
233
+ while n < self.max_iter and (
234
+ (u_diff > self.u_tol or a_diff > self.A_tol or omega_diff > self.omega_tol)
235
+ or (n + 1) <= phase_bound
236
+ ):
237
+ # ---- modes / submodes ------------------------------------------------
238
+ for k in range(self.K):
239
+ for m in range(self.M):
240
+ hilbert_mask = (
241
+ np.sign(
242
+ freqs_1 * omega[n, 0, k, m] + freqs_2 * omega[n, 1, k, m]
243
+ )
244
+ + 1.0
245
+ )
246
+
247
+ # Update accumulator of A_j v_j for j ≠ (k,m)
248
+ if m == 0:
249
+ if k == 0:
250
+ sum_avk = (
251
+ sum_avk
252
+ + A[:, :, -1] * v[:, :, -1, -1]
253
+ - A[:, :, 0] * v[:, :, 0, 0]
254
+ )
255
+ else:
256
+ sum_avk = (
257
+ sum_avk
258
+ + A[:, :, k - 1] * v[:, :, k - 1, -1]
259
+ - A[:, :, k] * v[:, :, k, 0]
260
+ )
261
+ else:
262
+ sum_avk = (
263
+ sum_avk
264
+ + A[:, :, k] * v[:, :, k, m - 1]
265
+ - A[:, :, k] * v[:, :, k, m]
266
+ )
267
+
268
+ one_minus_x = 1.0 - artifact.astype(float)
269
+
270
+ # Update v (spatial-domain averaging)
271
+ v[:, :, k, m] = (
272
+ self.rho_k * u[:, :, k, m]
273
+ + lambda_k[:, :, k, m]
274
+ + self.rho
275
+ * A[:, :, k]
276
+ * (signal - sum_avk + lambda_d / self.rho)
277
+ * one_minus_x
278
+ ) / (self.rho_k + self.rho * one_minus_x * A[:, :, k] ** 2)
279
+
280
+ # Update u_hat (analytic spectrum via Wiener filter)
281
+ u_hat[:, :, k, m] = (
282
+ fftshift(
283
+ fft2d(self.rho_k * v[:, :, k, m] - lambda_k[:, :, k, m])
284
+ )
285
+ * hilbert_mask
286
+ ) / (
287
+ self.rho_k
288
+ + 2.0
289
+ * self.alpha
290
+ * (
291
+ (freqs_1 - omega[n, 0, k, m]) ** 2
292
+ + (freqs_2 - omega[n, 1, k, m]) ** 2
293
+ )
294
+ )
295
+
296
+ # Centre frequencies (keep first mode at 0 if DC)
297
+ if (not self.DC) or k > 0:
298
+ power = np.abs(u_hat[:, :, k, m]) ** 2
299
+ denom = np.sum(power)
300
+ if denom > 1e-30:
301
+ omega[n + 1, 0, k, m] = np.sum(freqs_1 * power) / denom
302
+ omega[n + 1, 1, k, m] = np.sum(freqs_2 * power) / denom
303
+ # Keep omegas on the top half-plane
304
+ if omega[n + 1, 1, k, m] < 0:
305
+ omega[n + 1, :, k, m] = -omega[n + 1, :, k, m]
306
+
307
+ # Recover real mode from analytic spectrum
308
+ u[:, :, k, m] = np.real(ifft2d(ifftshift(u_hat[:, :, k, m])))
309
+
310
+ # Phase II: individual MBO / TV support propagation
311
+ # MATLAB: n >= A_phase(1) && n < A_phase(2) (1-based n)
312
+ if self.A_phase[0] <= (n + 1) < self.A_phase[1]:
313
+ one_minus_x = 1.0 - artifact.astype(float)
314
+ sum_v_k = np.sum(v[:, :, k, :], axis=2)
315
+ A[:, :, k] = A[:, :, k] + self.t * (
316
+ -self.beta
317
+ + 2.0
318
+ * self.rho
319
+ * sum_v_k
320
+ * (
321
+ signal
322
+ - np.sum(A * np.sum(v, axis=3), axis=2)
323
+ + A[:, :, k] * sum_v_k
324
+ + lambda_d / self.rho
325
+ )
326
+ * one_minus_x
327
+ )
328
+ A[:, :, k] = A[:, :, k] / (
329
+ 1.0 + self.t * 2.0 * self.rho * one_minus_x * sum_v_k**2
330
+ )
331
+
332
+ A[A > 1] = 1.0
333
+ A[A < 0] = 0.0
334
+
335
+ # Heat equation / spectral diffusion
336
+ A[:, :, k] = np.real(
337
+ ifft2d(
338
+ fft2d(A[:, :, k])
339
+ / (
340
+ 1.0
341
+ + self.t
342
+ * self.gamma
343
+ * ifftshift(freqs_1**2 + freqs_2**2)
344
+ )
345
+ )
346
+ )
347
+ A[:, :, k] = (A[:, :, k] >= 0.5).astype(float)
348
+
349
+ # Phase III: joint MBO + winner-takes-all segmentation
350
+ if (n + 1) >= self.A_phase[1]:
351
+ sum_av = np.sum(A * np.sum(v, axis=3), axis=2)
352
+ for k in range(self.K):
353
+ sum_v_k = np.sum(v[:, :, k, :], axis=2)
354
+ A[:, :, k] = A[:, :, k] + self.t * (
355
+ -self.beta
356
+ + 2.0
357
+ * self.rho
358
+ * sum_v_k
359
+ * (signal - sum_av + A[:, :, k] * sum_v_k + lambda_d / self.rho)
360
+ )
361
+ A[:, :, k] = A[:, :, k] / (
362
+ 1.0 + self.t * 2.0 * self.rho * sum_v_k**2
363
+ )
364
+ A[:, :, k] = np.real(
365
+ ifft2d(
366
+ fft2d(A[:, :, k])
367
+ / (
368
+ 1.0
369
+ + self.t
370
+ * self.gamma
371
+ * ifftshift(freqs_1**2 + freqs_2**2)
372
+ )
373
+ )
374
+ )
375
+
376
+ # Winner-takes-all (column-major / Fortran order like MATLAB)
377
+ a_flat = A.reshape(hy * hx, self.K, order="F")
378
+ winners = np.argmax(a_flat, axis=1)
379
+ a_new = np.zeros_like(a_flat)
380
+ a_new[np.arange(hy * hx), winners] = 1.0
381
+ A = a_new.reshape(hy, hx, self.K, order="F")
382
+
383
+ # Artifact thresholding
384
+ residual = signal - np.sum(A * np.sum(v, axis=3), axis=2)
385
+ artifact = residual**2 >= self.delta
386
+
387
+ # Dual ascent — data fidelity
388
+ lambda_d = lambda_d + self.tau * residual
389
+ # Dual ascent — u/v splitting (must use tau_k, not tau)
390
+ lambda_k = lambda_k + self.tau_k * (u - v)
391
+
392
+ n += 1
393
+
394
+ # Stopping criteria (match MATLAB formulas)
395
+ u_norm = norm(u.ravel())
396
+ if u_norm > 1e-30:
397
+ u_diff = (norm((u - u_old).ravel()) ** 2) / (u_norm**2) / (hx * hy)
398
+ else:
399
+ u_diff = 0.0
400
+ a_diff = norm(A.ravel() - A_old.ravel(), ord=1) / (hx * hy)
401
+ omega_diff = float(norm(omega[n, :, :, :] - omega[n - 1, :, :, :]) ** 2)
402
+
403
+ u_old = u.copy()
404
+ A_old = A.copy()
405
+
406
+ omega_final = omega[n, :, :, :]
407
+
408
+ if return_all:
409
+ return u, v, omega_final, A, artifact.astype(float)
410
+ return u
@@ -0,0 +1,158 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on 2025/08/04
4
+ @author: Whenxuan Wang
5
+ @email: wwhenxuan@gmail.com
6
+ """
7
+ import unittest
8
+
9
+ import numpy as np
10
+
11
+ from pysdkit import CVMD2D
12
+ from pysdkit.data import test_grayscale, get_meshgrid_2D
13
+
14
+
15
+ def _small_texture(size: int = 64) -> np.ndarray:
16
+ """Synthetic multi-orientation texture similar to the paper demos."""
17
+ x, y = get_meshgrid_2D(low=0.0, high=2.0 * np.pi, sampling_rate=size)
18
+ img = 0.8 * np.sin(6.0 * x) + 0.6 * np.sin(5.0 * y) + 0.5 * np.sin(4.0 * (x + y))
19
+ return img - img.mean()
20
+
21
+
22
+ class CVMD2DTest(unittest.TestCase):
23
+ """Unit tests for Compact / 2D-TV Variational Mode Decomposition."""
24
+
25
+ @classmethod
26
+ def setUpClass(cls) -> None:
27
+ gray = test_grayscale().astype(float)
28
+ cls.image = gray[::4, ::4] - gray[::4, ::4].mean()
29
+ cls.synth = _small_texture(48)
30
+
31
+ def _make(self, **kwargs) -> CVMD2D:
32
+ params = dict(
33
+ K=3,
34
+ alpha=1000,
35
+ beta=0.5,
36
+ gamma=500,
37
+ delta=np.inf,
38
+ rho=10,
39
+ rho_k=10,
40
+ tau=0.0,
41
+ tau_k=2.5,
42
+ t=1.5,
43
+ DC=True,
44
+ init="radially",
45
+ max_iter=35,
46
+ M=1,
47
+ A_phase=np.array([15.0, np.inf]),
48
+ )
49
+ params.update(kwargs)
50
+ return CVMD2D(**params)
51
+
52
+ def test_fit_transform_shape(self) -> None:
53
+ K = 3
54
+ decomp = self._make(K=K)
55
+ u = decomp.fit_transform(self.image)
56
+ hy, hx = self.image.shape
57
+ self.assertEqual(u.shape, (hy, hx, K, 1))
58
+ self.assertTrue(np.all(np.isfinite(u)))
59
+
60
+ def test_default_call(self) -> None:
61
+ decomp = self._make()
62
+ a = decomp(self.image)
63
+ b = decomp.fit_transform(self.image)
64
+ self.assertEqual(a.shape, b.shape)
65
+ self.assertTrue(np.allclose(a, b))
66
+
67
+ def test_return_all(self) -> None:
68
+ decomp = self._make(K=3)
69
+ u, v, omega, A, X = decomp.fit_transform(self.image, return_all=True)
70
+ hy, hx = self.image.shape
71
+ self.assertEqual(u.shape, (hy, hx, 3, 1))
72
+ self.assertEqual(v.shape, u.shape)
73
+ self.assertEqual(omega.shape[1], 3) # (2, K, M)
74
+ self.assertEqual(A.shape, (hy, hx, 3))
75
+ self.assertEqual(X.shape, (hy, hx))
76
+ self.assertTrue(np.all((A >= 0) & (A <= 1)))
77
+
78
+ def test_uniform_init_alias(self) -> None:
79
+ """'uniform' must map to MATLAB init=0 (radially)."""
80
+ decomp = self._make(init="uniform", max_iter=10, A_phase=np.array([5.0, 8.0]))
81
+ u = decomp.fit_transform(self.synth)
82
+ self.assertTrue(np.all(np.isfinite(u)))
83
+
84
+ def test_random_init(self) -> None:
85
+ decomp = self._make(
86
+ init="random", DC=False, max_iter=12, A_phase=np.array([6.0, 10.0])
87
+ )
88
+ u = decomp.fit_transform(self.synth)
89
+ self.assertEqual(u.shape[-2], 3)
90
+
91
+ def test_dc_mode_stays_at_origin(self) -> None:
92
+ decomp = self._make(K=3, DC=True, max_iter=20, A_phase=np.array([10.0, np.inf]))
93
+ _, _, omega, _, _ = decomp.fit_transform(self.synth, return_all=True)
94
+ self.assertTrue(np.allclose(omega[:, 0, :], 0.0, atol=1e-12))
95
+
96
+ def test_phase_iii_winner_takes_all(self) -> None:
97
+ """After phase III, supports should form a partition (sum_k A_k = 1)."""
98
+ decomp = self._make(
99
+ K=3,
100
+ max_iter=40,
101
+ A_phase=np.array([10.0, 20.0]),
102
+ tau=0.0,
103
+ tau_k=0.0,
104
+ )
105
+ _, _, _, A, _ = decomp.fit_transform(self.synth, return_all=True)
106
+ self.assertTrue(np.allclose(A.sum(axis=2), 1.0))
107
+
108
+ def test_modes_carry_energy(self) -> None:
109
+ decomp = self._make(K=3, max_iter=40, A_phase=np.array([20.0, np.inf]))
110
+ u = decomp.fit_transform(self.synth)
111
+ energies = [float(np.sum(u[:, :, k, 0] ** 2)) for k in range(3)]
112
+ self.assertTrue(all(e > 0 for e in energies))
113
+ # reconstruction from modes should explain a non-trivial fraction of energy
114
+ recon = u.sum(axis=(2, 3))
115
+ rel = np.linalg.norm(recon - self.synth) / (np.linalg.norm(self.synth) + 1e-30)
116
+ self.assertLess(rel, 1.5)
117
+
118
+ def test_invalid_init(self) -> None:
119
+ with self.assertRaises(ValueError):
120
+ self._make(init="not-a-method").fit_transform(self.synth)
121
+
122
+ def test_non_2d_input(self) -> None:
123
+ with self.assertRaises(ValueError):
124
+ self._make().fit_transform(np.ones(16))
125
+
126
+ def test_str(self) -> None:
127
+ self.assertIn("CVMD2D", str(CVMD2D()))
128
+
129
+ def test_grayscale_demo_params(self) -> None:
130
+ """MATLAB case-1 style parameters should run on the library grayscale demo."""
131
+ img = test_grayscale().astype(float)
132
+ img = img[::4, ::4] - img[::4, ::4].mean()
133
+ decomp = CVMD2D(
134
+ K=5,
135
+ alpha=1000,
136
+ beta=0.5,
137
+ gamma=500,
138
+ delta=np.inf,
139
+ rho=10,
140
+ rho_k=10,
141
+ tau=2.5,
142
+ tau_k=2.5,
143
+ t=1.5,
144
+ DC=True,
145
+ init="radially",
146
+ max_iter=45,
147
+ M=1,
148
+ A_phase=np.array([25.0, np.inf]),
149
+ )
150
+ u, v, omega, A, X = decomp.fit_transform(img, return_all=True)
151
+ self.assertEqual(u.shape[2], 5)
152
+ self.assertTrue(np.all(np.isfinite(u)))
153
+ self.assertTrue(np.all(np.isfinite(v)))
154
+ self.assertEqual(X.shape, img.shape)
155
+
156
+
157
+ if __name__ == "__main__":
158
+ unittest.main()