PySDKit 0.4.45__tar.gz → 0.4.46__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.45 → pysdkit-0.4.46}/PKG-INFO +2 -1
- {pysdkit-0.4.45 → pysdkit-0.4.46}/PySDKit.egg-info/PKG-INFO +2 -1
- {pysdkit-0.4.45 → pysdkit-0.4.46}/PySDKit.egg-info/SOURCES.txt +7 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/README.md +1 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pyproject.toml +2 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/__init__.py +8 -2
- pysdkit-0.4.46/pysdkit/_tfa/__init__.py +41 -0
- pysdkit-0.4.46/pysdkit/_tfa/data/__init__.py +2 -0
- pysdkit-0.4.46/pysdkit/_tfa/data/dual_signal_noise.npy +0 -0
- pysdkit-0.4.46/pysdkit/_tfa/data/map2.npy +0 -0
- pysdkit-0.4.46/pysdkit/_tfa/data/single_nsignal.npy +0 -0
- pysdkit-0.4.46/pysdkit/_tfa/vtfmtd.py +439 -0
- pysdkit-0.4.46/pysdkit/tests/test_vtfmtd.py +273 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/LICENSE +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/PySDKit.egg-info/dependency_links.txt +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/PySDKit.egg-info/requires.txt +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/PySDKit.egg-info/top_level.txt +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/_find_extrema.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/_prepare_points.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/_splines.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/ceemdan.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/eemd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/efd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/emd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/esmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/hht/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/hht/frequency.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/hht/hht.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/memd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/remd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/semd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd/tvf_emd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd2d/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd2d/bemd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd2d/bmemd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_emd2d/emd2d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_ewt/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_ewt/ewt.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_ewt/ewt2d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/extrema.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/faemd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/faemd2d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/faemd3d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_faemd/filter.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_fmd/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_fmd/fmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_fmd/nfmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_gdmd/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_gdmd/agncmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_gdmd/gdmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_gdmd/ivgnmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_gdmd/vgnmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_imd/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_imd/apmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_imd/data/gearbox_fault_snippet.npy +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_imd/data/input_sig.npy +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_imd/imd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_jmd/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_jmd/jmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_jmd/mjmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_jmd/sjmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_lmd/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_lmd/lmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_lmd/rlmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_osd/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_osd/components.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_osd/osd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_osd/swd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/alif.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/data/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/data/prefixed_double_filter.npy +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/hvd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/itd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/iterative_filtering.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_tid/ssa.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/acmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/avmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/ba_acmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/base.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/mvmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/ovmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/stvmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/svmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/vmd_c.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/vmd_f.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd/vme.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd2d/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd2d/cvmd2d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vmd2d/vmd2d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vncmd/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vncmd/avncmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vncmd/incmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/_vncmd/vncmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_add_noise.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_cube.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_generator.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_image.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_models.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_test_univariate.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/_time_series.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/fmd_demo.npy +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/texture.txt +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/data/vmd_example.npy +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/entropy/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/entropy/_permutation_entropy.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/entropy/_sample_entropy.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/models/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/models/_base.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/models/_kmeans.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/models/_knn.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/models/_pca.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/_fourier_spectra.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/_functions.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/_plot_images.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/_plot_imfs.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/plot/_plot_signal.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/data/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/data/test_generator_cube.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/data/test_generator_image.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/data/test_generator_signal.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/models/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/models/test_knn.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/models/test_pca.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/run_all.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_agncmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_alif.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_apmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_avncmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_bmemd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ceemdan.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_cvmd2d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_eemd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_emd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_esmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ewt.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ewt2d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_faemd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_faemd2d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_faemd3d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_fmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_gdmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_hvd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_imd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_itd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ivgnmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_jmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_lmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_moving_decomp.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_mstl.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_osd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ovmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_rlmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_semd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_sjmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_ssa.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_stl.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_stvmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_svmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_swd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_vgnmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_vmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_vmd2d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tests/test_vncmd.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tsa/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tsa/_dtw.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tsa/_moving_decomp.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tsa/_mstl.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/tsa/_stl.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/__init__.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_correlation.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_diagnalization.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_differ.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_fft.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_function.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_hilbert.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_instantaneous.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_kernel_matrix.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_mirror.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_process.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/pysdkit/utils/_smooth1d.py +0 -0
- {pysdkit-0.4.45 → pysdkit-0.4.46}/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.46
|
|
4
4
|
Summary: A Python library for signal decomposition algorithms with a unified interface.
|
|
5
5
|
Author: Whenxuan Wang, RuiZhe Wang, Rongkun Zhu, Kai Wu, Lei Wang, josefinez, Deeksha Manjunath, Yuan Feng, WenTong Zhao, JacktheFowler
|
|
6
6
|
Maintainer-email: Whenxuan Wang <wwhenxuan@gmail.com>, RuiZhe Wang <3133986068@qq.com>
|
|
@@ -200,6 +200,7 @@ plot_IMFs(signal, IMFs) # per-channel panels
|
|
|
200
200
|
| [`SJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/sjmd.py) / `SMJMD` (Successive Jump and Mode Decomposition) | [[paper]](https://arxiv.org/abs/2504.08453) | [[code]]() | ✔️ |
|
|
201
201
|
| [`ESMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/esmd.py) (Extreme-Point Symmetric Mode Decomposition) | [[paper]](https://arxiv.org/abs/1303.6540) | [[code]](https://github.com/WuShichao/esmd) | ✔️ |
|
|
202
202
|
| [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
|
|
203
|
+
| [`VTFMTD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_tfa/vftmtd.py) (Variational TF Mode Tracking Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0165168426001179) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/183389-variational-time-frequency-mode-tracking-decomposition) | ✔️ |
|
|
203
204
|
| [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
|
|
204
205
|
| [`OSD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/osd.py) (Optimization-based Signal Decomposition) | [[paper]](https://web.stanford.edu/~boyd/papers/sig_decomp_mprox.html) | [[code]](https://github.com/cvxgrp/signal-decomposition) | ✔️ |
|
|
205
206
|
| [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySDKit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.46
|
|
4
4
|
Summary: A Python library for signal decomposition algorithms with a unified interface.
|
|
5
5
|
Author: Whenxuan Wang, RuiZhe Wang, Rongkun Zhu, Kai Wu, Lei Wang, josefinez, Deeksha Manjunath, Yuan Feng, WenTong Zhao, JacktheFowler
|
|
6
6
|
Maintainer-email: Whenxuan Wang <wwhenxuan@gmail.com>, RuiZhe Wang <3133986068@qq.com>
|
|
@@ -200,6 +200,7 @@ plot_IMFs(signal, IMFs) # per-channel panels
|
|
|
200
200
|
| [`SJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/sjmd.py) / `SMJMD` (Successive Jump and Mode Decomposition) | [[paper]](https://arxiv.org/abs/2504.08453) | [[code]]() | ✔️ |
|
|
201
201
|
| [`ESMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/esmd.py) (Extreme-Point Symmetric Mode Decomposition) | [[paper]](https://arxiv.org/abs/1303.6540) | [[code]](https://github.com/WuShichao/esmd) | ✔️ |
|
|
202
202
|
| [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
|
|
203
|
+
| [`VTFMTD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_tfa/vftmtd.py) (Variational TF Mode Tracking Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0165168426001179) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/183389-variational-time-frequency-mode-tracking-decomposition) | ✔️ |
|
|
203
204
|
| [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
|
|
204
205
|
| [`OSD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/osd.py) (Optimization-based Signal Decomposition) | [[paper]](https://web.stanford.edu/~boyd/papers/sig_decomp_mprox.html) | [[code]](https://github.com/cvxgrp/signal-decomposition) | ✔️ |
|
|
205
206
|
| [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
|
|
@@ -60,6 +60,12 @@ pysdkit/_osd/__init__.py
|
|
|
60
60
|
pysdkit/_osd/components.py
|
|
61
61
|
pysdkit/_osd/osd.py
|
|
62
62
|
pysdkit/_osd/swd.py
|
|
63
|
+
pysdkit/_tfa/__init__.py
|
|
64
|
+
pysdkit/_tfa/vtfmtd.py
|
|
65
|
+
pysdkit/_tfa/data/__init__.py
|
|
66
|
+
pysdkit/_tfa/data/dual_signal_noise.npy
|
|
67
|
+
pysdkit/_tfa/data/map2.npy
|
|
68
|
+
pysdkit/_tfa/data/single_nsignal.npy
|
|
63
69
|
pysdkit/_tid/__init__.py
|
|
64
70
|
pysdkit/_tid/alif.py
|
|
65
71
|
pysdkit/_tid/hvd.py
|
|
@@ -154,6 +160,7 @@ pysdkit/tests/test_vgnmd.py
|
|
|
154
160
|
pysdkit/tests/test_vmd.py
|
|
155
161
|
pysdkit/tests/test_vmd2d.py
|
|
156
162
|
pysdkit/tests/test_vncmd.py
|
|
163
|
+
pysdkit/tests/test_vtfmtd.py
|
|
157
164
|
pysdkit/tests/data/__init__.py
|
|
158
165
|
pysdkit/tests/data/test_generator_cube.py
|
|
159
166
|
pysdkit/tests/data/test_generator_image.py
|
|
@@ -147,6 +147,7 @@ plot_IMFs(signal, IMFs) # per-channel panels
|
|
|
147
147
|
| [`SJMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_jmd/sjmd.py) / `SMJMD` (Successive Jump and Mode Decomposition) | [[paper]](https://arxiv.org/abs/2504.08453) | [[code]]() | ✔️ |
|
|
148
148
|
| [`ESMD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_emd/esmd.py) (Extreme-Point Symmetric Mode Decomposition) | [[paper]](https://arxiv.org/abs/1303.6540) | [[code]](https://github.com/WuShichao/esmd) | ✔️ |
|
|
149
149
|
| [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
|
|
150
|
+
| [`VTFMTD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_tfa/vftmtd.py) (Variational TF Mode Tracking Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0165168426001179) | [[code]](https://www.mathworks.com/matlabcentral/fileexchange/183389-variational-time-frequency-mode-tracking-decomposition) | ✔️ |
|
|
150
151
|
| [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
|
|
151
152
|
| [`OSD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/osd.py) (Optimization-based Signal Decomposition) | [[paper]](https://web.stanford.edu/~boyd/papers/sig_decomp_mprox.html) | [[code]](https://github.com/cvxgrp/signal-decomposition) | ✔️ |
|
|
152
153
|
| [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
|
|
@@ -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.46"
|
|
6
6
|
|
|
7
7
|
# Empirical Mode Decomposition
|
|
8
8
|
from ._emd import EMD
|
|
@@ -112,6 +112,9 @@ from ._vncmd import AVNCMD
|
|
|
112
112
|
# Adaptive Local Iterative Filtering (time iterative decomposition)
|
|
113
113
|
from ._tid import ALIF
|
|
114
114
|
|
|
115
|
+
# Variational Time-Frequency Mode Tracking Decomposition (TFA)
|
|
116
|
+
from ._tfa import VTFMTD, vtfmtd
|
|
117
|
+
|
|
115
118
|
# Adaptive Polymorphic Mode Decomposition / Impulsive Mode Decomposition
|
|
116
119
|
from ._imd import APMD, IMD, imd
|
|
117
120
|
|
|
@@ -193,7 +196,7 @@ Optimization-based Signal Decomposition | OSD
|
|
|
193
196
|
Generalized Dispersion Mode Decomposition | GDMD
|
|
194
197
|
Variational Generalized Nonlinear Mode Dec. | VGNMD
|
|
195
198
|
Improved VGNMD (crossed chirp / dispersive) | IVGNMD
|
|
196
|
-
Adaptive Generalized Dispersive Mode Dec.
|
|
199
|
+
Adaptive Generalized Dispersive Mode Dec. | AGNCMD / AGDMD
|
|
197
200
|
Variational Mode Decomposition | VMD
|
|
198
201
|
Multivariate Variational Mode Decomposition | MVMD
|
|
199
202
|
Variational Mode Extraction | VME
|
|
@@ -206,6 +209,7 @@ Variational Nonlinear Chirp Mode Decomposition | VNCMD
|
|
|
206
209
|
Iterative Nonlinear Chirp Mode Decomposition | INCMD
|
|
207
210
|
Adaptive Variational Nonlinear Chirp Mode Dec. | AVNCMD
|
|
208
211
|
Adaptive Local Iterative Filtering | ALIF
|
|
212
|
+
Variational TF Mode Tracking Decomposition | VTFMTD
|
|
209
213
|
Adaptive Polymorphic Mode Decomposition | APMD
|
|
210
214
|
Impulsive Mode Decomposition | IMD
|
|
211
215
|
Empirical Wavelet Transform | EWT
|
|
@@ -273,6 +277,8 @@ __all__ = [
|
|
|
273
277
|
"INCMD",
|
|
274
278
|
"AVNCMD",
|
|
275
279
|
"ALIF",
|
|
280
|
+
"VTFMTD",
|
|
281
|
+
"vtfmtd",
|
|
276
282
|
"APMD",
|
|
277
283
|
"IMD",
|
|
278
284
|
"imd",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Time-Frequency Analysis (TFA).
|
|
4
|
+
|
|
5
|
+
Methods that operate primarily on time-frequency representations
|
|
6
|
+
(e.g. STFT) for mode separation and instantaneous-frequency tracking.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .vtfmtd import (
|
|
10
|
+
VTFMTD,
|
|
11
|
+
vtfmtd,
|
|
12
|
+
stft,
|
|
13
|
+
frequency_axis,
|
|
14
|
+
bin_index_grid,
|
|
15
|
+
expand_omega_init,
|
|
16
|
+
first_difference_gram,
|
|
17
|
+
estimate_if_centroid,
|
|
18
|
+
smooth_if,
|
|
19
|
+
moving_average_if,
|
|
20
|
+
omega_bins_to_hz,
|
|
21
|
+
load_dual_signal_noise,
|
|
22
|
+
load_single_nsignal,
|
|
23
|
+
load_map2,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"VTFMTD",
|
|
28
|
+
"vtfmtd",
|
|
29
|
+
"stft",
|
|
30
|
+
"frequency_axis",
|
|
31
|
+
"bin_index_grid",
|
|
32
|
+
"expand_omega_init",
|
|
33
|
+
"first_difference_gram",
|
|
34
|
+
"estimate_if_centroid",
|
|
35
|
+
"smooth_if",
|
|
36
|
+
"moving_average_if",
|
|
37
|
+
"omega_bins_to_hz",
|
|
38
|
+
"load_dual_signal_noise",
|
|
39
|
+
"load_single_nsignal",
|
|
40
|
+
"load_map2",
|
|
41
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Variational Time-Frequency Mode Tracking Decomposition (VTFMTD).
|
|
4
|
+
|
|
5
|
+
Dong, H., Shan, T., Yu, G., Shi, Y., Chen, Y.
|
|
6
|
+
Variational time-frequency mode tracking for micro-Doppler signature extraction.
|
|
7
|
+
Signal Processing, 246:110603, 2026.
|
|
8
|
+
https://doi.org/10.1016/j.sigpro.2026.110603
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Dict, Optional, Tuple, Union
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
from scipy import sparse
|
|
18
|
+
from scipy.sparse.linalg import spsolve
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Packaged demo data
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
_DATA_DIR = Path(__file__).resolve().parent / "data"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _load_complex_npy(name: str) -> np.ndarray:
|
|
29
|
+
path = _DATA_DIR / name
|
|
30
|
+
if not path.is_file():
|
|
31
|
+
raise FileNotFoundError(
|
|
32
|
+
f"Missing VTFMTD demo data: {path}. "
|
|
33
|
+
"Reinstall PySDKit or restore pysdkit/_tfa/data/"
|
|
34
|
+
)
|
|
35
|
+
return np.asarray(np.load(path))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_dual_signal_noise() -> Dict[str, Union[np.ndarray, float]]:
|
|
39
|
+
"""
|
|
40
|
+
Load the packaged dual-component noisy complex demo
|
|
41
|
+
(MATLAB ``Dual_signal_noise.mat``).
|
|
42
|
+
|
|
43
|
+
Sampling rate is ``fs = 3000`` Hz and length is 3000 samples (1 s),
|
|
44
|
+
matching ``test1.m``.
|
|
45
|
+
"""
|
|
46
|
+
signal = _load_complex_npy("dual_signal_noise.npy").astype(np.complex128).ravel()
|
|
47
|
+
fs = 3000.0
|
|
48
|
+
t = np.arange(1, signal.size + 1, dtype=float) / fs
|
|
49
|
+
return {"signal": signal, "fs": fs, "t": t, "K": 2}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def load_single_nsignal() -> Dict[str, Union[np.ndarray, float]]:
|
|
53
|
+
"""
|
|
54
|
+
Load the packaged single-component noisy micro-Doppler demo
|
|
55
|
+
(MATLAB ``Single_nsignal.mat``).
|
|
56
|
+
|
|
57
|
+
Sampling rate is ``fs = 8011`` Hz and length is 8011 samples (1 s),
|
|
58
|
+
matching ``test2.m``.
|
|
59
|
+
"""
|
|
60
|
+
signal = _load_complex_npy("single_nsignal.npy").astype(np.complex128).ravel()
|
|
61
|
+
fs = 8011.0
|
|
62
|
+
t = np.arange(signal.size, dtype=float) / fs
|
|
63
|
+
return {"signal": signal, "fs": fs, "t": t, "K": 1}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def load_map2() -> np.ndarray:
|
|
67
|
+
"""Load the packaged MATLAB ``map2`` colormap (shape ``(64, 3)``)."""
|
|
68
|
+
return np.asarray(np.load(_DATA_DIR / "map2.npy"), dtype=float)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# STFT (MATLAB STFT.m)
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def stft(signal: np.ndarray, hlength: Optional[int] = None) -> np.ndarray:
|
|
77
|
+
"""
|
|
78
|
+
Short-time Fourier transform used by VTFMTD (MATLAB ``STFT.m``).
|
|
79
|
+
|
|
80
|
+
Uses a Gaussian analysis window and hop size 1 (one column per sample).
|
|
81
|
+
Only the positive-frequency half of the FFT is retained.
|
|
82
|
+
|
|
83
|
+
:param signal: 1D array (real or complex), length ``N``
|
|
84
|
+
:param hlength: window length; defaults to ``round(N / 8)``. Odd length
|
|
85
|
+
is enforced as in MATLAB (``hlength + 1 - rem(hlength, 2)``).
|
|
86
|
+
:return: STFT matrix of shape ``(F, N)`` with ``F = round(N / 2)``
|
|
87
|
+
"""
|
|
88
|
+
x = np.asarray(signal).ravel()
|
|
89
|
+
n = int(x.size)
|
|
90
|
+
if n < 2:
|
|
91
|
+
raise ValueError("signal length must be >= 2")
|
|
92
|
+
|
|
93
|
+
if hlength is None:
|
|
94
|
+
hlength = int(round(n / 8.0))
|
|
95
|
+
hlength = int(hlength)
|
|
96
|
+
hlength = hlength + 1 - (hlength % 2)
|
|
97
|
+
|
|
98
|
+
ht = np.linspace(-0.5, 0.5, hlength)
|
|
99
|
+
h = np.exp(-np.pi / (0.32**2) * ht**2)
|
|
100
|
+
lh = (h.size - 1) // 2
|
|
101
|
+
|
|
102
|
+
f_bins = int(round(n / 2.0))
|
|
103
|
+
tfr = np.zeros((n, n), dtype=np.complex128)
|
|
104
|
+
|
|
105
|
+
# Vectorized over time for each lag tau (equivalent to MATLAB STFT.m)
|
|
106
|
+
half_n = n // 2 - 1
|
|
107
|
+
for tau_i, w in enumerate(h):
|
|
108
|
+
tau = int(tau_i - lh)
|
|
109
|
+
if abs(tau) > half_n:
|
|
110
|
+
continue
|
|
111
|
+
row = int(np.mod(n + tau, n))
|
|
112
|
+
# columns ti with valid sample index ti + tau
|
|
113
|
+
ti_lo = max(0, -tau)
|
|
114
|
+
ti_hi = min(n - 1, n - 1 - tau)
|
|
115
|
+
if ti_lo > ti_hi:
|
|
116
|
+
continue
|
|
117
|
+
cols = np.arange(ti_lo, ti_hi + 1)
|
|
118
|
+
tfr[row, cols] = x[cols + tau] * np.conj(w)
|
|
119
|
+
|
|
120
|
+
tfr = np.fft.fft(tfr, axis=0)
|
|
121
|
+
tfr = tfr[:f_bins, :]
|
|
122
|
+
return tfr / n * 2.0
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def frequency_axis(n: int, fs: float) -> np.ndarray:
|
|
126
|
+
"""
|
|
127
|
+
Frequency axis (Hz) matching MATLAB demos:
|
|
128
|
+
``f = (0 : round(N/2)-1) * fs / N``.
|
|
129
|
+
"""
|
|
130
|
+
n = int(n)
|
|
131
|
+
f_bins = int(round(n / 2.0))
|
|
132
|
+
return np.arange(f_bins, dtype=float) * (float(fs) / n)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def bin_index_grid(f_bins: int, n_time: int) -> np.ndarray:
|
|
136
|
+
"""
|
|
137
|
+
MATLAB ``omega = repmat((1:F)', 1, T)`` — 1-based STFT bin indices
|
|
138
|
+
broadcast to shape ``(F, T)``.
|
|
139
|
+
"""
|
|
140
|
+
return np.tile(np.arange(1, f_bins + 1, dtype=float)[:, None], (1, n_time))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def expand_omega_init(
|
|
144
|
+
omega_init: np.ndarray,
|
|
145
|
+
f_bins: int,
|
|
146
|
+
n_time: int,
|
|
147
|
+
k_modes: int,
|
|
148
|
+
) -> np.ndarray:
|
|
149
|
+
"""
|
|
150
|
+
Broadcast initial IFs to shape ``(F, T, K)``.
|
|
151
|
+
|
|
152
|
+
Accepted forms:
|
|
153
|
+
- ``(F, T, K)`` — used as-is
|
|
154
|
+
- ``(K,)`` or ``(K, 1)`` — constant IF (bin index) per mode
|
|
155
|
+
- ``(T, K)`` or ``(K, T)`` — time-varying IF per mode (no frequency axis)
|
|
156
|
+
"""
|
|
157
|
+
w = np.asarray(omega_init, dtype=float)
|
|
158
|
+
if w.shape == (f_bins, n_time, k_modes):
|
|
159
|
+
return w.copy()
|
|
160
|
+
|
|
161
|
+
out = np.zeros((f_bins, n_time, k_modes), dtype=float)
|
|
162
|
+
if w.ndim == 1 and w.size == k_modes:
|
|
163
|
+
for k in range(k_modes):
|
|
164
|
+
out[:, :, k] = w[k]
|
|
165
|
+
return out
|
|
166
|
+
if w.shape == (k_modes, 1):
|
|
167
|
+
for k in range(k_modes):
|
|
168
|
+
out[:, :, k] = float(w[k, 0])
|
|
169
|
+
return out
|
|
170
|
+
if w.shape == (n_time, k_modes):
|
|
171
|
+
for k in range(k_modes):
|
|
172
|
+
out[:, :, k] = w[:, k][None, :]
|
|
173
|
+
return out
|
|
174
|
+
if w.shape == (k_modes, n_time):
|
|
175
|
+
for k in range(k_modes):
|
|
176
|
+
out[:, :, k] = w[k, :][None, :]
|
|
177
|
+
return out
|
|
178
|
+
raise ValueError(
|
|
179
|
+
"omega_init must have shape (F,T,K), (K,), (T,K) or (K,T); "
|
|
180
|
+
f"got {w.shape} for F={f_bins}, T={n_time}, K={k_modes}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def first_difference_gram(n_time: int) -> sparse.csc_matrix:
|
|
185
|
+
"""
|
|
186
|
+
Build ``D.T @ D`` for the first-order difference operator in MATLAB
|
|
187
|
+
``VTFMTD.m`` (``spdiags`` with ``D(1,1)=1``).
|
|
188
|
+
"""
|
|
189
|
+
t = int(n_time)
|
|
190
|
+
# D: diagonal 1, sub-diagonal -1
|
|
191
|
+
d0 = np.ones(t)
|
|
192
|
+
d_m1 = -np.ones(t - 1)
|
|
193
|
+
d = sparse.diags([d_m1, d0], [-1, 0], shape=(t, t), format="csc")
|
|
194
|
+
return (d.T @ d).tocsc()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def estimate_if_centroid(
|
|
198
|
+
mode_stft: np.ndarray,
|
|
199
|
+
bin_grid: np.ndarray,
|
|
200
|
+
eps: float = np.finfo(float).eps,
|
|
201
|
+
) -> np.ndarray:
|
|
202
|
+
"""
|
|
203
|
+
Centroid IF estimate (MATLAB ``omega_est`` update).
|
|
204
|
+
|
|
205
|
+
:param mode_stft: complex STFT of one mode, shape ``(F, T)``
|
|
206
|
+
:param bin_grid: frequency-bin indices, shape ``(F, T)``
|
|
207
|
+
:return: IF field shape ``(F, T)`` (constant along frequency)
|
|
208
|
+
"""
|
|
209
|
+
power = np.abs(mode_stft) ** 2
|
|
210
|
+
numerator = np.sum(bin_grid * power, axis=0)
|
|
211
|
+
denominator = np.sum(power, axis=0)
|
|
212
|
+
if_traj = np.zeros(denominator.shape, dtype=float)
|
|
213
|
+
ok = denominator >= eps
|
|
214
|
+
if_traj[ok] = numerator[ok] / denominator[ok]
|
|
215
|
+
return np.tile(if_traj[None, :], (mode_stft.shape[0], 1))
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def smooth_if(
|
|
219
|
+
omega_est: np.ndarray,
|
|
220
|
+
gram: sparse.csc_matrix,
|
|
221
|
+
beta: float,
|
|
222
|
+
) -> np.ndarray:
|
|
223
|
+
"""
|
|
224
|
+
Smooth IF trajectories (MATLAB ``omega_smooth`` update).
|
|
225
|
+
|
|
226
|
+
Solves ``(2/beta * D'D + I) x = omega_est`` once per mode row pattern.
|
|
227
|
+
Because centroids are constant along frequency, only one solve per
|
|
228
|
+
time-series is required, then broadcast.
|
|
229
|
+
"""
|
|
230
|
+
f_bins, n_time = omega_est.shape
|
|
231
|
+
beta = float(beta)
|
|
232
|
+
eye = sparse.eye(n_time, format="csc")
|
|
233
|
+
system = ((2.0 / beta) * gram + eye).tocsc()
|
|
234
|
+
# all frequency rows share the same target trajectory
|
|
235
|
+
target = omega_est[0, :].astype(float)
|
|
236
|
+
smoothed = spsolve(system, target)
|
|
237
|
+
return np.tile(np.asarray(smoothed, dtype=float)[None, :], (f_bins, 1))
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def moving_average_if(if_traj: np.ndarray, win: int = 30) -> np.ndarray:
|
|
241
|
+
"""
|
|
242
|
+
Post-smoothing used in MATLAB demos (``Lth`` moving average on IF).
|
|
243
|
+
|
|
244
|
+
:param if_traj: 1D IF trajectory
|
|
245
|
+
:param win: odd/even window length (``Lth`` in the demos)
|
|
246
|
+
"""
|
|
247
|
+
x = np.asarray(if_traj, dtype=float).ravel()
|
|
248
|
+
m = x.size
|
|
249
|
+
half = int(round(win / 2.0))
|
|
250
|
+
out = np.empty(m, dtype=float)
|
|
251
|
+
for i in range(m):
|
|
252
|
+
lo = max(0, i - half)
|
|
253
|
+
hi = min(m, i + half + 1)
|
|
254
|
+
out[i] = np.mean(x[lo:hi])
|
|
255
|
+
return out
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def omega_bins_to_hz(omega_bins: np.ndarray, fs: float, n: int) -> np.ndarray:
|
|
259
|
+
"""
|
|
260
|
+
Convert MATLAB-style 1-based bin IF values to Hertz.
|
|
261
|
+
|
|
262
|
+
STFT row ``j`` (1-based) corresponds to frequency ``(j - 1) * fs / n``.
|
|
263
|
+
"""
|
|
264
|
+
return (np.asarray(omega_bins, dtype=float) - 1.0) * (float(fs) / float(n))
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
# Core algorithm
|
|
269
|
+
# ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def vtfmtd(
|
|
273
|
+
signal: np.ndarray,
|
|
274
|
+
hlength: int,
|
|
275
|
+
K: int,
|
|
276
|
+
omega_init: np.ndarray,
|
|
277
|
+
alpha: float = 1e-5,
|
|
278
|
+
sigma: float = 1e-2,
|
|
279
|
+
beta: float = 1.0,
|
|
280
|
+
max_iter: int = 100,
|
|
281
|
+
epsilon: float = 1e-3,
|
|
282
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
283
|
+
"""
|
|
284
|
+
Functional VTFMTD interface (MATLAB ``VTFMTD``).
|
|
285
|
+
|
|
286
|
+
:param signal: 1D real/complex signal
|
|
287
|
+
:param hlength: STFT window length
|
|
288
|
+
:param K: number of modes
|
|
289
|
+
:param omega_init: initial IF field / centers (see ``expand_omega_init``)
|
|
290
|
+
:param alpha: bandwidth penalty (larger -> narrower TF support)
|
|
291
|
+
:param sigma: dual-ascent step size
|
|
292
|
+
:param beta: IF smoothness penalty (smaller -> smoother IF)
|
|
293
|
+
:param max_iter: maximum ADMM iterations
|
|
294
|
+
:param epsilon: relative convergence tolerance on mode STFTs
|
|
295
|
+
:return: ``(Gk, omega_smooth)`` with shapes ``(F, T, K)`` and ``(F, T, K)``
|
|
296
|
+
"""
|
|
297
|
+
x = np.asarray(signal).ravel()
|
|
298
|
+
g = stft(x, hlength=hlength)
|
|
299
|
+
f_bins, n_time = g.shape
|
|
300
|
+
k_modes = int(K)
|
|
301
|
+
|
|
302
|
+
omega_smooth = expand_omega_init(omega_init, f_bins, n_time, k_modes)
|
|
303
|
+
gk = np.zeros((f_bins, n_time, k_modes), dtype=np.complex128)
|
|
304
|
+
lam = np.zeros((f_bins, n_time), dtype=np.complex128)
|
|
305
|
+
bin_grid = bin_index_grid(f_bins, n_time)
|
|
306
|
+
gram = first_difference_gram(n_time)
|
|
307
|
+
|
|
308
|
+
alpha = float(alpha)
|
|
309
|
+
sigma = float(sigma)
|
|
310
|
+
beta = float(beta)
|
|
311
|
+
epsilon = float(epsilon)
|
|
312
|
+
|
|
313
|
+
for it in range(int(max_iter)):
|
|
314
|
+
gk_prev = gk.copy()
|
|
315
|
+
|
|
316
|
+
# ----- update mode STFTs (TF Wiener filtering) -----
|
|
317
|
+
for k in range(k_modes):
|
|
318
|
+
sum_others = np.sum(gk, axis=2) - gk[:, :, k]
|
|
319
|
+
denom = 1.0 + alpha * (bin_grid - omega_smooth[:, :, k]) ** 2
|
|
320
|
+
gk[:, :, k] = (g - sum_others - lam / 2.0) / denom
|
|
321
|
+
|
|
322
|
+
# ----- centroid IF + smoothing -----
|
|
323
|
+
for k in range(k_modes):
|
|
324
|
+
omega_est = estimate_if_centroid(gk[:, :, k], bin_grid)
|
|
325
|
+
omega_smooth[:, :, k] = smooth_if(omega_est, gram, beta=beta)
|
|
326
|
+
|
|
327
|
+
# ----- dual ascent -----
|
|
328
|
+
lam = lam + sigma * (g - np.sum(gk, axis=2))
|
|
329
|
+
|
|
330
|
+
if it > 0:
|
|
331
|
+
diff = np.sum(np.abs(gk - gk_prev))
|
|
332
|
+
norm = np.sum(np.abs(gk_prev)) + np.finfo(float).eps
|
|
333
|
+
if diff < epsilon * norm:
|
|
334
|
+
break
|
|
335
|
+
|
|
336
|
+
return gk, omega_smooth
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class VTFMTD(object):
|
|
340
|
+
"""
|
|
341
|
+
Variational Time-Frequency Mode Tracking Decomposition.
|
|
342
|
+
|
|
343
|
+
Decomposes the STFT of a (possibly complex) 1D signal into ``K`` mode
|
|
344
|
+
STFTs while tracking instantaneous-frequency ridges via ADMM, following
|
|
345
|
+
Dong et al., Signal Processing 246:110603, 2026.
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
def __init__(
|
|
349
|
+
self,
|
|
350
|
+
hlength: int = 30,
|
|
351
|
+
K: int = 2,
|
|
352
|
+
alpha: float = 1e-5,
|
|
353
|
+
sigma: float = 1e-2,
|
|
354
|
+
beta: float = 1.0,
|
|
355
|
+
max_iter: int = 100,
|
|
356
|
+
epsilon: float = 1e-3,
|
|
357
|
+
) -> None:
|
|
358
|
+
"""
|
|
359
|
+
:param hlength: STFT window length
|
|
360
|
+
:param K: number of TF modes
|
|
361
|
+
:param alpha: TF bandwidth penalty
|
|
362
|
+
:param sigma: Lagrange dual step
|
|
363
|
+
:param beta: IF smoothness penalty
|
|
364
|
+
:param max_iter: maximum iterations
|
|
365
|
+
:param epsilon: convergence tolerance
|
|
366
|
+
"""
|
|
367
|
+
self.hlength = int(hlength)
|
|
368
|
+
self.K = int(K)
|
|
369
|
+
self.alpha = float(alpha)
|
|
370
|
+
self.sigma = float(sigma)
|
|
371
|
+
self.beta = float(beta)
|
|
372
|
+
self.max_iter = int(max_iter)
|
|
373
|
+
self.epsilon = float(epsilon)
|
|
374
|
+
|
|
375
|
+
self.signal: Optional[np.ndarray] = None
|
|
376
|
+
self.stft_signal: Optional[np.ndarray] = None
|
|
377
|
+
self.Gk: Optional[np.ndarray] = None
|
|
378
|
+
self.omega: Optional[np.ndarray] = None
|
|
379
|
+
|
|
380
|
+
def __str__(self) -> str:
|
|
381
|
+
return "Variational Time-Frequency Mode Tracking Decomposition (VTFMTD)"
|
|
382
|
+
|
|
383
|
+
def __call__(
|
|
384
|
+
self,
|
|
385
|
+
signal: np.ndarray,
|
|
386
|
+
omega_init: np.ndarray,
|
|
387
|
+
return_all: bool = False,
|
|
388
|
+
):
|
|
389
|
+
return self.fit_transform(
|
|
390
|
+
signal=signal, omega_init=omega_init, return_all=return_all
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
def fit_transform(
|
|
394
|
+
self,
|
|
395
|
+
signal: np.ndarray,
|
|
396
|
+
omega_init: np.ndarray,
|
|
397
|
+
return_all: bool = False,
|
|
398
|
+
) -> Union[
|
|
399
|
+
Tuple[np.ndarray, np.ndarray], Tuple[np.ndarray, np.ndarray, np.ndarray]
|
|
400
|
+
]:
|
|
401
|
+
"""
|
|
402
|
+
Run VTFMTD.
|
|
403
|
+
|
|
404
|
+
:param signal: 1D real/complex input
|
|
405
|
+
:param omega_init: initial IFs (bin units), see ``expand_omega_init``
|
|
406
|
+
:param return_all: if True, also return the composite STFT
|
|
407
|
+
:return: ``(Gk, omega)`` or ``(Gk, omega, G)``
|
|
408
|
+
"""
|
|
409
|
+
x = np.asarray(signal).ravel()
|
|
410
|
+
gk, omega = vtfmtd(
|
|
411
|
+
signal=x,
|
|
412
|
+
hlength=self.hlength,
|
|
413
|
+
K=self.K,
|
|
414
|
+
omega_init=omega_init,
|
|
415
|
+
alpha=self.alpha,
|
|
416
|
+
sigma=self.sigma,
|
|
417
|
+
beta=self.beta,
|
|
418
|
+
max_iter=self.max_iter,
|
|
419
|
+
epsilon=self.epsilon,
|
|
420
|
+
)
|
|
421
|
+
g = stft(x, hlength=self.hlength)
|
|
422
|
+
|
|
423
|
+
self.signal = x
|
|
424
|
+
self.stft_signal = g
|
|
425
|
+
self.Gk = gk
|
|
426
|
+
self.omega = omega
|
|
427
|
+
|
|
428
|
+
if return_all:
|
|
429
|
+
return gk, omega, g
|
|
430
|
+
return gk, omega
|
|
431
|
+
|
|
432
|
+
def if_trajectories(self) -> np.ndarray:
|
|
433
|
+
"""
|
|
434
|
+
Return IF trajectories with shape ``(K, T)`` (bin units),
|
|
435
|
+
taken from the first frequency row of ``omega`` (MATLAB convention).
|
|
436
|
+
"""
|
|
437
|
+
if self.omega is None:
|
|
438
|
+
raise ValueError("Call fit_transform before requesting IF trajectories.")
|
|
439
|
+
return np.asarray(self.omega[0, :, :], dtype=float).T.copy()
|