PySDKit 0.4.24__tar.gz → 0.4.25__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.24 → pysdkit-0.4.25}/PKG-INFO +1 -1
- {pysdkit-0.4.24 → pysdkit-0.4.25}/PySDKit.egg-info/PKG-INFO +1 -1
- {pysdkit-0.4.24 → pysdkit-0.4.25}/PySDKit.egg-info/SOURCES.txt +1 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/__init__.py +1 -1
- pysdkit-0.4.25/pysdkit/_itd/itd.py +187 -0
- pysdkit-0.4.25/pysdkit/tests/test_itd.py +116 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/setup.py +1 -1
- pysdkit-0.4.24/pysdkit/_itd/itd.py +0 -166
- {pysdkit-0.4.24 → pysdkit-0.4.25}/LICENSE +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/PySDKit.egg-info/dependency_links.txt +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/PySDKit.egg-info/requires.txt +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/PySDKit.egg-info/top_level.txt +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/README.md +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/_find_extrema.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/_prepare_points.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/_splines.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/ceemdan.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/eemd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/efd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/emd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/hht/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/hht/frequency.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/hht/hht.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/memd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/remd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd/tvf_emd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd2d/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd2d/bemd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd2d/bmemd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_emd2d/emd2d.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_esmd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_esmd/esmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_ewt/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_ewt/ewt.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_ewt/ewt2d.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_faemd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_faemd/extrema.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_faemd/faemd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_faemd/faemd2d.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_faemd/faemd3d.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_faemd/filter.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_fmd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_fmd/fmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_fmd/nfmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_hvd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_hvd/hvd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_ifd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_itd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_jmd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_jmd/jmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_jmd/mjmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_lmd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_lmd/lmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_lmd/rlmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_osd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_ssa/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_ssa/ssa.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd/acmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd/avmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd/ba_acmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd/base.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd/mvmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd/svmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd/vmd_c.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd/vmd_f.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd/vme.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd2d/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd2d/cvmd2d.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vmd2d/vmd2d.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vncmd/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vncmd/avncmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vncmd/incmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vncmd/mncmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/_vncmd/vncmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/data/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/data/_add_noise.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/data/_cube.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/data/_generator.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/data/_image.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/data/_models.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/data/_test_univariate.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/data/_time_series.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/data/texture.txt +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/entropy/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/entropy/_permutation_entropy.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/entropy/_sample_entropy.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/models/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/models/_base.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/models/_kmeans.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/models/_knn.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/models/_pca.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/plot/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/plot/_fourier_spectra.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/plot/_functions.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/plot/_plot_images.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/plot/_plot_imfs.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/plot/_plot_signal.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/data/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/data/test_generator_cube.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/data/test_generator_image.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/data/test_generator_signal.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/models/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/models/test_knn.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/models/test_pca.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_all.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_ceemdan.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_emd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_ewt.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_faemd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_faemd2d.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_faemd3d.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_lmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_moving_decomp.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_rlmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_stl.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_vmd.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tests/test_vmd2d.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tsa/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tsa/_dtw.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tsa/_moving_decomp.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tsa/_mstl.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/tsa/_stl.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/__init__.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_cite.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_correlation.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_diagnalization.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_differ.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_fft.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_function.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_hilbert.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_instantaneous.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_kernel_matrix.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_mirror.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_process.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/pysdkit/utils/_smooth1d.py +0 -0
- {pysdkit-0.4.24 → pysdkit-0.4.25}/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.25
|
|
4
4
|
Summary: A Python library for signal decomposition algorithms with a unified interface.
|
|
5
5
|
Home-page: https://github.com/wwhenxuan/PySDKit
|
|
6
6
|
Author: whenxuan, changewam, josefinez, Yuan Feng, Wentong Zhao, JacktheFowler, Deeksha Manjunath
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySDKit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.25
|
|
4
4
|
Summary: A Python library for signal decomposition algorithms with a unified interface.
|
|
5
5
|
Home-page: https://github.com/wwhenxuan/PySDKit
|
|
6
6
|
Author: whenxuan, changewam, josefinez, Yuan Feng, Wentong Zhao, JacktheFowler, Deeksha Manjunath
|
|
@@ -103,6 +103,7 @@ pysdkit/tests/test_ewt.py
|
|
|
103
103
|
pysdkit/tests/test_faemd.py
|
|
104
104
|
pysdkit/tests/test_faemd2d.py
|
|
105
105
|
pysdkit/tests/test_faemd3d.py
|
|
106
|
+
pysdkit/tests/test_itd.py
|
|
106
107
|
pysdkit/tests/test_lmd.py
|
|
107
108
|
pysdkit/tests/test_moving_decomp.py
|
|
108
109
|
pysdkit/tests/test_rlmd.py
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on 2025/01/12 12:24:54
|
|
4
|
+
@author: Whenxuan Wang
|
|
5
|
+
@email: wwhenxuan@gmail.com
|
|
6
|
+
"""
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from scipy.signal import find_peaks
|
|
10
|
+
from scipy.interpolate import interp1d
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ITD(object):
|
|
14
|
+
"""
|
|
15
|
+
ITD: Intrinsic Time-Scale Decomposition
|
|
16
|
+
|
|
17
|
+
H=_itd(x); returns the returns proper rotation components(PRC) and residual signal corresponding to the ITD of X
|
|
18
|
+
Frei, M. G., & Osorio, I. (2007, February).
|
|
19
|
+
Intrinsic time-scale decomposition: time-frequency-energy analysis and real-time filtering of non-stationary signals.
|
|
20
|
+
In Proceedings of the Royal Society of London A: Mathematical, Physical and Engineering Sciences
|
|
21
|
+
(Vol. 463, No. 2078, pp. 321-342). The Royal Society.
|
|
22
|
+
MATLAB Link: https://www.mathworks.com/matlabcentral/fileexchange/69380-intrinsic-time-scale-decomposition-itd
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, N_max: int = 10):
|
|
26
|
+
self.N_max = N_max
|
|
27
|
+
|
|
28
|
+
def __call__(self, signal: np.ndarray) -> np.ndarray:
|
|
29
|
+
"""allow instances to be called like functions"""
|
|
30
|
+
return self.fit_transform(signal=signal)
|
|
31
|
+
|
|
32
|
+
def __str__(self) -> str:
|
|
33
|
+
"""Get the full name and abbreviation of the algorithm"""
|
|
34
|
+
return "Intrinsic Time-Scale Decomposition (ITD)"
|
|
35
|
+
|
|
36
|
+
def stop_iter(self, x, counter, E_x):
|
|
37
|
+
"""Determine whether the ITD iteration should stop.
|
|
38
|
+
|
|
39
|
+
:param x: current residual signal
|
|
40
|
+
:param counter: number of iterations completed
|
|
41
|
+
:param E_x: energy of the original input signal
|
|
42
|
+
:return: True if decomposition should stop
|
|
43
|
+
"""
|
|
44
|
+
if counter > self.N_max:
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
Exx = np.sum(x**2)
|
|
48
|
+
if Exx <= 0.01 * E_x:
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
pks1, _ = find_peaks(x)
|
|
52
|
+
pks2, _ = find_peaks(-x)
|
|
53
|
+
pks = np.union1d(pks1, pks2)
|
|
54
|
+
|
|
55
|
+
if len(pks) <= 7:
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def itd_baseline_extract(x):
|
|
62
|
+
"""
|
|
63
|
+
Calculate the baseline L of the signal x using the ITD algorithm.
|
|
64
|
+
|
|
65
|
+
The baseline is constructed by computing control points (LK) at each
|
|
66
|
+
extremum as a weighted average (alpha=0.5) of the signal value and the
|
|
67
|
+
interpolated opposite envelope, then piecewise-linearly interpolating
|
|
68
|
+
between consecutive extrema using signal-value-based slopes.
|
|
69
|
+
|
|
70
|
+
:param x: input 1D signal array
|
|
71
|
+
:return: tuple (L, H) where L is the baseline and H = x - L is the
|
|
72
|
+
proper rotation component
|
|
73
|
+
"""
|
|
74
|
+
length = len(x)
|
|
75
|
+
t = np.arange(length)
|
|
76
|
+
alpha = 0.5
|
|
77
|
+
|
|
78
|
+
idx_max, _ = find_peaks(x)
|
|
79
|
+
val_max = x[idx_max]
|
|
80
|
+
|
|
81
|
+
idx_min, _ = find_peaks(-x)
|
|
82
|
+
val_min = x[idx_min]
|
|
83
|
+
|
|
84
|
+
if len(idx_max) == 0 or len(idx_min) == 0:
|
|
85
|
+
return x.copy(), np.zeros_like(x)
|
|
86
|
+
|
|
87
|
+
idx_cb = np.union1d(idx_max, idx_min)
|
|
88
|
+
|
|
89
|
+
# Pad boundary so that idx_max and idx_min span the same index range.
|
|
90
|
+
# When the first (or last) extremum on one side has no counterpart,
|
|
91
|
+
# mirror the opposite extremum's position with the signal value there.
|
|
92
|
+
if idx_max[0] < idx_min[0]:
|
|
93
|
+
idx_min = np.concatenate(([idx_max[0]], idx_min))
|
|
94
|
+
val_min = np.concatenate(([x[idx_max[0]]], val_min))
|
|
95
|
+
elif idx_min[0] < idx_max[0]:
|
|
96
|
+
idx_max = np.concatenate(([idx_min[0]], idx_max))
|
|
97
|
+
val_max = np.concatenate(([x[idx_min[0]]], val_max))
|
|
98
|
+
|
|
99
|
+
if idx_max[-1] > idx_min[-1]:
|
|
100
|
+
idx_min = np.concatenate((idx_min, [idx_max[-1]]))
|
|
101
|
+
val_min = np.concatenate((val_min, [x[idx_max[-1]]]))
|
|
102
|
+
elif idx_min[-1] > idx_max[-1]:
|
|
103
|
+
idx_max = np.concatenate((idx_max, [idx_min[-1]]))
|
|
104
|
+
val_max = np.concatenate((val_max, [x[idx_min[-1]]]))
|
|
105
|
+
|
|
106
|
+
Max_line_interp = interp1d(
|
|
107
|
+
idx_max, val_max, kind="linear", fill_value="extrapolate"
|
|
108
|
+
)
|
|
109
|
+
Min_line_interp = interp1d(
|
|
110
|
+
idx_min, val_min, kind="linear", fill_value="extrapolate"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
Max_line = Max_line_interp(t)
|
|
114
|
+
Min_line = Min_line_interp(t)
|
|
115
|
+
|
|
116
|
+
# LK control points: weighted average at each extremum
|
|
117
|
+
LK1_vals = alpha * Max_line[idx_min] + (1 - alpha) * val_min
|
|
118
|
+
LK2_vals = alpha * Min_line[idx_max] + (1 - alpha) * val_max
|
|
119
|
+
|
|
120
|
+
# Build (position, value) pairs and merge
|
|
121
|
+
LK1_pts = np.column_stack((idx_min, LK1_vals))
|
|
122
|
+
LK2_pts = np.column_stack((idx_max, LK2_vals))
|
|
123
|
+
LK = np.vstack((LK1_pts, LK2_pts))
|
|
124
|
+
|
|
125
|
+
# Sort by position and remove duplicates at the boundaries
|
|
126
|
+
order = np.argsort(LK[:, 0])
|
|
127
|
+
LK = LK[order]
|
|
128
|
+
|
|
129
|
+
# Remove the padded boundary duplicates (first and last are mirrors)
|
|
130
|
+
if len(LK) > 2:
|
|
131
|
+
LK = LK[1:-1]
|
|
132
|
+
|
|
133
|
+
# Clamp to signal boundaries
|
|
134
|
+
LK = np.vstack((
|
|
135
|
+
[0, LK[0, 1]],
|
|
136
|
+
LK,
|
|
137
|
+
[length - 1, LK[-1, 1]],
|
|
138
|
+
))
|
|
139
|
+
|
|
140
|
+
# Compute baseline by piecewise-linear interpolation between extrema
|
|
141
|
+
idx_Xk = np.concatenate(([0], idx_cb, [length - 1])).astype(int)
|
|
142
|
+
|
|
143
|
+
L = np.zeros(length)
|
|
144
|
+
for i in range(len(idx_Xk) - 1):
|
|
145
|
+
denom = x[idx_Xk[i + 1]] - x[idx_Xk[i]]
|
|
146
|
+
kij = (LK[i + 1, 1] - LK[i, 1]) / denom if denom != 0 else 0.0
|
|
147
|
+
for j in range(idx_Xk[i], idx_Xk[i + 1]):
|
|
148
|
+
L[j] = LK[i, 1] + kij * (x[j] - x[idx_Xk[i]])
|
|
149
|
+
|
|
150
|
+
L[length - 1] = LK[-1, 1]
|
|
151
|
+
|
|
152
|
+
H = x - L
|
|
153
|
+
return L, H
|
|
154
|
+
|
|
155
|
+
def fit_transform(self, signal: np.ndarray) -> np.ndarray:
|
|
156
|
+
"""Input x is a 1D numpy signal and return the decomposition results."""
|
|
157
|
+
H = []
|
|
158
|
+
E_x = np.sum(signal**2)
|
|
159
|
+
counter = 0
|
|
160
|
+
|
|
161
|
+
while True:
|
|
162
|
+
counter = counter + 1
|
|
163
|
+
L1, H1 = self.itd_baseline_extract(signal)
|
|
164
|
+
H.append(H1)
|
|
165
|
+
STOP = self.stop_iter(signal, counter, E_x)
|
|
166
|
+
if STOP is True:
|
|
167
|
+
H.append(L1)
|
|
168
|
+
break
|
|
169
|
+
signal = L1
|
|
170
|
+
|
|
171
|
+
H = np.vstack(H)
|
|
172
|
+
|
|
173
|
+
return H
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == "__main__":
|
|
177
|
+
from pysdkit.data import test_univariate_signal
|
|
178
|
+
from pysdkit.plot import plot_IMFs
|
|
179
|
+
|
|
180
|
+
time, signal = test_univariate_signal()
|
|
181
|
+
|
|
182
|
+
print(signal.shape)
|
|
183
|
+
|
|
184
|
+
itd = ITD()
|
|
185
|
+
imfs = itd.fit_transform(signal=signal)
|
|
186
|
+
|
|
187
|
+
plot_IMFs(signal, IMFs=imfs)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Tests for the Intrinsic Time-Scale Decomposition (ITD) algorithm.
|
|
4
|
+
"""
|
|
5
|
+
import unittest
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from pysdkit import ITD
|
|
9
|
+
from pysdkit.data import test_emd, test_univariate_signal
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ITDTest(unittest.TestCase):
|
|
13
|
+
"""Test that ITD runs correctly on various signal types."""
|
|
14
|
+
|
|
15
|
+
def test_fit_transform(self) -> None:
|
|
16
|
+
"""Verify signal decomposition on built-in test signals."""
|
|
17
|
+
itd = ITD(N_max=5)
|
|
18
|
+
for index in range(1, 4):
|
|
19
|
+
time, signal = test_univariate_signal(case=index)
|
|
20
|
+
IMFs = itd.fit_transform(signal)
|
|
21
|
+
dim = len(IMFs.shape)
|
|
22
|
+
self.assertEqual(
|
|
23
|
+
first=dim,
|
|
24
|
+
second=2,
|
|
25
|
+
msg="The output shape of the decomposed signal is wrong",
|
|
26
|
+
)
|
|
27
|
+
_, length = IMFs.shape
|
|
28
|
+
self.assertEqual(
|
|
29
|
+
first=len(signal),
|
|
30
|
+
second=length,
|
|
31
|
+
msg="Wrong length of decomposed signal",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def test_default_call(self) -> None:
|
|
35
|
+
"""Verify the __call__ interface works."""
|
|
36
|
+
time, signal = test_emd()
|
|
37
|
+
itd = ITD(N_max=5)
|
|
38
|
+
IMFs = itd(signal)
|
|
39
|
+
|
|
40
|
+
dim = len(IMFs.shape)
|
|
41
|
+
self.assertEqual(
|
|
42
|
+
first=dim,
|
|
43
|
+
second=2,
|
|
44
|
+
msg="The output shape of the decomposed signal is wrong",
|
|
45
|
+
)
|
|
46
|
+
_, length = IMFs.shape
|
|
47
|
+
self.assertEqual(
|
|
48
|
+
first=len(signal), second=length, msg="Wrong length of decomposed signal"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def test_reconstruction(self) -> None:
|
|
52
|
+
"""Verify that IMFs sum back to the original signal."""
|
|
53
|
+
time, signal = test_univariate_signal(case=1)
|
|
54
|
+
itd = ITD(N_max=5)
|
|
55
|
+
IMFs = itd.fit_transform(signal)
|
|
56
|
+
|
|
57
|
+
reconstructed = np.sum(IMFs, axis=0)
|
|
58
|
+
self.assertTrue(
|
|
59
|
+
np.allclose(signal, reconstructed, atol=1e-10),
|
|
60
|
+
"IMFs should reconstruct the original signal",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def test_near_monotonic_signal(self) -> None:
|
|
64
|
+
"""Verify ITD handles near-monotonic signals without crashing (issue #29)."""
|
|
65
|
+
np.random.seed(42)
|
|
66
|
+
N = 200
|
|
67
|
+
t = np.linspace(0, 1, N)
|
|
68
|
+
signal = -1100 * t + 0.01 * np.sin(50 * t) + 0.005 * np.random.randn(N)
|
|
69
|
+
|
|
70
|
+
itd = ITD(N_max=3)
|
|
71
|
+
IMFs = itd.fit_transform(signal)
|
|
72
|
+
|
|
73
|
+
self.assertTrue(IMFs.shape[0] >= 1, "Should produce at least one component")
|
|
74
|
+
self.assertEqual(IMFs.shape[1], N, "Output length should match input")
|
|
75
|
+
reconstructed = np.sum(IMFs, axis=0)
|
|
76
|
+
self.assertTrue(
|
|
77
|
+
np.allclose(signal, reconstructed, atol=1e-10),
|
|
78
|
+
"IMFs should reconstruct the original signal",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def test_pure_trend(self) -> None:
|
|
82
|
+
"""Verify ITD handles a pure trend (no oscillation)."""
|
|
83
|
+
signal = np.linspace(0, 10, 100)
|
|
84
|
+
itd = ITD(N_max=3)
|
|
85
|
+
IMFs = itd.fit_transform(signal)
|
|
86
|
+
|
|
87
|
+
self.assertTrue(IMFs.shape[0] >= 1, "Should produce at least one component")
|
|
88
|
+
reconstructed = np.sum(IMFs, axis=0)
|
|
89
|
+
self.assertTrue(
|
|
90
|
+
np.allclose(signal, reconstructed, atol=1e-10),
|
|
91
|
+
"IMFs should reconstruct the original signal",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def test_asymmetric_extrema(self) -> None:
|
|
95
|
+
"""Verify ITD works when maxima and minima counts differ."""
|
|
96
|
+
t = np.linspace(0, 4 * np.pi, 500)
|
|
97
|
+
signal = np.sin(t) + 0.5 * np.sin(3 * t) + 0.3 * np.cos(0.5 * t)
|
|
98
|
+
|
|
99
|
+
itd = ITD(N_max=5)
|
|
100
|
+
IMFs = itd.fit_transform(signal)
|
|
101
|
+
|
|
102
|
+
self.assertTrue(IMFs.shape[0] >= 2, "Should produce multiple components")
|
|
103
|
+
reconstructed = np.sum(IMFs, axis=0)
|
|
104
|
+
self.assertTrue(
|
|
105
|
+
np.allclose(signal, reconstructed, atol=1e-10),
|
|
106
|
+
"IMFs should reconstruct the original signal",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def test_str(self) -> None:
|
|
110
|
+
"""Verify string representation."""
|
|
111
|
+
itd = ITD()
|
|
112
|
+
self.assertIn("ITD", str(itd))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
unittest.main()
|
|
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="PySDKit",
|
|
8
8
|
packages=setuptools.find_packages(),
|
|
9
|
-
version="0.4.
|
|
9
|
+
version="0.4.25",
|
|
10
10
|
description="A Python library for signal decomposition algorithms with a unified interface.",
|
|
11
11
|
url="https://github.com/wwhenxuan/PySDKit",
|
|
12
12
|
author="whenxuan, changewam, josefinez, Yuan Feng, Wentong Zhao, JacktheFowler, Deeksha Manjunath",
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
Created on 2025/01/12 12:24:54
|
|
4
|
-
@author: Whenxuan Wang
|
|
5
|
-
@email: wwhenxuan@gmail.com
|
|
6
|
-
not done!!!
|
|
7
|
-
"""
|
|
8
|
-
import numpy as np
|
|
9
|
-
|
|
10
|
-
from scipy.signal import find_peaks
|
|
11
|
-
from scipy.interpolate import interp1d
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class ITD(object):
|
|
15
|
-
"""
|
|
16
|
-
ITD: Intrinsic Time-Scale Decomposition
|
|
17
|
-
|
|
18
|
-
H=_itd(x); returns the returns proper rotation components(PRC) and residual signal corresponding to the ITD of X
|
|
19
|
-
Frei, M. G., & Osorio, I. (2007, February).
|
|
20
|
-
Intrinsic time-scale decomposition: time-frequency-energy analysis and real-time filtering of non-stationary signals.
|
|
21
|
-
In Proceedings of the Royal Society of London A: Mathematical, Physical and Engineering Sciences
|
|
22
|
-
(Vol. 463, No. 2078, pp. 321-342). The Royal Society.
|
|
23
|
-
MATLAB Link: https://www.mathworks.com/matlabcentral/fileexchange/69380-intrinsic-time-scale-decomposition-itd
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def __init__(self, N_max: int = 10):
|
|
27
|
-
self.N_max = N_max
|
|
28
|
-
|
|
29
|
-
def __call__(self, signal: np.ndarray) -> np.ndarray:
|
|
30
|
-
"""allow instances to be called like functions"""
|
|
31
|
-
return self.fit_transform(signal=signal)
|
|
32
|
-
|
|
33
|
-
def __str__(self) -> str:
|
|
34
|
-
"""Get the full name and abbreviation of the algorithm"""
|
|
35
|
-
return "Intrinsic Time-Scale Decomposition (ITD)"
|
|
36
|
-
|
|
37
|
-
def stop_iter(self, x, counter, E_x):
|
|
38
|
-
"""Stop the main iteration loop"""
|
|
39
|
-
if counter > self.N_max:
|
|
40
|
-
return True
|
|
41
|
-
|
|
42
|
-
Exx = np.sum(x**2)
|
|
43
|
-
if Exx <= 0.01 * E_x:
|
|
44
|
-
return True
|
|
45
|
-
|
|
46
|
-
# 获取输入信号的峰值
|
|
47
|
-
pks1, _ = find_peaks(x) # 极大值位置
|
|
48
|
-
pks2, _ = find_peaks(-x) # 极小值位置
|
|
49
|
-
pks = np.union1d(pks1, pks2) # 所有的极值点
|
|
50
|
-
|
|
51
|
-
if len(pks) <= 7:
|
|
52
|
-
return True
|
|
53
|
-
|
|
54
|
-
# EOF
|
|
55
|
-
return False
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def itd_baseline_extract(x):
|
|
59
|
-
"""This function is to calculate the baseline L of the signal x."""
|
|
60
|
-
length = len(x)
|
|
61
|
-
t = np.arange(0, length)
|
|
62
|
-
alpha = 0.5
|
|
63
|
-
|
|
64
|
-
idx_max, _ = find_peaks(x)
|
|
65
|
-
val_max = x[idx_max]
|
|
66
|
-
|
|
67
|
-
idx_min, _ = find_peaks(-x)
|
|
68
|
-
val_min = x[idx_min]
|
|
69
|
-
|
|
70
|
-
idx_cb = np.union1d(idx_max, idx_min)
|
|
71
|
-
|
|
72
|
-
# Check the boundary conditions
|
|
73
|
-
|
|
74
|
-
# Left side
|
|
75
|
-
if np.min(idx_max) < np.min(idx_min):
|
|
76
|
-
idx_min = np.append(idx_max[0], idx_min)
|
|
77
|
-
val_min = np.append(val_min[0], val_min)
|
|
78
|
-
elif np.min(idx_max) > np.min(idx_min):
|
|
79
|
-
idx_max = np.append(idx_min[0], idx_max)
|
|
80
|
-
val_max = np.append(val_max[0], val_max)
|
|
81
|
-
|
|
82
|
-
# Right side
|
|
83
|
-
if np.max(idx_max) > np.max(idx_min):
|
|
84
|
-
idx_min = np.append(idx_min, idx_max[-1])
|
|
85
|
-
val_min = np.append(val_min, val_min[-1])
|
|
86
|
-
elif np.max(idx_max) < np.max(idx_min):
|
|
87
|
-
idx_max = np.append(idx_max, idx_min[-1])
|
|
88
|
-
val_max = np.append(val_max, val_max[-1])
|
|
89
|
-
|
|
90
|
-
# Compute the LK points
|
|
91
|
-
# 创建插值函数对象,使用线性插值
|
|
92
|
-
Max_line_interp = interp1d(
|
|
93
|
-
idx_max, val_max, kind="linear", fill_value="extrapolate"
|
|
94
|
-
)
|
|
95
|
-
Min_line_interp = interp1d(
|
|
96
|
-
idx_min, val_min, kind="linear", fill_value="extrapolate"
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
# 计算插值点的值
|
|
100
|
-
Max_line = Max_line_interp(t)
|
|
101
|
-
Min_line = Min_line_interp(t)
|
|
102
|
-
|
|
103
|
-
LK1 = alpha * Max_line[idx_min] + val_min * (1 - alpha)
|
|
104
|
-
LK2 = alpha * Min_line[idx_max] + val_max * (1 - alpha)
|
|
105
|
-
|
|
106
|
-
LK1 = np.append(idx_min.reshape(-1, 1), LK1.reshape(-1, 1))
|
|
107
|
-
LK2 = np.append(idx_max.reshape(-1, 1), LK2.reshape(-1, 1))
|
|
108
|
-
|
|
109
|
-
print(LK1.shape, LK2.shape)
|
|
110
|
-
|
|
111
|
-
LK = np.vstack((LK1, LK2)).T
|
|
112
|
-
|
|
113
|
-
LK_col_2 = np.argsort(LK[:, 0])
|
|
114
|
-
LK_sorted = LK[LK_col_2, :]
|
|
115
|
-
LK = LK_sorted[1:-1, :]
|
|
116
|
-
|
|
117
|
-
LK = np.vstack((np.array([0, LK[0, 1]]), LK, np.array([length - 1, LK[-1, 1]])))
|
|
118
|
-
|
|
119
|
-
# Compute the Lt points
|
|
120
|
-
idx_Xk = np.hstack((0, idx_cb, length - 1)).reshape([len(idx_cb) + 2])
|
|
121
|
-
|
|
122
|
-
L = np.zeros(length)
|
|
123
|
-
for i in range(0, len(idx_Xk) - 1):
|
|
124
|
-
for j in range(idx_Xk[i], idx_Xk[i + 1]):
|
|
125
|
-
kij = (LK[i + 1, 1] - LK[i, 1]) / (
|
|
126
|
-
x[idx_Xk[i + 1]] - x[idx_Xk[i]]
|
|
127
|
-
) # compute the slope K
|
|
128
|
-
L[j] = LK[i, 1] + kij * (x[j] - x[idx_Xk[i]])
|
|
129
|
-
|
|
130
|
-
H = x - L
|
|
131
|
-
print(H)
|
|
132
|
-
return L, H
|
|
133
|
-
|
|
134
|
-
def fit_transform(self, signal: np.ndarray) -> np.ndarray:
|
|
135
|
-
"""Input x is a 1D numpy signal and return the decomposition results."""
|
|
136
|
-
H = []
|
|
137
|
-
E_x = np.sum(signal**2)
|
|
138
|
-
counter = 0
|
|
139
|
-
|
|
140
|
-
while True:
|
|
141
|
-
counter = counter + 1
|
|
142
|
-
L1, H1 = self.itd_baseline_extract(signal)
|
|
143
|
-
H.append(H1)
|
|
144
|
-
STOP = self.stop_iter(signal, counter, E_x)
|
|
145
|
-
if STOP is True:
|
|
146
|
-
H.append(L1)
|
|
147
|
-
break
|
|
148
|
-
signal = L1
|
|
149
|
-
|
|
150
|
-
H = np.vstack(H)
|
|
151
|
-
|
|
152
|
-
return H
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if __name__ == "__main__":
|
|
156
|
-
from pysdkit.data import test_univariate_signal
|
|
157
|
-
from pysdkit.plot import plot_IMFs
|
|
158
|
-
|
|
159
|
-
time, signal = test_univariate_signal()
|
|
160
|
-
|
|
161
|
-
print(signal.shape)
|
|
162
|
-
|
|
163
|
-
itd = ITD()
|
|
164
|
-
imfs = itd.fit_transform(signal=signal)
|
|
165
|
-
|
|
166
|
-
plot_IMFs(signal, IMFs=imfs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|