SignalProcessingTools 1.2.0__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.
@@ -0,0 +1,157 @@
1
+ Metadata-Version: 2.4
2
+ Name: SignalProcessingTools
3
+ Version: 1.2.0
4
+ Summary: Signal processing tools
5
+ Author: attr: SignalProcessingTools.__author__
6
+ Author-email: bruno.zuadacoelho@deltares.nl, aron.noordam@deltares.nl
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: matplotlib>=3.10
13
+ Requires-Dist: numpy>=2.2
14
+ Requires-Dist: scipy>=1.15
15
+ Provides-Extra: testing
16
+ Requires-Dist: pytest>=8.3; extra == "testing"
17
+ Requires-Dist: tox>=4.24; extra == "testing"
18
+
19
+ # SignalProcessingTools
20
+
21
+ ![Tests](https://github.com/StemVibrations/STEM/actions/workflows/tests.yml/badge.svg)
22
+
23
+ A comprehensive Python package for time and space domain signal processing operations with a focus on vibration analysis and frequency-domain transformations.
24
+ The space domain operations focus on railway applications, while the time domain operations are more general.
25
+
26
+ ## Overview
27
+
28
+ SignalProcessingTools provides a suite of tools for analyzing, transforming, and processing data.
29
+
30
+ ### Time domain operations:
31
+ * Fast Fourier Transforms (FFT) and inverse FFT
32
+ * Signal filtering
33
+ * Integration
34
+ * Power Spectral Density (PSD) using Welch's method
35
+ * Spectrogram generation
36
+ * Effective velocity calculations using SBR method
37
+ * 1/3 octave band analysis
38
+ * Windowing functions (Hann, Hamming, Blackman, etc.)
39
+
40
+
41
+ ### Space domain operations:
42
+ * D0, D1, D2, and D3 track longitudinal levels, following EN 13848-1:2006.
43
+ * Hmax and Hrms according to Zandberg et al. (2022).
44
+
45
+ ## Installation
46
+
47
+ ### Install from PyPI
48
+ You can install the package directly from PyPI using pip:
49
+
50
+ ```bash
51
+ pip install SignalProcessingTools
52
+ ```
53
+
54
+ ### Install from Source
55
+ To install the package from the source, clone the repository and run the following commands:
56
+
57
+ ```bash
58
+ git clone https://github.com/PlatypusBytes/SignalProcessing.git
59
+ cd SignalProcessing
60
+ pip install -e .
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ ### Basic Example of Time Domain Operations
66
+
67
+ #### FFT and signal integration
68
+ ```python
69
+ import numpy as np
70
+ from SignalProcessingTools.time_signal import SignalProcessing, Windows
71
+
72
+ # Create a test signal
73
+ t = np.linspace(0, 10, 5001)
74
+ y = 1.75 * np.sin(2 * np.pi * 6 * t)
75
+
76
+ # Initialize the signal processor
77
+ sig = SignalProcessing(t, y)
78
+
79
+ # Perform FFT
80
+ sig.fft()
81
+
82
+ # Integrate the signal
83
+ sig.integrate(baseline=True, hp=True, fpass=1, n=6)
84
+ ```
85
+
86
+ #### Windowed Processing and PSD and spectrogram
87
+
88
+ ```python
89
+ # Create a signal processor with Hamming window
90
+ sig = SignalProcessing(t, y, window=Windows.HAMMING, window_size=4096)
91
+
92
+ # Calculate Power Spectral Density
93
+ sig.psd()
94
+
95
+ # Generate a spectrogram
96
+ sig.spectrogram()
97
+ ```
98
+
99
+ #### Signal Filtering
100
+
101
+ ```python
102
+ # Apply a low-pass filter to remove high frequency noise
103
+ sig.filter(10, 4, type_filter="lowpass")
104
+ ```
105
+
106
+ #### Effective Velocity Calculation (SBR-B Method)
107
+
108
+ ```python
109
+ # Calculate effective velocity using SBR method
110
+ sig.v_eff_SBR()
111
+ ```
112
+
113
+ ### Basic Example of Spatial Domain Operations
114
+
115
+ #### D0, D1, D2, and D3 Calculation
116
+
117
+ ```python
118
+ import numpy as np
119
+ from SignalProcessingTools.space_signal import SpatialSignal
120
+ from SignalProcessingTools.space_signal import EN13848
121
+
122
+ # Create test data
123
+ x = np.linspace(0, 100, 50001)
124
+ omega = 2 * np.pi * 6
125
+ y = 1.75 * np.sin(omega * x)
126
+ y_noise = y + 0.01 * np.sin(120 * x)
127
+
128
+ sig = SpaceSignalProcessing(x, y_noise)
129
+ # Compute track longitudinal levels
130
+ sig.compute_track_longitudinal_levels()
131
+ ```
132
+
133
+ #### Hmax and Hrms Calculation
134
+
135
+ ```python
136
+ x_track = np.linspace(0, 500, 25001)
137
+ track_irregularity = (
138
+ 0.002 * np.sin(2 * np.pi * 0.1 * x_track) +
139
+ 0.001 * np.sin(2 * np.pi * 0.2 * x_track) +
140
+ 0.0005 * np.sin(2 * np.pi * 0.4 * x_track) +
141
+ 0.0002 * np.random.randn(len(x_track))
142
+ )
143
+
144
+ sig_hmax = SpaceSignalProcessing(x_track, track_irregularity)
145
+ # Compute Hmax parameters
146
+ sig_hmax.compute_Hmax(convert_m2mm=True)
147
+ ```
148
+
149
+ ## Example Files
150
+
151
+ A comprehensive example demonstrating all features is provided for the [time signal](./example_time_signal.py) and [space signal](./example_space_signal.py).
152
+
153
+
154
+ ## License
155
+
156
+ This project is licensed under the MIT License - see the License file for details.
157
+
@@ -0,0 +1,139 @@
1
+ # SignalProcessingTools
2
+
3
+ ![Tests](https://github.com/StemVibrations/STEM/actions/workflows/tests.yml/badge.svg)
4
+
5
+ A comprehensive Python package for time and space domain signal processing operations with a focus on vibration analysis and frequency-domain transformations.
6
+ The space domain operations focus on railway applications, while the time domain operations are more general.
7
+
8
+ ## Overview
9
+
10
+ SignalProcessingTools provides a suite of tools for analyzing, transforming, and processing data.
11
+
12
+ ### Time domain operations:
13
+ * Fast Fourier Transforms (FFT) and inverse FFT
14
+ * Signal filtering
15
+ * Integration
16
+ * Power Spectral Density (PSD) using Welch's method
17
+ * Spectrogram generation
18
+ * Effective velocity calculations using SBR method
19
+ * 1/3 octave band analysis
20
+ * Windowing functions (Hann, Hamming, Blackman, etc.)
21
+
22
+
23
+ ### Space domain operations:
24
+ * D0, D1, D2, and D3 track longitudinal levels, following EN 13848-1:2006.
25
+ * Hmax and Hrms according to Zandberg et al. (2022).
26
+
27
+ ## Installation
28
+
29
+ ### Install from PyPI
30
+ You can install the package directly from PyPI using pip:
31
+
32
+ ```bash
33
+ pip install SignalProcessingTools
34
+ ```
35
+
36
+ ### Install from Source
37
+ To install the package from the source, clone the repository and run the following commands:
38
+
39
+ ```bash
40
+ git clone https://github.com/PlatypusBytes/SignalProcessing.git
41
+ cd SignalProcessing
42
+ pip install -e .
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ ### Basic Example of Time Domain Operations
48
+
49
+ #### FFT and signal integration
50
+ ```python
51
+ import numpy as np
52
+ from SignalProcessingTools.time_signal import SignalProcessing, Windows
53
+
54
+ # Create a test signal
55
+ t = np.linspace(0, 10, 5001)
56
+ y = 1.75 * np.sin(2 * np.pi * 6 * t)
57
+
58
+ # Initialize the signal processor
59
+ sig = SignalProcessing(t, y)
60
+
61
+ # Perform FFT
62
+ sig.fft()
63
+
64
+ # Integrate the signal
65
+ sig.integrate(baseline=True, hp=True, fpass=1, n=6)
66
+ ```
67
+
68
+ #### Windowed Processing and PSD and spectrogram
69
+
70
+ ```python
71
+ # Create a signal processor with Hamming window
72
+ sig = SignalProcessing(t, y, window=Windows.HAMMING, window_size=4096)
73
+
74
+ # Calculate Power Spectral Density
75
+ sig.psd()
76
+
77
+ # Generate a spectrogram
78
+ sig.spectrogram()
79
+ ```
80
+
81
+ #### Signal Filtering
82
+
83
+ ```python
84
+ # Apply a low-pass filter to remove high frequency noise
85
+ sig.filter(10, 4, type_filter="lowpass")
86
+ ```
87
+
88
+ #### Effective Velocity Calculation (SBR-B Method)
89
+
90
+ ```python
91
+ # Calculate effective velocity using SBR method
92
+ sig.v_eff_SBR()
93
+ ```
94
+
95
+ ### Basic Example of Spatial Domain Operations
96
+
97
+ #### D0, D1, D2, and D3 Calculation
98
+
99
+ ```python
100
+ import numpy as np
101
+ from SignalProcessingTools.space_signal import SpatialSignal
102
+ from SignalProcessingTools.space_signal import EN13848
103
+
104
+ # Create test data
105
+ x = np.linspace(0, 100, 50001)
106
+ omega = 2 * np.pi * 6
107
+ y = 1.75 * np.sin(omega * x)
108
+ y_noise = y + 0.01 * np.sin(120 * x)
109
+
110
+ sig = SpaceSignalProcessing(x, y_noise)
111
+ # Compute track longitudinal levels
112
+ sig.compute_track_longitudinal_levels()
113
+ ```
114
+
115
+ #### Hmax and Hrms Calculation
116
+
117
+ ```python
118
+ x_track = np.linspace(0, 500, 25001)
119
+ track_irregularity = (
120
+ 0.002 * np.sin(2 * np.pi * 0.1 * x_track) +
121
+ 0.001 * np.sin(2 * np.pi * 0.2 * x_track) +
122
+ 0.0005 * np.sin(2 * np.pi * 0.4 * x_track) +
123
+ 0.0002 * np.random.randn(len(x_track))
124
+ )
125
+
126
+ sig_hmax = SpaceSignalProcessing(x_track, track_irregularity)
127
+ # Compute Hmax parameters
128
+ sig_hmax.compute_Hmax(convert_m2mm=True)
129
+ ```
130
+
131
+ ## Example Files
132
+
133
+ A comprehensive example demonstrating all features is provided for the [time signal](./example_time_signal.py) and [space signal](./example_space_signal.py).
134
+
135
+
136
+ ## License
137
+
138
+ This project is licensed under the MIT License - see the License file for details.
139
+
@@ -0,0 +1 @@
1
+ from .__version__ import __version__
@@ -0,0 +1,3 @@
1
+ __title__ = "SignalProcessingTools"
2
+ __version__ = "1.2.0"
3
+ __author__ = "Bruno Zuada Coelho, Aron Noordam"
@@ -0,0 +1,189 @@
1
+ from typing import Optional, List, Tuple
2
+ import numpy as np
3
+ import numpy.typing as npt
4
+ from .time_signal import TimeSignalProcessing, FilterDesign, Windows
5
+
6
+
7
+ class SpaceSignalProcessing:
8
+ """
9
+ SignalProcessing class for processing signals in space.
10
+ """
11
+ def __init__(self, x: npt.NDArray[np.float64], values: npt.NDArray[np.float64], Fs: Optional[float] = None):
12
+ """
13
+ Initializes the ProcessSignal object.
14
+
15
+ Parameters
16
+ ----------
17
+ :param x (npt.NDArray[np.float64]): coordinates of the signal
18
+ :param values (npt.NDArray[np.float64]): signal values
19
+ :param Fs (Optional[float]): sampling frequency of the signal (optional: default None)
20
+ """
21
+
22
+ self.coordinates = x
23
+ self.signal_raw = values
24
+ self.n_points = values.shape[0]
25
+
26
+ # acquisition frequency
27
+ if Fs is None:
28
+ self.fs = int(np.ceil(1 / np.mean(np.diff(x))))
29
+
30
+ else:
31
+ self.fs = Fs
32
+
33
+ # track quality indexes
34
+ self.d0 = None
35
+ self.d1 = None
36
+ self.d2 = None
37
+ self.d3 = None
38
+ # track descriptors
39
+ self.rms_bands = None
40
+ self.max_fast = None
41
+ self.max_fast_Dx = None
42
+
43
+
44
+ def compute_track_longitudinal_levels(self):
45
+ """
46
+ Computes the track longitudinal levels, following EN 13848-1:2006.
47
+
48
+ The method computes the D0, D1, D2, and D3 components of the signal.
49
+ It uses the following frequency bands:
50
+ - D0: 1m < lambda <= 5m (1/5 Hz < f <= 1 Hz)
51
+ - D1: 3m < lambda <= 25m (1/25 Hz < f <= 1/3 Hz)
52
+ - D2: 25m < lambda <= 70m (1/70 Hz < f <= 1/25 Hz)
53
+ - D3: 70m < lambda <= 150m (1/150 Hz < f <= 1/70 Hz)
54
+ """
55
+
56
+ sig = TimeSignalProcessing(self.coordinates, self.signal_raw, Fs=self.fs)
57
+ sig.filter([1/5., 1.], 4, type_filter="bandpass", filter_design=FilterDesign.BUTTERWORTH)
58
+ self.d0 = sig.signal
59
+
60
+ sig.reset()
61
+ sig.filter([1/25., 1/3.], 4, type_filter="bandpass", filter_design=FilterDesign.BUTTERWORTH)
62
+ self.d1 = sig.signal
63
+ sig.reset()
64
+
65
+ sig.filter([1/70., 1/25.], 4, type_filter="bandpass", filter_design=FilterDesign.BUTTERWORTH)
66
+ self.d2 = sig.signal
67
+ sig.reset()
68
+
69
+ sig.filter([1/150., 1/70.], 4, type_filter="bandpass", filter_design=FilterDesign.BUTTERWORTH)
70
+ self.d3 = sig.signal
71
+ sig.reset()
72
+
73
+ def compute_Hmax(self, convert_m2mm: bool = True):
74
+ """
75
+ Computes the descriptor Hmax and Hrms according to Zandberg et al. (2022)
76
+ 'Deriving parameters for the characterisation of the railway track quality in
77
+ relation to environmental vibration'
78
+
79
+ Parameters
80
+ ----------
81
+ :param convert_m2mm (optional, default = True): if True, converts the results from m to mm
82
+ """
83
+
84
+ # octave bands used for the processing
85
+ one_third_octave_bands = [[.08, .10],
86
+ [.10, .126],
87
+ [.126, .16],
88
+ [.16, .20],
89
+ [.20, .253],
90
+ [.253, .32],
91
+ [.32, .40],
92
+ [.40, .50],
93
+ [.50, .63],
94
+ ]
95
+
96
+
97
+ # setting for the processing
98
+ self.DXmaxFast = 1
99
+ nb_fft_min = 256 # minimum number of samples for the power spectral density
100
+ derivative = [0, 0, 0, 2, 2, 2, 2, 2, 2] # number of times that each frequency band is derived
101
+
102
+ # RMS of the square root of the power spectral density
103
+ self.rms_bands = np.zeros(len(one_third_octave_bands))
104
+ # maximum effective value over the entire signal
105
+ self.max_fast = np.zeros(len(one_third_octave_bands))
106
+ # maximum effective value over the length Dx
107
+ self.max_fast_Dx = np.zeros(len(one_third_octave_bands))
108
+
109
+ # convert the signal from m to mm
110
+ if convert_m2mm:
111
+ self.signal = self.signal_raw * 1000
112
+
113
+ # compute the power spectral density
114
+ n_fft = int(np.max([2 ** (np.ceil(np.log2(len(self.signal)))), nb_fft_min]))
115
+ # if signal is odd length, add a zero to make it even
116
+ if len(self.signal) % 2 != 0:
117
+ signal = np.append(self.signal, 0)
118
+ coordinates = np.append(self.coordinates, self.coordinates[-1] + (self.coordinates[1] - self.coordinates[0]))
119
+ else:
120
+ signal = self.signal
121
+ coordinates = self.coordinates
122
+ sig = TimeSignalProcessing(coordinates, signal, Fs=self.fs, window=Windows.HAMMING,
123
+ window_size=len(signal))
124
+ sig.psd(nb_points=n_fft, detrend=False)
125
+
126
+ # compute the rsm psd
127
+ self.__rms_effective(sig.frequency_Pxx, sig.Pxx, one_third_octave_bands, derivative)
128
+ # compute the effective values
129
+ self.__effective_values(one_third_octave_bands, derivative)
130
+
131
+
132
+ def __rms_effective(self, frequency: npt.NDArray[np.float64], Pxx: npt.NDArray[np.float64],
133
+ one_third_octave_bands: List[Tuple[float, float]], derivative: List[int]):
134
+ """
135
+ Computes RMS square root of power spectral density
136
+
137
+ Parameters
138
+ ----------
139
+ :param frequency (npt.NDArray[np.float64]): frequency vector
140
+ :param Pxx (npt.NDArray[np.float64]): power spectral density
141
+ :param one_third_octave_bands (List[Tuple[float, float]]): frequency bands
142
+ :param derivative (List[int]): derivative order
143
+ """
144
+ # frequency step
145
+ delta_f = frequency[1] - frequency[0]
146
+
147
+ # compute the rms value at each frequency band
148
+ for i, band in enumerate(one_third_octave_bands):
149
+ # find indexes where the bands exist
150
+ idx = np.where((frequency >= band[0]) & (frequency < band[1]))[0]
151
+ Pxx[idx] = (2 * np.pi * frequency[idx]) ** (2 * derivative[i]) * Pxx[idx]
152
+ self.rms_bands[i] = np.sqrt(np.sum(Pxx[idx] * delta_f))
153
+
154
+ def __effective_values(self, one_third_octave_bands: List[Tuple[float, float]], derivative: List[int]):
155
+ """
156
+ Computes the effective values of the signal
157
+
158
+ Parameters
159
+ ----------
160
+ :param one_third_octave_bands (List[Tuple[float, float]]): frequency bands
161
+ :param derivative (List[int]): derivative order
162
+ """
163
+
164
+ n = 4 # number of time constants
165
+ tau = 2 # time constant
166
+
167
+ fout = 1 / (1 - np.exp(-n))
168
+
169
+ dx = self.coordinates[1] - self.coordinates[0]
170
+
171
+ for i, band in enumerate(one_third_octave_bands):
172
+ derivative_value = derivative[i]
173
+ sig = TimeSignalProcessing(self.coordinates, self.signal, Fs=self.fs)
174
+ sig.filter(np.array(band), N=3, type_filter="bandpass", filter_design=FilterDesign.BUTTERWORTH)
175
+ new_signal = sig.signal
176
+
177
+ while derivative_value != 0:
178
+ new_signal = np.diff(new_signal) / dx
179
+ derivative_value -= 1
180
+
181
+ ksi = np.linspace(0, n * tau, int(n * tau / dx + 1))
182
+ g = fout * np.exp(-ksi / tau)
183
+
184
+ convoluted_signal = np.sqrt(np.convolve(new_signal**2, g) * dx / tau)
185
+ self.max_fast[i] = np.max(convoluted_signal)
186
+ idx = np.floor((len(self.signal) - np.floor(self.DXmaxFast / dx)) / 2) + \
187
+ np.linspace(0, np.floor(self.DXmaxFast / dx)-1, int(np.floor(self.DXmaxFast / dx)))
188
+
189
+ self.max_fast_Dx[i] = np.max(convoluted_signal[idx.astype(int)])