PySDKit 0.4.22__tar.gz → 0.4.24__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.22 → pysdkit-0.4.24}/PKG-INFO +2 -2
- {pysdkit-0.4.22 → pysdkit-0.4.24}/PySDKit.egg-info/PKG-INFO +2 -2
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/__init__.py +6 -1
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/ceemdan.py +33 -83
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/emd.py +1 -2
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ewt/ewt.py +3 -3
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_fmd/__init__.py +1 -0
- pysdkit-0.4.24/pysdkit/_fmd/fmd.py +302 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_jmd/jmd.py +1 -3
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_lmd/rlmd.py +9 -5
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/base.py +3 -3
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/svmd.py +1 -2
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd2d/cvmd2d.py +8 -14
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_test_univariate.py +4 -14
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/models/test_knn.py +6 -2
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/models/test_pca.py +6 -2
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_ceemdan.py +29 -38
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_ewt.py +9 -3
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_faemd.py +12 -4
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_moving_decomp.py +6 -2
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_vmd2d.py +12 -4
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tsa/_stl.py +2 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/setup.py +2 -2
- pysdkit-0.4.22/pysdkit/_fmd/fmd.py +0 -149
- {pysdkit-0.4.22 → pysdkit-0.4.24}/LICENSE +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/PySDKit.egg-info/SOURCES.txt +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/PySDKit.egg-info/dependency_links.txt +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/PySDKit.egg-info/requires.txt +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/PySDKit.egg-info/top_level.txt +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/README.md +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/_find_extrema.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/_prepare_points.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/_splines.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/eemd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/efd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/hht/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/hht/frequency.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/hht/hht.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/memd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/remd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd/tvf_emd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd2d/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd2d/bemd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd2d/bmemd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_emd2d/emd2d.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_esmd/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_esmd/esmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ewt/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ewt/ewt2d.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/extrema.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/faemd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/faemd2d.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/faemd3d.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_faemd/filter.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_fmd/nfmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_hvd/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_hvd/hvd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ifd/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_itd/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_itd/itd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_jmd/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_jmd/mjmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_lmd/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_lmd/lmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_osd/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ssa/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_ssa/ssa.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/acmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/avmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/ba_acmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/mvmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/vmd_c.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/vmd_f.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd/vme.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd2d/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vmd2d/vmd2d.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vncmd/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vncmd/avncmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vncmd/incmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vncmd/mncmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/_vncmd/vncmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_add_noise.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_cube.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_generator.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_image.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_models.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/_time_series.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/data/texture.txt +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/entropy/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/entropy/_permutation_entropy.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/entropy/_sample_entropy.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/models/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/models/_base.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/models/_kmeans.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/models/_knn.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/models/_pca.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/_fourier_spectra.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/_functions.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/_plot_images.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/_plot_imfs.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/plot/_plot_signal.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/data/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/data/test_generator_cube.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/data/test_generator_image.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/data/test_generator_signal.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/models/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_all.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_emd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_faemd2d.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_faemd3d.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_lmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_rlmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_stl.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tests/test_vmd.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tsa/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tsa/_dtw.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tsa/_moving_decomp.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/tsa/_mstl.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/__init__.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_cite.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_correlation.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_diagnalization.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_differ.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_fft.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_function.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_hilbert.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_instantaneous.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_kernel_matrix.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_mirror.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_process.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/pysdkit/utils/_smooth1d.py +0 -0
- {pysdkit-0.4.22 → pysdkit-0.4.24}/setup.cfg +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySDKit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.24
|
|
4
4
|
Summary: A Python library for signal decomposition algorithms with a unified interface.
|
|
5
5
|
Home-page: https://github.com/wwhenxuan/PySDKit
|
|
6
|
-
Author: whenxuan, changewam, josefinez, Yuan Feng
|
|
6
|
+
Author: whenxuan, changewam, josefinez, Yuan Feng, Wentong Zhao, JacktheFowler, Deeksha Manjunath
|
|
7
7
|
Author-email: wwhenxuan@gmail.com
|
|
8
8
|
Keywords: signal decomposition,signal processing,machine learning
|
|
9
9
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySDKit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.24
|
|
4
4
|
Summary: A Python library for signal decomposition algorithms with a unified interface.
|
|
5
5
|
Home-page: https://github.com/wwhenxuan/PySDKit
|
|
6
|
-
Author: whenxuan, changewam, josefinez, Yuan Feng
|
|
6
|
+
Author: whenxuan, changewam, josefinez, Yuan Feng, Wentong Zhao, JacktheFowler, Deeksha Manjunath
|
|
7
7
|
Author-email: wwhenxuan@gmail.com
|
|
8
8
|
Keywords: signal decomposition,signal processing,machine learning
|
|
9
9
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -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.24"
|
|
6
6
|
|
|
7
7
|
# Empirical Mode Decomposition
|
|
8
8
|
from ._emd import EMD
|
|
@@ -76,6 +76,9 @@ from ._ewt import ewt, EWT
|
|
|
76
76
|
# Jump Plus AM-FM Mode Decomposition
|
|
77
77
|
from ._jmd import JMD
|
|
78
78
|
|
|
79
|
+
# Feature Mode Decomposition
|
|
80
|
+
from ._fmd import FMD
|
|
81
|
+
|
|
79
82
|
# Moving Average Decomposition
|
|
80
83
|
from .tsa import Moving_Decomp
|
|
81
84
|
|
|
@@ -132,6 +135,7 @@ Variational Nonlinear Chirp Mode Decomposition | VNCMD
|
|
|
132
135
|
Iterative Nonlinear Chirp Mode Decomposition | INCMD
|
|
133
136
|
Empirical Wavelet Transform | EWT
|
|
134
137
|
Jump Plus AM-FM Mode Decomposition | JMD
|
|
138
|
+
Feature Mode Decomposition | FMD
|
|
135
139
|
Moving Average Decomposition | Moving
|
|
136
140
|
Seasonal-Trend decomposition using LOESS | STL
|
|
137
141
|
Hilbert-Huang Transform | HHT
|
|
@@ -167,6 +171,7 @@ __all__ = [
|
|
|
167
171
|
"ewt",
|
|
168
172
|
"EWT",
|
|
169
173
|
"JMD",
|
|
174
|
+
"FMD",
|
|
170
175
|
"Moving_Decomp",
|
|
171
176
|
"STL",
|
|
172
177
|
"models",
|
|
@@ -71,49 +71,38 @@ class CEEMDAN(object):
|
|
|
71
71
|
:param max_iter: maximum number of decomposition iterations
|
|
72
72
|
:param random_seed: create a random seed for the random number generator
|
|
73
73
|
"""
|
|
74
|
-
# Fixed configuration of the EMD algorithm
|
|
75
74
|
self.max_imfs = max_imfs
|
|
75
|
+
self._max_imf_iters = 100
|
|
76
76
|
self.trials = trials
|
|
77
77
|
self.epsilon = epsilon
|
|
78
78
|
self.max_iter = max_iter
|
|
79
79
|
|
|
80
|
-
# Configuration parameters related to multi-process
|
|
81
80
|
self.parallel = parallel
|
|
82
81
|
self.processes = processes
|
|
83
82
|
|
|
84
|
-
# Parameters for creating noise
|
|
85
83
|
self.noise_scale = noise_scale
|
|
86
84
|
self.noise_kind = noise_kind
|
|
87
85
|
self.noise_list = ["normal", "uniform"]
|
|
88
|
-
# Whether to normalize the decomposed noise
|
|
89
86
|
self.beta_progress = beta_progress
|
|
90
87
|
|
|
91
|
-
# Thresholds related to algorithm stopping
|
|
92
88
|
self.range_thr = range_thr
|
|
93
89
|
self.total_power_thr = total_power_thr
|
|
94
90
|
|
|
95
|
-
# Create the EMD algorithm to be integrated
|
|
96
91
|
self.EMD = (
|
|
97
92
|
EMD(max_imfs=max_imfs, random_seed=random_seed, max_iteration=self.max_iter)
|
|
98
93
|
if ext_EMD is None
|
|
99
94
|
else ext_EMD
|
|
100
95
|
)
|
|
101
96
|
|
|
102
|
-
# Creating a random number generator
|
|
103
97
|
self.random_seed = random_seed
|
|
104
98
|
self.rng = np.random.RandomState(seed=random_seed)
|
|
105
99
|
|
|
106
|
-
# Generated noise sequence
|
|
107
100
|
self.all_noises = None
|
|
108
|
-
|
|
109
|
-
# List of noises to be decomposed
|
|
110
101
|
self.all_noise_EMD = []
|
|
111
102
|
|
|
112
|
-
# Record the results of this algorithm decomposition
|
|
113
103
|
self.imfs = None
|
|
114
104
|
self.residue = None
|
|
115
105
|
|
|
116
|
-
# Used to update the trial
|
|
117
106
|
self._signal, self._time, self._seq_len, self._scale = None, None, None, None
|
|
118
107
|
|
|
119
108
|
def __call__(
|
|
@@ -171,9 +160,8 @@ class CEEMDAN(object):
|
|
|
171
160
|
else:
|
|
172
161
|
all_noise_EMD = [self.EMD(noise, max_imfs=-1) for noise in self.all_noises]
|
|
173
162
|
|
|
174
|
-
# Normalize the decomposed noise sequences
|
|
175
163
|
if self.beta_progress:
|
|
176
|
-
all_stds = [np.
|
|
164
|
+
all_stds = [np.std(imfs[0]) for imfs in all_noise_EMD]
|
|
177
165
|
all_noise_EMD = [
|
|
178
166
|
imfs / imfs_std for (imfs, imfs_std) in zip(all_noise_EMD, all_stds)
|
|
179
167
|
]
|
|
@@ -188,57 +176,45 @@ class CEEMDAN(object):
|
|
|
188
176
|
progress: Optional[bool] = True,
|
|
189
177
|
) -> np.ndarray:
|
|
190
178
|
"""Perform the specified EEMD algorithm to obtain the corresponding signal decomposition results."""
|
|
191
|
-
# Length of the signal
|
|
192
179
|
seq_len = len(signal)
|
|
193
180
|
|
|
194
|
-
# Generate the time array
|
|
195
181
|
if time is None:
|
|
196
182
|
time = get_timeline(seq_len, dtype=signal.dtype)
|
|
197
183
|
|
|
198
|
-
# Store the current signal state
|
|
199
184
|
self._signal, self._time, self._seq_len = signal, time, seq_len
|
|
185
|
+
self._eemd_max_imfs = max_imfs if max_imfs is not None else self.max_imfs
|
|
200
186
|
|
|
201
|
-
if max_imfs is not None:
|
|
202
|
-
self.max_imfs = max_imfs
|
|
203
|
-
|
|
204
|
-
# For the specified number of trials, perform EMD on the signal with added white noise
|
|
205
187
|
if self.parallel:
|
|
206
188
|
pool = Pool(processes=self.processes)
|
|
207
189
|
map_pool = pool.imap_unordered
|
|
208
|
-
else:
|
|
190
|
+
else:
|
|
209
191
|
map_pool = map
|
|
210
192
|
|
|
211
|
-
|
|
212
|
-
self.imfs = np.zeros((1, seq_len))
|
|
193
|
+
eemd_imfs = np.zeros((1, seq_len))
|
|
213
194
|
|
|
214
|
-
# Create an iterator for the decomposition process
|
|
215
195
|
it = (
|
|
216
196
|
iter
|
|
217
197
|
if not progress
|
|
218
198
|
else lambda x: tqdm(x, desc="Decomposing noise", total=self.trials)
|
|
219
199
|
)
|
|
220
200
|
|
|
221
|
-
# Perform signal decomposition and store the results
|
|
222
201
|
for IMFs in it(map_pool(self._update_trial, range(self.trials))):
|
|
223
|
-
if
|
|
224
|
-
num_new_layers = IMFs.shape[0] -
|
|
225
|
-
|
|
226
|
-
(
|
|
202
|
+
if eemd_imfs.shape[0] < IMFs.shape[0]:
|
|
203
|
+
num_new_layers = IMFs.shape[0] - eemd_imfs.shape[0]
|
|
204
|
+
eemd_imfs = np.vstack(
|
|
205
|
+
(eemd_imfs, np.zeros(shape=(num_new_layers, seq_len)))
|
|
227
206
|
)
|
|
228
|
-
|
|
207
|
+
eemd_imfs[: IMFs.shape[0]] += IMFs
|
|
229
208
|
|
|
230
209
|
if self.parallel:
|
|
231
210
|
pool.close()
|
|
232
211
|
|
|
233
|
-
return
|
|
212
|
+
return eemd_imfs / self.trials
|
|
234
213
|
|
|
235
214
|
def _update_trial(self, trial: int) -> np.ndarray:
|
|
236
215
|
"""A single trial evaluation, i.e., EMD(signal + noise)."""
|
|
237
|
-
# Generate noise
|
|
238
216
|
noise = self.epsilon * self.all_noise_EMD[trial][0]
|
|
239
|
-
|
|
240
|
-
# Return the result of a single EMD execution
|
|
241
|
-
return self._run_emd(self._signal + noise, self._time, self.max_imfs)
|
|
217
|
+
return self._run_emd(self._signal + noise, self._time, self._eemd_max_imfs)
|
|
242
218
|
|
|
243
219
|
def _run_emd(
|
|
244
220
|
self,
|
|
@@ -302,41 +278,34 @@ class CEEMDAN(object):
|
|
|
302
278
|
"""
|
|
303
279
|
Test for end condition for CEEMDAN method.
|
|
304
280
|
|
|
305
|
-
|
|
281
|
+
Procedure stops if:
|
|
282
|
+
* number of components reaches provided `max_imf`, or
|
|
283
|
+
* residue has insufficient extrema for further decomposition, or
|
|
284
|
+
* residue amplitude range falls below `range_thr`, or
|
|
285
|
+
* residue total power falls below `total_power_thr`.
|
|
306
286
|
|
|
307
287
|
:param signal: the original input signal as a numpy ndarray
|
|
308
288
|
:param cIMFs: the signal decomposition results
|
|
309
|
-
:param max_imf: the maximum number of IMFs
|
|
289
|
+
:param max_imf: the maximum number of IMFs to extract (-1 means no limit)
|
|
310
290
|
:return: a boolean stop flag indicating whether to stop the CEEMDAN method
|
|
311
291
|
"""
|
|
312
|
-
# Number of IMFs currently decomposed
|
|
313
292
|
imf_number = cIMFs.shape[0]
|
|
314
293
|
|
|
315
|
-
# Check if the maximum number of cIMFs has been reached
|
|
316
294
|
if 0 < max_imf <= imf_number:
|
|
317
295
|
return True
|
|
318
296
|
|
|
319
|
-
# Compute the Empirical Mode Decomposition (EMD) for the residue
|
|
320
297
|
R = signal - np.sum(cIMFs, axis=0)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
# print("Not enough extrema")
|
|
327
|
-
# return True
|
|
328
|
-
|
|
329
|
-
# Check for range threshold
|
|
298
|
+
|
|
299
|
+
_test_imfs = self._run_emd(signal=R, time=None, max_imfs=1)
|
|
300
|
+
if _test_imfs.shape[0] == 1:
|
|
301
|
+
return True
|
|
302
|
+
|
|
330
303
|
if np.max(R) - np.min(R) < self.range_thr:
|
|
331
|
-
print("Finished by Range")
|
|
332
304
|
return True
|
|
333
305
|
|
|
334
|
-
# Check for power threshold
|
|
335
306
|
if np.sum(np.abs(R)) < self.total_power_thr:
|
|
336
|
-
print("Finished by Power")
|
|
337
307
|
return True
|
|
338
308
|
|
|
339
|
-
# The algorithm did not stop
|
|
340
309
|
return False
|
|
341
310
|
|
|
342
311
|
def fit_transform(
|
|
@@ -351,81 +320,62 @@ class CEEMDAN(object):
|
|
|
351
320
|
|
|
352
321
|
:param signal: the original signal on which CEEMDAN is to be performed
|
|
353
322
|
:param time: the time array of the original input signal
|
|
354
|
-
:param max_imfs: the maximum number of components to extract
|
|
323
|
+
:param max_imfs: the maximum number of components to extract (-1 for no limit)
|
|
355
324
|
:param progress: whether to print out '.' every 1s to indicate progress
|
|
356
325
|
:return: the intrinsic mode functions (IMFs) after signal decomposition
|
|
357
326
|
"""
|
|
358
|
-
# Normalize the signal's amplitude
|
|
359
327
|
scale_s = np.std(signal)
|
|
360
328
|
signal = signal / scale_s
|
|
361
329
|
|
|
362
|
-
# Begin executing the specific decomposition algorithm
|
|
363
330
|
max_imfs = self.max_imfs if max_imfs is None else max_imfs
|
|
364
|
-
total = (max_imfs - 1) if max_imfs != -1 else None
|
|
365
331
|
|
|
366
|
-
# Define the noise sequences to be added
|
|
367
332
|
self.all_noises = self._generate_noise(
|
|
368
333
|
self.noise_scale, size=(self.trials, signal.size)
|
|
369
334
|
)
|
|
370
|
-
|
|
371
|
-
# Decompose all noise and remember the standard deviation of the 1st IMF
|
|
372
335
|
self.all_noise_EMD = self._decompose_noise()
|
|
373
336
|
|
|
374
|
-
# Create the first IMF
|
|
375
337
|
last_imf = self._run_eemd(signal, time, max_imfs=1, progress=progress)[0]
|
|
376
338
|
|
|
377
339
|
all_cimfs = last_imf.reshape((-1, last_imf.size))
|
|
378
340
|
prev_res = signal - last_imf
|
|
379
341
|
|
|
380
|
-
|
|
342
|
+
total = (max_imfs - 1) if max_imfs != -1 else None
|
|
381
343
|
it = (
|
|
382
344
|
iter
|
|
383
345
|
if not progress
|
|
384
346
|
else lambda x: tqdm(x, desc="cIMF decomposition", total=total)
|
|
385
347
|
)
|
|
386
348
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
imf_number = all_cimfs.shape[0]
|
|
349
|
+
for _ in it(range(self._max_imf_iters)):
|
|
350
|
+
if self.end_condition(signal=signal, cIMFs=all_cimfs, max_imf=max_imfs):
|
|
351
|
+
break
|
|
391
352
|
|
|
353
|
+
imf_number = all_cimfs.shape[0]
|
|
392
354
|
beta = self.epsilon * np.std(prev_res)
|
|
393
|
-
|
|
394
355
|
local_mean = np.zeros(signal.size)
|
|
395
356
|
|
|
396
357
|
for trial in range(self.trials):
|
|
397
|
-
# Skip if noise[trial] didn't have k'th mode
|
|
398
358
|
noise_imf = self.all_noise_EMD[trial]
|
|
399
359
|
res = prev_res.copy()
|
|
400
360
|
|
|
401
|
-
# add noise for every imf to be decomposed.
|
|
402
361
|
if len(noise_imf) > imf_number:
|
|
403
362
|
res += beta * noise_imf[imf_number]
|
|
404
363
|
|
|
405
|
-
# Extract the local mean, which is at the 2nd position
|
|
406
364
|
imfs = self._run_emd(res, time, max_imfs=1)
|
|
407
365
|
local_mean += imfs[-1] / self.trials
|
|
408
366
|
|
|
409
|
-
# Record the results of this decomposition
|
|
410
367
|
last_imf = prev_res - local_mean
|
|
411
|
-
# vstack the new imf
|
|
412
368
|
all_cimfs = np.vstack((all_cimfs, last_imf))
|
|
413
369
|
prev_res = local_mean.copy()
|
|
414
370
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
break
|
|
371
|
+
residue = signal - np.sum(all_cimfs, axis=0)
|
|
372
|
+
all_cimfs = np.vstack((all_cimfs, residue))
|
|
373
|
+
|
|
374
|
+
all_cimfs = all_cimfs * scale_s
|
|
420
375
|
|
|
421
|
-
# Clear all IMF noise
|
|
422
|
-
# The noise will be initialized for the new input signal
|
|
423
376
|
del self.all_noise_EMD[:]
|
|
424
377
|
|
|
425
|
-
# Record the results of this decomposition
|
|
426
378
|
self.imfs = all_cimfs
|
|
427
|
-
|
|
428
|
-
# Calculate the remaining residual components
|
|
429
379
|
self.residue = signal * scale_s - np.sum(self.imfs, axis=0)
|
|
430
380
|
|
|
431
381
|
return all_cimfs
|
|
@@ -519,8 +519,7 @@ class EMD(object):
|
|
|
519
519
|
IMF = np.vstack((IMF, imf.copy()))
|
|
520
520
|
imfNo += 1
|
|
521
521
|
|
|
522
|
-
if self.end_condition(signal, IMF) or imfNo
|
|
523
|
-
# Determine whether to stop iterative decomposition based on the original input signal and the intrinsic mode function
|
|
522
|
+
if self.end_condition(signal, IMF) or (max_imfs > 0 and imfNo >= max_imfs):
|
|
524
523
|
break
|
|
525
524
|
|
|
526
525
|
# If the last sifting had 2 or less extrema then that's a trend (residue)
|
|
@@ -251,9 +251,9 @@ def EWT_Boundaries_Detect(
|
|
|
251
251
|
|
|
252
252
|
elif reg == "gaussian":
|
|
253
253
|
regFilter = np.zeros(lengthFilter)
|
|
254
|
-
regFilter[
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
regFilter[regFilter.size // 2] = (
|
|
255
|
+
1 # Ensure center is set for Gaussian filtering
|
|
256
|
+
)
|
|
257
257
|
presig = np.convolve(
|
|
258
258
|
ff, gaussian_filter(regFilter, sigmaFilter), mode="same"
|
|
259
259
|
) # Gaussian filter
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on 2025/01/12 00:05:39
|
|
4
|
+
@author: Whenxuan Wang
|
|
5
|
+
@email: wwhenxuan@gmail.com
|
|
6
|
+
"""
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
import scipy.signal as signal
|
|
10
|
+
from scipy.fftpack import fft
|
|
11
|
+
from scipy.signal import firwin, hilbert
|
|
12
|
+
|
|
13
|
+
from typing import Optional, Union, Tuple
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FMD(object):
|
|
17
|
+
"""
|
|
18
|
+
Feature Mode Decomposition (FMD) class for decomposing a signal into a predefined
|
|
19
|
+
number of modes using iterative filter bank and kurtosis-based optimization.
|
|
20
|
+
|
|
21
|
+
Yonghao, Miao, et al. “Feature Mode Decomposition: New Decomposition Theory for Rotating Machinery Fault Diagnosis.”
|
|
22
|
+
IEEE Transactions on Industrial Electronics, Institute of Electrical and Electronics Engineers (IEEE), 2022, pp. 1–1, doi:10.1109/tie.2022.3156156.
|
|
23
|
+
Matlab Code: https://www.mathworks.com/matlabcentral/fileexchange/108099-feature-mode-decomposition-fmd
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
fs: Union[int, float],
|
|
29
|
+
mode_num: int = 2,
|
|
30
|
+
filter_size: Union[int, float] = 30,
|
|
31
|
+
cut_num: Optional[int] = 7,
|
|
32
|
+
max_iter_num: Optional[int] = 20,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Initialize the FMD instance with specified parameters.
|
|
36
|
+
|
|
37
|
+
:param alpha: (Union[int, float]),
|
|
38
|
+
Sampling frequency of the input signal.
|
|
39
|
+
:param mode_num: (int, optional),
|
|
40
|
+
Number of modes to extract from the input signal. Defaults to 2.
|
|
41
|
+
:param filter_size: (Union[int, float], optional),
|
|
42
|
+
Length of the FIR filters used in the filter bank. Defaults to 30.
|
|
43
|
+
:param cut_num: (Optional[int], optional),
|
|
44
|
+
Number of equally spaced sub-bands to split the frequency range [0, fs/2]. Defaults to 7.
|
|
45
|
+
:param max_iter_num: (Optional[int], optional),
|
|
46
|
+
Maximum number of iterations for filter optimization. Defaults to 20.
|
|
47
|
+
"""
|
|
48
|
+
self.fs = fs
|
|
49
|
+
self.filter_size = filter_size
|
|
50
|
+
self.cut_num = cut_num
|
|
51
|
+
self.mode_num = mode_num
|
|
52
|
+
self.max_iter_num = max_iter_num
|
|
53
|
+
|
|
54
|
+
def fit_transform(
|
|
55
|
+
self, x: np.ndarray, fs: Optional[Union[int, float]] = None
|
|
56
|
+
) -> np.ndarray:
|
|
57
|
+
"""
|
|
58
|
+
Decompose the input signal into modes using FMD.
|
|
59
|
+
|
|
60
|
+
:param x: (array-like),
|
|
61
|
+
Input signal to be decomposed.
|
|
62
|
+
:param fs: (Optional[Union[int, float]], optional),
|
|
63
|
+
Sampling frequency of the input signal. If None, uses the instance's fs. Defaults to None.
|
|
64
|
+
|
|
65
|
+
:return: (ndarray, shape (mode_num, len(x))),
|
|
66
|
+
Extracted modes after decomposition.
|
|
67
|
+
"""
|
|
68
|
+
# Initialize the input signal and parameters
|
|
69
|
+
x = np.asarray(x).flatten()
|
|
70
|
+
|
|
71
|
+
if fs is not None:
|
|
72
|
+
self.fs = fs
|
|
73
|
+
|
|
74
|
+
freq_bound = np.arange(0, 1, 1 / self.cut_num)
|
|
75
|
+
temp_filters = np.zeros((self.filter_size, self.cut_num))
|
|
76
|
+
|
|
77
|
+
# Initialize a filter bank with Hann-windowed FIR bandpass filters
|
|
78
|
+
for n, fb in enumerate(freq_bound):
|
|
79
|
+
b = firwin(
|
|
80
|
+
self.filter_size,
|
|
81
|
+
[fb + np.finfo(float).eps, fb + 1 / self.cut_num - np.finfo(float).eps],
|
|
82
|
+
window="hann",
|
|
83
|
+
pass_zero=False,
|
|
84
|
+
)
|
|
85
|
+
temp_filters[:, n] = b
|
|
86
|
+
|
|
87
|
+
temp_sig = np.tile(x[:, None], (1, self.cut_num))
|
|
88
|
+
itercount = 2
|
|
89
|
+
|
|
90
|
+
while True:
|
|
91
|
+
iternum = 2
|
|
92
|
+
if itercount == 2:
|
|
93
|
+
# Reduce iterations for non-target bands to speed convergence
|
|
94
|
+
iternum = self.max_iter_num - (self.cut_num - self.mode_num) * iternum
|
|
95
|
+
|
|
96
|
+
X = []
|
|
97
|
+
for n in range(temp_filters.shape[1]):
|
|
98
|
+
# Apply Multi-Cycle Kurtosis Deconvolution (MCKD) to refine each filter output
|
|
99
|
+
y_iter, f_iter, k_iter, T_iter = self._xxc_mckd(
|
|
100
|
+
temp_sig[:, n], temp_filters[:, n], iternum, M=1
|
|
101
|
+
)
|
|
102
|
+
freq_resp = np.abs(fft(f_iter))[: self.filter_size // 2]
|
|
103
|
+
peak_freq = (np.argmax(freq_resp)) * (self.fs / self.filter_size)
|
|
104
|
+
X.append(
|
|
105
|
+
[
|
|
106
|
+
y_iter[:, -1],
|
|
107
|
+
f_iter[:, -1],
|
|
108
|
+
k_iter[-1],
|
|
109
|
+
freq_resp,
|
|
110
|
+
peak_freq,
|
|
111
|
+
T_iter,
|
|
112
|
+
]
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Update signal matrix and filter bank using optimized filters
|
|
116
|
+
temp_sig = np.column_stack([xi[0] for xi in X])
|
|
117
|
+
temp_filters = np.column_stack([xi[1] for xi in X])
|
|
118
|
+
|
|
119
|
+
# Compute pairwise correlation and remove the most redundant component
|
|
120
|
+
corr_matrix = np.abs(np.corrcoef(temp_sig, rowvar=False))
|
|
121
|
+
corr_matrix = np.triu(corr_matrix, 1)
|
|
122
|
+
I, J, _ = _max_IJ(corr_matrix)
|
|
123
|
+
|
|
124
|
+
XI = temp_sig[:, I] - np.mean(temp_sig[:, I])
|
|
125
|
+
XJ = temp_sig[:, J] - np.mean(temp_sig[:, J])
|
|
126
|
+
|
|
127
|
+
# Compare cyclic kurtosis (CK) values to decide which component to drop
|
|
128
|
+
KI = _CK(XI, X[I][5], 1)
|
|
129
|
+
KJ = _CK(XJ, X[J][5], 1)
|
|
130
|
+
|
|
131
|
+
output = J if KI > KJ else I
|
|
132
|
+
temp_sig = np.delete(temp_sig, output, axis=1)
|
|
133
|
+
temp_filters = np.delete(temp_filters, output, axis=1)
|
|
134
|
+
|
|
135
|
+
if temp_filters.shape[1] == self.mode_num - 1:
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
itercount += 1
|
|
139
|
+
|
|
140
|
+
final_modes = np.column_stack([xi[0] for xi in X])
|
|
141
|
+
return final_modes.T
|
|
142
|
+
|
|
143
|
+
def _xxc_mckd(
|
|
144
|
+
self,
|
|
145
|
+
x: np.ndarray,
|
|
146
|
+
f_init: np.ndarray,
|
|
147
|
+
term_iter: int,
|
|
148
|
+
T: Optional[int] = None,
|
|
149
|
+
M: Optional[int] = 3,
|
|
150
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, int]:
|
|
151
|
+
"""
|
|
152
|
+
Multi-Cycle Kurtosis Deconvolution (MCKD) optimization for filter refinement.
|
|
153
|
+
|
|
154
|
+
:param x: (ndarray),
|
|
155
|
+
Input signal segment.
|
|
156
|
+
:param f_init: (ndarray),
|
|
157
|
+
Initial filter coefficients.
|
|
158
|
+
:param term_iter: (int),
|
|
159
|
+
Number of iterations to optimize filter.
|
|
160
|
+
:param T: (int, optional),
|
|
161
|
+
Cycle period (if None, estimated from signal envelope autocorrelation).
|
|
162
|
+
:param M: (int, optional, default=3),
|
|
163
|
+
Order of cyclic kurtosis.
|
|
164
|
+
|
|
165
|
+
:return: (Tuple[np.ndarray, np.ndarray, np.ndarray, int]),
|
|
166
|
+
y_final: Filtered signal at each iteration.
|
|
167
|
+
f_final: Optimized filter coefficients at each iteration.
|
|
168
|
+
ck_iter: CK values over iterations.
|
|
169
|
+
T: Estimated cycle period.
|
|
170
|
+
"""
|
|
171
|
+
if T is None:
|
|
172
|
+
env = np.abs(hilbert(x)) - np.mean(np.abs(hilbert(x)))
|
|
173
|
+
T = _TT(env)
|
|
174
|
+
|
|
175
|
+
T = int(round(T))
|
|
176
|
+
x = np.asarray(x).flatten()
|
|
177
|
+
L = len(f_init)
|
|
178
|
+
N = len(x)
|
|
179
|
+
|
|
180
|
+
# Build delayed signal matrix XmT for MCKD optimization
|
|
181
|
+
XmT = np.zeros((L, N, M + 1))
|
|
182
|
+
for m in range(M + 1):
|
|
183
|
+
for l in range(L):
|
|
184
|
+
if l == 0:
|
|
185
|
+
XmT[l, m * T :, m] = x[: N - m * T]
|
|
186
|
+
else:
|
|
187
|
+
XmT[l, 1:, m] = XmT[l - 1, :-1, m]
|
|
188
|
+
|
|
189
|
+
Xinv = np.linalg.inv(XmT[:, :, 0] @ XmT[:, :, 0].T)
|
|
190
|
+
|
|
191
|
+
f = f_init.copy()
|
|
192
|
+
y_final = []
|
|
193
|
+
f_final = []
|
|
194
|
+
ck_iter = []
|
|
195
|
+
|
|
196
|
+
for _ in range(term_iter):
|
|
197
|
+
y = XmT[:, :, 0].T @ f # filter output
|
|
198
|
+
|
|
199
|
+
# Construct shifted versions for cyclic statistics
|
|
200
|
+
yt = np.zeros((N, M + 1))
|
|
201
|
+
for m in range(M + 1):
|
|
202
|
+
yt[:, m] = (
|
|
203
|
+
y if m == 0 else np.concatenate([np.zeros(T), yt[:-T, m - 1]])
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Compute alpha (partial derivatives) and beta for filter update
|
|
207
|
+
alpha = np.zeros_like(yt)
|
|
208
|
+
for m in range(M + 1):
|
|
209
|
+
idx = [i for i in range(M + 1) if i != m]
|
|
210
|
+
alpha[:, m] = (np.prod(yt[:, idx], axis=1) ** 2) * yt[:, m]
|
|
211
|
+
|
|
212
|
+
beta = np.prod(yt, axis=1)
|
|
213
|
+
|
|
214
|
+
Xalpha = np.zeros(L)
|
|
215
|
+
for m in range(M + 1):
|
|
216
|
+
Xalpha += XmT[:, :, m] @ alpha[:, m]
|
|
217
|
+
|
|
218
|
+
# Update filter using gradient ascent on CK criterion
|
|
219
|
+
f = (np.sum(y**2) / (2 * np.sum(beta**2))) * (Xinv @ Xalpha)
|
|
220
|
+
f /= np.sqrt(np.sum(f**2))
|
|
221
|
+
|
|
222
|
+
# Compute CK value for convergence monitoring
|
|
223
|
+
ck_val = np.sum(np.prod(yt, axis=1) ** 2) / (np.sum(y**2) ** (M + 1))
|
|
224
|
+
ck_iter.append(ck_val)
|
|
225
|
+
|
|
226
|
+
# Update delay matrix with new cycle period estimate
|
|
227
|
+
env_y = np.abs(hilbert(y)) - np.mean(np.abs(hilbert(y)))
|
|
228
|
+
T = int(round(_TT(env_y)))
|
|
229
|
+
|
|
230
|
+
XmT = np.zeros((L, N, M + 1))
|
|
231
|
+
for m in range(M + 1):
|
|
232
|
+
for l in range(L):
|
|
233
|
+
if l == 0:
|
|
234
|
+
XmT[l, m * T :, m] = x[: N - m * T]
|
|
235
|
+
else:
|
|
236
|
+
XmT[l, 1:, m] = XmT[l - 1, :-1, m]
|
|
237
|
+
|
|
238
|
+
Xinv = np.linalg.inv(XmT[:, :, 0] @ XmT[:, :, 0].T)
|
|
239
|
+
|
|
240
|
+
y_final.append(signal.lfilter(f, [1], x))
|
|
241
|
+
f_final.append(f.copy())
|
|
242
|
+
|
|
243
|
+
y_final = np.column_stack(y_final) if len(y_final) > 1 else y_final[0][:, None]
|
|
244
|
+
f_final = np.column_stack(f_final) if len(f_final) > 1 else f_final[0][:, None]
|
|
245
|
+
|
|
246
|
+
return y_final, f_final, np.array(ck_iter), T
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _TT(y: np.ndarray) -> int:
|
|
250
|
+
"""Estimate signal period T using autocorrelation zero-crossing and peak."""
|
|
251
|
+
NA = signal.correlate(y, y, mode="full") / np.dot(y, y)
|
|
252
|
+
NA = NA[len(NA) // 2 :]
|
|
253
|
+
zeroposi = 1
|
|
254
|
+
for lag in range(1, len(NA)):
|
|
255
|
+
if (NA[lag - 1] > 0 and NA[lag] < 0) or NA[lag] == 0:
|
|
256
|
+
zeroposi = lag
|
|
257
|
+
break
|
|
258
|
+
|
|
259
|
+
if zeroposi >= len(NA): # fallback if no zero-crossing is found
|
|
260
|
+
zeroposi = 1
|
|
261
|
+
|
|
262
|
+
NA = NA[zeroposi:]
|
|
263
|
+
max_position = np.argmax(NA)
|
|
264
|
+
return zeroposi + max_position
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _CK(x: np.ndarray, T: int, M: Optional[int] = 2) -> Union[int, float, np.ndarray]:
|
|
268
|
+
"""Compute cyclic kurtosis (CK) of the signal for a given period T and order M."""
|
|
269
|
+
x = np.asarray(x).flatten()
|
|
270
|
+
N = len(x)
|
|
271
|
+
x_shift = np.zeros((M + 1, N))
|
|
272
|
+
x_shift[0] = x
|
|
273
|
+
for m in range(1, M + 1):
|
|
274
|
+
x_shift[m, T:] = x_shift[m - 1, :-T]
|
|
275
|
+
return np.sum(np.prod(x_shift, axis=0) ** 2) / (np.sum(x**2) ** (M + 1))
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _max_IJ(
|
|
279
|
+
X: np.ndarray,
|
|
280
|
+
) -> Tuple[
|
|
281
|
+
Union[int, float, np.ndarray],
|
|
282
|
+
Union[int, float, np.ndarray],
|
|
283
|
+
Union[int, float, np.ndarray],
|
|
284
|
+
]:
|
|
285
|
+
"""Find the indices (I, J) of the maximum value in the upper triangle of correlation matrix X."""
|
|
286
|
+
tempI = np.argmax(X, axis=0)
|
|
287
|
+
temp = np.max(X, axis=0)
|
|
288
|
+
J = np.argmax(temp)
|
|
289
|
+
I = tempI[J]
|
|
290
|
+
return I, J, X[I, J]
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
if __name__ == "__main__":
|
|
294
|
+
from pysdkit.data import test_emd
|
|
295
|
+
from pysdkit.plot import plot_IMFs
|
|
296
|
+
from matplotlib import pyplot as plt
|
|
297
|
+
|
|
298
|
+
time, sig = test_emd()
|
|
299
|
+
fmd = FMD(fs=1000, mode_num=2)
|
|
300
|
+
modes = fmd.fit_transform(sig)
|
|
301
|
+
plot_IMFs(sig, modes)
|
|
302
|
+
plt.show()
|
|
@@ -77,9 +77,7 @@ class JMD(object):
|
|
|
77
77
|
"""Get the full name and abbreviation of the algorithm"""
|
|
78
78
|
return "Jump Plus AM-FM Mode Decomposition (JMD)"
|
|
79
79
|
|
|
80
|
-
def jump_step(
|
|
81
|
-
self, freqs: np.ndarray, T: int
|
|
82
|
-
) -> Tuple[
|
|
80
|
+
def jump_step(self, freqs: np.ndarray, T: int) -> Tuple[
|
|
83
81
|
float,
|
|
84
82
|
np.ndarray,
|
|
85
83
|
np.ndarray,
|