PySDKit 0.4.36__tar.gz → 0.4.37__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.
- {pysdkit-0.4.36 → pysdkit-0.4.37}/PKG-INFO +3 -3
- {pysdkit-0.4.36 → pysdkit-0.4.37}/PySDKit.egg-info/PKG-INFO +3 -3
- {pysdkit-0.4.36 → pysdkit-0.4.37}/PySDKit.egg-info/SOURCES.txt +2 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/README.md +2 -2
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/__init__.py +6 -1
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/semd.py +8 -2
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd2d/__init__.py +2 -0
- pysdkit-0.4.37/pysdkit/_emd2d/bmemd.py +413 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_faemd/faemd.py +2 -6
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_faemd/faemd2d.py +2 -6
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_faemd/faemd3d.py +2 -6
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_faemd/filter.py +4 -12
- pysdkit-0.4.37/pysdkit/tests/test_bmemd.py +102 -0
- pysdkit-0.4.37/pysdkit/tests/test_ceemdan.py +202 -0
- pysdkit-0.4.37/pysdkit/tests/test_eemd.py +175 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_faemd.py +0 -1
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_mstl.py +8 -4
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tsa/_mstl.py +3 -1
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tsa/_stl.py +11 -4
- pysdkit-0.4.36/pysdkit/_emd2d/bmemd.py +0 -24
- pysdkit-0.4.36/pysdkit/tests/test_ceemdan.py +0 -286
- {pysdkit-0.4.36 → pysdkit-0.4.37}/LICENSE +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/PySDKit.egg-info/dependency_links.txt +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/PySDKit.egg-info/requires.txt +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/PySDKit.egg-info/top_level.txt +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pyproject.toml +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_alif/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_alif/alif.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_alif/data/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_alif/data/prefixed_double_filter.npy +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_alif/iterative_filtering.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_apmd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_apmd/apmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/_find_extrema.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/_prepare_points.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/_splines.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/ceemdan.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/eemd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/efd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/emd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/hht/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/hht/frequency.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/hht/hht.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/memd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/remd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd/tvf_emd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd2d/bemd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_emd2d/emd2d.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_esmd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_esmd/esmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_ewt/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_ewt/ewt.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_ewt/ewt2d.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_faemd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_faemd/extrema.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_fmd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_fmd/fmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_fmd/nfmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_gdmd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_gdmd/gdmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_hvd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_hvd/hvd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_itd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_itd/itd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_jmd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_jmd/jmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_jmd/mjmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_jmd/sjmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_lmd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_lmd/lmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_lmd/rlmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_osd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_osd/swd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_ssa/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_ssa/ssa.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/acmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/avmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/ba_acmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/base.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/mvmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/ovmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/stvmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/svmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/vmd_c.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/vmd_f.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd/vme.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd2d/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd2d/cvmd2d.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vmd2d/vmd2d.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vncmd/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vncmd/avncmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vncmd/incmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/_vncmd/vncmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/data/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/data/_add_noise.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/data/_cube.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/data/_generator.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/data/_image.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/data/_models.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/data/_test_univariate.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/data/_time_series.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/data/texture.txt +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/entropy/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/entropy/_permutation_entropy.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/entropy/_sample_entropy.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/models/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/models/_base.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/models/_kmeans.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/models/_knn.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/models/_pca.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/plot/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/plot/_fourier_spectra.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/plot/_functions.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/plot/_plot_images.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/plot/_plot_imfs.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/plot/_plot_signal.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/data/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/data/test_generator_cube.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/data/test_generator_image.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/data/test_generator_signal.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/models/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/models/test_knn.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/models/test_pca.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/run_all.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_alif.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_apmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_avncmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_emd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_ewt.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_ewt2d.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_faemd2d.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_faemd3d.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_gdmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_itd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_jmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_lmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_moving_decomp.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_ovmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_rlmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_semd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_sjmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_ssa.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_stl.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_stvmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_svmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_swd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_vmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_vmd2d.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tests/test_vncmd.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tsa/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tsa/_dtw.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/tsa/_moving_decomp.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/__init__.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_cite.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_correlation.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_diagnalization.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_differ.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_fft.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_function.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_hilbert.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_instantaneous.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_kernel_matrix.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_mirror.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_process.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/pysdkit/utils/_smooth1d.py +0 -0
- {pysdkit-0.4.36 → pysdkit-0.4.37}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySDKit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.37
|
|
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.
|
|
3
|
+
Version: 0.4.37
|
|
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) | ✔️ |
|
|
@@ -112,7 +112,9 @@ pysdkit/tests/run_all.py
|
|
|
112
112
|
pysdkit/tests/test_alif.py
|
|
113
113
|
pysdkit/tests/test_apmd.py
|
|
114
114
|
pysdkit/tests/test_avncmd.py
|
|
115
|
+
pysdkit/tests/test_bmemd.py
|
|
115
116
|
pysdkit/tests/test_ceemdan.py
|
|
117
|
+
pysdkit/tests/test_eemd.py
|
|
116
118
|
pysdkit/tests/test_emd.py
|
|
117
119
|
pysdkit/tests/test_ewt.py
|
|
118
120
|
pysdkit/tests/test_ewt2d.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.
|
|
5
|
+
__version__ = "0.4.37"
|
|
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
|
|
|
@@ -163,6 +166,7 @@ Fast and Adaptive Empirical Mode Decomposition | FAEMD
|
|
|
163
166
|
Bidimensional FAEMD | FAEMD2D
|
|
164
167
|
Tridimensional FAEMD | FAEMD3D
|
|
165
168
|
Empirical Mode Decomposition 2D for images | EMD2D
|
|
169
|
+
Bidimensional Multivariate EMD | BMEMD
|
|
166
170
|
Hilbert Vibration Decomposition | HVD
|
|
167
171
|
Intrinsic Time-Scale Decomposition | ITD
|
|
168
172
|
Local Mean Decomposition | LMD
|
|
@@ -211,6 +215,7 @@ __all__ = [
|
|
|
211
215
|
"FAEMD2D",
|
|
212
216
|
"FAEMD3D",
|
|
213
217
|
"EMD2D",
|
|
218
|
+
"BMEMD",
|
|
214
219
|
"HVD",
|
|
215
220
|
"ITD",
|
|
216
221
|
"LMD",
|
|
@@ -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(
|
|
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 =
|
|
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)
|
|
@@ -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,
|
|
@@ -81,9 +81,7 @@ def filter_size1D(
|
|
|
81
81
|
return _seven_windows_from_spacings(edge_max, edge_min, window_type)
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
def ord_filt1(
|
|
85
|
-
signal: np.ndarray, order: str, window_size: int
|
|
86
|
-
) -> np.ndarray:
|
|
84
|
+
def ord_filt1(signal: np.ndarray, order: str, window_size: int) -> np.ndarray:
|
|
87
85
|
"""1-D rank-order filter with symmetric (reflected) padding."""
|
|
88
86
|
signal = np.asarray(signal, dtype=float)
|
|
89
87
|
shape = signal.shape
|
|
@@ -139,12 +137,8 @@ def identify_max_min_2d(signal: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
|
139
137
|
signal = np.asarray(signal, dtype=float)
|
|
140
138
|
mask = np.ones((3, 3), dtype=bool)
|
|
141
139
|
mask[1, 1] = False
|
|
142
|
-
neigh_max = ndimage.maximum_filter(
|
|
143
|
-
|
|
144
|
-
)
|
|
145
|
-
neigh_min = ndimage.minimum_filter(
|
|
146
|
-
signal, footprint=mask, mode="nearest"
|
|
147
|
-
)
|
|
140
|
+
neigh_max = ndimage.maximum_filter(signal, footprint=mask, mode="nearest")
|
|
141
|
+
neigh_min = ndimage.minimum_filter(signal, footprint=mask, mode="nearest")
|
|
148
142
|
maxima = signal >= neigh_max
|
|
149
143
|
minima = signal <= neigh_min
|
|
150
144
|
return maxima, minima
|
|
@@ -281,9 +275,7 @@ def filter_size_3d(
|
|
|
281
275
|
return _seven_windows_from_spacings(max_nearest, min_nearest, window_type)
|
|
282
276
|
|
|
283
277
|
|
|
284
|
-
def ord_filt3_separable(
|
|
285
|
-
signal: np.ndarray, order: str, window_size: int
|
|
286
|
-
) -> np.ndarray:
|
|
278
|
+
def ord_filt3_separable(signal: np.ndarray, order: str, window_size: int) -> np.ndarray:
|
|
287
279
|
"""Separable 3-D OSF: apply 1-D rank filter along each axis."""
|
|
288
280
|
w = int(window_size)
|
|
289
281
|
if w < 3:
|