smoothiepy 0.0.2__py3-none-any.whl → 0.1.0__py3-none-any.whl
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.
- smoothiepy/__init__.py +0 -1
- smoothiepy/core.py +32 -0
- smoothiepy/filter/__init__.py +0 -0
- smoothiepy/filter/basefilter.py +122 -0
- smoothiepy/filter/filter1d.py +324 -0
- smoothiepy/filter/filter2d.py +41 -0
- smoothiepy/filter/filter2d_naive.py +266 -0
- smoothiepy/smoother/__init__.py +0 -0
- smoothiepy/smoother/builder.py +110 -0
- smoothiepy/smoother/smoother.py +200 -0
- smoothiepy-0.1.0.dist-info/METADATA +181 -0
- smoothiepy-0.1.0.dist-info/RECORD +15 -0
- {smoothiepy-0.0.2.dist-info → smoothiepy-0.1.0.dist-info}/WHEEL +1 -1
- smoothiepy-0.1.0.dist-info/entry_points.txt +4 -0
- smoothiepy-0.1.0.dist-info/licenses/LICENSE +674 -0
- smoothiepy/easing_functions.py +0 -231
- smoothiepy-0.0.2.dist-info/LICENSE +0 -21
- smoothiepy-0.0.2.dist-info/METADATA +0 -16
- smoothiepy-0.0.2.dist-info/RECORD +0 -6
smoothiepy/__init__.py
CHANGED
@@ -1 +0,0 @@
|
|
1
|
-
print("Easing with this one! [From smoothiepy]")
|
smoothiepy/core.py
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
"""
|
2
|
+
Main module for the application.
|
3
|
+
"""
|
4
|
+
from smoothiepy.smoother.builder import SmootherBuilder
|
5
|
+
from smoothiepy.filter.filter1d import ExponentialMovingAverageFilter1D
|
6
|
+
|
7
|
+
def main() -> None:
|
8
|
+
"""
|
9
|
+
Main function to demonstrate and test the usage of the Signal Smoother.
|
10
|
+
"""
|
11
|
+
smoother = (
|
12
|
+
SmootherBuilder()
|
13
|
+
.one_dimensional()
|
14
|
+
.continuous()
|
15
|
+
.attach_filter(ExponentialMovingAverageFilter1D(alpha=0.25))
|
16
|
+
.build()
|
17
|
+
)
|
18
|
+
|
19
|
+
print("Init finished")
|
20
|
+
|
21
|
+
smoother.add(40.0)
|
22
|
+
print(f"Smoothed value 1: {smoother.get()}")
|
23
|
+
smoother.add(60.0)
|
24
|
+
print(f"Smoothed value 2: {smoother.get()}")
|
25
|
+
smoother.add(100.0)
|
26
|
+
print(f"Smoothed value 3: {smoother.get()}")
|
27
|
+
smoother.add(3)
|
28
|
+
print(f"Smoothed value 4: {smoother.get()}")
|
29
|
+
|
30
|
+
|
31
|
+
if __name__ == "__main__":
|
32
|
+
main()
|
File without changes
|
@@ -0,0 +1,122 @@
|
|
1
|
+
"""
|
2
|
+
Base module that defines the abstract base class for all signal filters.
|
3
|
+
"""
|
4
|
+
import enum
|
5
|
+
from abc import ABC, abstractmethod
|
6
|
+
from collections import deque
|
7
|
+
import numpy as np
|
8
|
+
|
9
|
+
|
10
|
+
class Filter(ABC):
|
11
|
+
"""
|
12
|
+
Abstract base class for all signal filters.
|
13
|
+
"""
|
14
|
+
|
15
|
+
|
16
|
+
class Filter1D(Filter, ABC):
|
17
|
+
"""
|
18
|
+
Abstract base class for all one-dimensional signal filters.
|
19
|
+
:param window_size: The size of the window for the filter. Must be a positive integer.
|
20
|
+
:type window_size: int
|
21
|
+
:ivar window_size: The size of the window for the filter.
|
22
|
+
:type window_size: int
|
23
|
+
:raises ValueError: If window_size is not a positive integer.
|
24
|
+
"""
|
25
|
+
def __init__(self, window_size: int):
|
26
|
+
if window_size <= 0:
|
27
|
+
raise ValueError("window_size must be greater than 0")
|
28
|
+
self.window_size = window_size
|
29
|
+
self.buffer = deque(maxlen=window_size)
|
30
|
+
self.last_buffer_value: float | int = 0.0
|
31
|
+
|
32
|
+
def next(self, data: float | int) -> float | int:
|
33
|
+
"""
|
34
|
+
Processes the next data point by adding it to the buffer
|
35
|
+
and calling the internal processing method.
|
36
|
+
|
37
|
+
:param data: The next data point to be processed.
|
38
|
+
:type data: float | int
|
39
|
+
:return: The filtered value.
|
40
|
+
:rtype: float | int
|
41
|
+
"""
|
42
|
+
self.last_buffer_value = self.buffer[0] if self.buffer else 0.0
|
43
|
+
self.buffer.append(data)
|
44
|
+
return self._process_next(np.array(self.buffer))
|
45
|
+
|
46
|
+
@abstractmethod
|
47
|
+
def _process_next(self, buffer: np.array) -> float | int:
|
48
|
+
"""
|
49
|
+
Processes the next data point using the current buffer data.
|
50
|
+
|
51
|
+
:param buffer: The current buffer data.
|
52
|
+
:type buffer: list[float | int]
|
53
|
+
:return: The processed value.
|
54
|
+
:rtype: float | int
|
55
|
+
"""
|
56
|
+
|
57
|
+
|
58
|
+
class Filter2D(Filter, ABC):
|
59
|
+
"""
|
60
|
+
Abstract base class for all two-dimensional signal filters.
|
61
|
+
This class is currently a placeholder and does not implement any methods.
|
62
|
+
It serves as a base for future two-dimensional filter implementations.
|
63
|
+
"""
|
64
|
+
def __init__(self, window_size_x: int, window_size_y: int):
|
65
|
+
"""
|
66
|
+
Initializes the 2D filter with specified window sizes.
|
67
|
+
|
68
|
+
:param window_size_x: The size of the window in the x-direction. Must be a positive integer.
|
69
|
+
:type window_size_x: int
|
70
|
+
:param window_size_y: The size of the window in the y-direction. Must be a positive integer.
|
71
|
+
:type window_size_y: int
|
72
|
+
"""
|
73
|
+
if window_size_x <= 0 or window_size_y <= 0:
|
74
|
+
raise ValueError("window_size_x and window_size_y must be greater than 0")
|
75
|
+
self.window_size_x = window_size_x
|
76
|
+
self.window_size_y = window_size_y
|
77
|
+
self.buffer_x = deque(maxlen=window_size_x)
|
78
|
+
self.buffer_y = deque(maxlen=window_size_y)
|
79
|
+
|
80
|
+
def next(self, data_x: float | int, data_y: float | int) -> tuple[float | int, float | int]:
|
81
|
+
"""
|
82
|
+
Processes the next data point in both x and y dimensions by adding
|
83
|
+
them to their respective buffers and calling the internal processing method.
|
84
|
+
|
85
|
+
:param data_x: The next data point in the x dimension.
|
86
|
+
:type data_x: float | int
|
87
|
+
:param data_y: The next data point in the y dimension.
|
88
|
+
:type data_y: float | int
|
89
|
+
:return: A tuple containing the filtered values for x and y dimensions.
|
90
|
+
:rtype: tuple[float | int, float | int]
|
91
|
+
"""
|
92
|
+
self.buffer_x.append(data_x)
|
93
|
+
self.buffer_y.append(data_y)
|
94
|
+
return self._process_next(np.array(self.buffer_x), np.array(self.buffer_y))
|
95
|
+
|
96
|
+
@abstractmethod
|
97
|
+
def _process_next(self, buffer_x: np.array, buffer_y: np.array) \
|
98
|
+
-> tuple[float | int, float | int]:
|
99
|
+
"""
|
100
|
+
Processes the next data points using the current buffer data in both x and y dimensions.
|
101
|
+
|
102
|
+
:param buffer_x: The current buffer data in the x dimension.
|
103
|
+
:type buffer_x: np.array
|
104
|
+
:param buffer_y: The current buffer data in the y dimension.
|
105
|
+
:type buffer_y: np.array
|
106
|
+
:return: A tuple containing the processed values for x and y dimensions.
|
107
|
+
:rtype: tuple[float | int, float | int]
|
108
|
+
"""
|
109
|
+
|
110
|
+
|
111
|
+
class MovingAverageType(enum.Enum):
|
112
|
+
"""
|
113
|
+
Enum for different types of moving averages.
|
114
|
+
|
115
|
+
This enum defines the types of moving averages that can be applied to signals.
|
116
|
+
"""
|
117
|
+
SIMPLE = "simple"
|
118
|
+
WEIGHTED = "weighted"
|
119
|
+
GAUSSIAN = "gaussian"
|
120
|
+
MEDIAN = "median"
|
121
|
+
EXPONENTIAL = "exponential"
|
122
|
+
CUMULATIVE = "cumulative"
|
@@ -0,0 +1,324 @@
|
|
1
|
+
"""
|
2
|
+
Contains the one-dimensional filters used for signal processing.
|
3
|
+
"""
|
4
|
+
from abc import ABC, abstractmethod
|
5
|
+
import numpy as np
|
6
|
+
from typing_extensions import deprecated
|
7
|
+
|
8
|
+
from smoothiepy.filter.basefilter import MovingAverageType, Filter1D
|
9
|
+
from smoothiepy.smoother.builder import SmootherBuilder
|
10
|
+
|
11
|
+
|
12
|
+
# TODO versions for list data -> also account for future data
|
13
|
+
|
14
|
+
@deprecated("Filter has no use, why would you use it?")
|
15
|
+
class UselessFilter1D(Filter1D):
|
16
|
+
"""
|
17
|
+
A filter that does not perform any filtering.
|
18
|
+
It simply returns the input data as is.
|
19
|
+
"""
|
20
|
+
def __init__(self):
|
21
|
+
super().__init__(window_size=1)
|
22
|
+
|
23
|
+
def _process_next(self, buffer: np.array) -> float | int:
|
24
|
+
return buffer[0]
|
25
|
+
|
26
|
+
|
27
|
+
class OffsetFilter1D(Filter1D):
|
28
|
+
"""
|
29
|
+
A filter that applies a constant offset to the input data.
|
30
|
+
|
31
|
+
:param offset: The constant value to be added to the input data.
|
32
|
+
:type offset: float | int
|
33
|
+
"""
|
34
|
+
def __init__(self, offset: float | int):
|
35
|
+
super().__init__(window_size=1)
|
36
|
+
self.offset = offset
|
37
|
+
|
38
|
+
def _process_next(self, buffer: np.array) -> float | int:
|
39
|
+
return buffer[0] + self.offset
|
40
|
+
|
41
|
+
|
42
|
+
class KernelMovingAverageFilter1D(Filter1D, ABC):
|
43
|
+
"""
|
44
|
+
KernelMovingAverageFilter1D is an abstract base class that implements a kernel-based moving
|
45
|
+
average filter in one dimension.
|
46
|
+
|
47
|
+
This class processes data using weights constructed for the filter,
|
48
|
+
providing a weighted average over a defined window size. The weights are
|
49
|
+
normalized automatically during processing.
|
50
|
+
|
51
|
+
:ivar weights: Weights for the kernel moving average filter, calculated by
|
52
|
+
the `_construct_weights` method.
|
53
|
+
:type weights: np.array
|
54
|
+
:ivar weights_sum: Precomputed sum of the weights, used for normalization in
|
55
|
+
the filtering process.
|
56
|
+
:type weights_sum: float
|
57
|
+
"""
|
58
|
+
def __init__(self, window_size: int):
|
59
|
+
super().__init__(window_size)
|
60
|
+
self.weights = self._construct_weights()
|
61
|
+
self.weights_sum = self.weights.sum()
|
62
|
+
|
63
|
+
def _process_next(self, buffer: np.array) -> float | int:
|
64
|
+
if len(buffer) < self.window_size:
|
65
|
+
offset = self.window_size - len(buffer)
|
66
|
+
weighted_sum = np.sum(buffer * self.weights[offset:])
|
67
|
+
cur_weights_sum = self.weights[offset:].sum()
|
68
|
+
if cur_weights_sum == 0:
|
69
|
+
return 0.0
|
70
|
+
return weighted_sum / cur_weights_sum
|
71
|
+
|
72
|
+
weighted_sum = np.sum(buffer * self.weights)
|
73
|
+
return weighted_sum / self.weights_sum
|
74
|
+
|
75
|
+
@abstractmethod
|
76
|
+
def _construct_weights(self) -> np.array:
|
77
|
+
"""
|
78
|
+
Constructs the weights for the kernel moving average filter.
|
79
|
+
This method should be implemented by subclasses to define
|
80
|
+
how the weights are calculated based on the window size.
|
81
|
+
|
82
|
+
The weights don't have to sum up to 1, they are normalized
|
83
|
+
during the processing step.
|
84
|
+
|
85
|
+
:return: An array of weights for the kernel moving average filter.
|
86
|
+
:rtype: np.array
|
87
|
+
"""
|
88
|
+
|
89
|
+
|
90
|
+
class SimpleMovingAverageFilter1D(KernelMovingAverageFilter1D):
|
91
|
+
"""
|
92
|
+
A filter that computes the average of the input data over a specified window size.
|
93
|
+
No weighting is applied, and the average is computed
|
94
|
+
as a simple arithmetic mean of the values in the buffer.
|
95
|
+
|
96
|
+
If not enough data points are available to fill the window,
|
97
|
+
it computes the average of the available data points.
|
98
|
+
|
99
|
+
:ivar window_size: The size of the window for averaging.
|
100
|
+
:type window_size: int
|
101
|
+
"""
|
102
|
+
def _construct_weights(self) -> np.array:
|
103
|
+
return np.ones(self.window_size) / self.window_size
|
104
|
+
|
105
|
+
class WeightedMovingAverageFilter1D(KernelMovingAverageFilter1D):
|
106
|
+
"""
|
107
|
+
A filter that computes a weighted average of the input data over a specified window size.
|
108
|
+
The weights are linearly decreasing from 1 to 0, applied to the most recent data points.
|
109
|
+
|
110
|
+
If not enough data points are available to fill the window,
|
111
|
+
it computes the weighted average of the available data points.
|
112
|
+
|
113
|
+
:ivar window_size: The size of the sliding window used for the filter.
|
114
|
+
:type window_size: int
|
115
|
+
"""
|
116
|
+
def _construct_weights(self) -> np.array:
|
117
|
+
return np.linspace(1, 0, self.window_size)
|
118
|
+
|
119
|
+
|
120
|
+
class GaussianAverageFilter1D(KernelMovingAverageFilter1D):
|
121
|
+
"""
|
122
|
+
Implements a Gaussian Average Filter for one-dimensional data.
|
123
|
+
|
124
|
+
Incorporating a Gaussian weighting function applied over a sliding window of data.
|
125
|
+
It is used for smoothing data by placing higher importance on values closer more
|
126
|
+
recent values of the window while progressively down-weighting values farther away.
|
127
|
+
The Gaussian distribution is controlled via the window size and standard deviation parameters.
|
128
|
+
|
129
|
+
If not enough data points are available to fill the window,
|
130
|
+
it computes the gaussian average of the available data points with
|
131
|
+
a trimmed gaussian filter.
|
132
|
+
|
133
|
+
The filter is only relying on previous data values in the buffer, not future values
|
134
|
+
which would result in a delay / offset in the output.
|
135
|
+
|
136
|
+
:param window_size: Size of the sliding window used for the filter.
|
137
|
+
:type window_size: int
|
138
|
+
:param std_dev: The standard deviation of the Gaussian distribution that determines the spread.
|
139
|
+
Must be a positive value.
|
140
|
+
:type std_dev: float
|
141
|
+
:raises ValueError: If std_dev is negative.
|
142
|
+
"""
|
143
|
+
def __init__(self, window_size: int, std_dev: float = None):
|
144
|
+
if std_dev is None:
|
145
|
+
std_dev = window_size / 3
|
146
|
+
if std_dev <= 0:
|
147
|
+
raise ValueError("std_dev must be a positive value")
|
148
|
+
self.std_dev = std_dev
|
149
|
+
super().__init__(window_size)
|
150
|
+
|
151
|
+
def _construct_weights(self) -> np.array:
|
152
|
+
"""
|
153
|
+
Constructs Gaussian weights based on the specified window size
|
154
|
+
and standard deviation.The weights are not centered around zero,
|
155
|
+
but rather they are computed from the window size down to zero.
|
156
|
+
The most recent data points are given more weight than older ones.
|
157
|
+
|
158
|
+
:return: Computed array of Gaussian weights with length equal to the window size
|
159
|
+
:rtype: np.array
|
160
|
+
"""
|
161
|
+
lin_space = np.linspace(self.window_size, 0, self.window_size) \
|
162
|
+
if self.window_size % 2 == 0 else np.linspace(self.window_size, 0, self.window_size)
|
163
|
+
gaussian = np.exp(-0.5 * (lin_space / self.std_dev) ** 2)
|
164
|
+
return gaussian
|
165
|
+
|
166
|
+
|
167
|
+
class MedianAverageFilter1D(Filter1D):
|
168
|
+
def _process_next(self, buffer: np.array) -> float | int:
|
169
|
+
return np.median(buffer).astype(float)
|
170
|
+
|
171
|
+
|
172
|
+
class ExponentialMovingAverageFilter1D(Filter1D):
|
173
|
+
"""
|
174
|
+
Implements a one-dimensional exponential moving average filter.
|
175
|
+
|
176
|
+
This filter applies exponential moving average weights
|
177
|
+
to the current and the previous filtered data point.
|
178
|
+
The smoothing factor (alpha) determines the weight given to the most recent data point
|
179
|
+
compared to the previous filtered value.
|
180
|
+
|
181
|
+
:ivar alpha: The smoothing factor. Must be between 0 and 1 (inclusive).
|
182
|
+
:type alpha: float
|
183
|
+
"""
|
184
|
+
def __init__(self, alpha: float):
|
185
|
+
super().__init__(window_size=1)
|
186
|
+
if not 0 <= alpha <= 1:
|
187
|
+
raise ValueError("Alpha must be between 0 and 1")
|
188
|
+
|
189
|
+
self.alpha = alpha
|
190
|
+
self.__inverted_alpha = 1 - alpha
|
191
|
+
self.latest_filtered_value: float | int = 0.0
|
192
|
+
|
193
|
+
def _process_next(self, buffer: np.array) -> float | int:
|
194
|
+
if not self.latest_filtered_value:
|
195
|
+
self.latest_filtered_value = buffer[0]
|
196
|
+
return buffer[0]
|
197
|
+
|
198
|
+
self.latest_filtered_value = ((self.alpha * buffer[0])
|
199
|
+
+ (self.__inverted_alpha * self.latest_filtered_value))
|
200
|
+
return self.latest_filtered_value
|
201
|
+
|
202
|
+
|
203
|
+
class CumulativeMovingAverageFilter1D(Filter1D):
|
204
|
+
"""
|
205
|
+
Implements a one-dimensional cumulative moving average filter.
|
206
|
+
|
207
|
+
This filter computes the cumulative average of the input data points
|
208
|
+
as they are processed, updating the average with each new data point.
|
209
|
+
|
210
|
+
:ivar cumulative_average: The current cumulative average of the input data.
|
211
|
+
:type cumulative_average: float | int
|
212
|
+
"""
|
213
|
+
def __init__(self):
|
214
|
+
super().__init__(window_size=1)
|
215
|
+
self.cumulative_average = 0.0
|
216
|
+
self.count = 0
|
217
|
+
|
218
|
+
def _process_next(self, buffer: np.array) -> float | int:
|
219
|
+
self.count += 1
|
220
|
+
self.cumulative_average += (buffer[0] - self.cumulative_average) / self.count
|
221
|
+
return self.cumulative_average
|
222
|
+
|
223
|
+
|
224
|
+
class FixationSmoothFilter1D(Filter1D):
|
225
|
+
"""
|
226
|
+
A filter class to smooth fixation-like data in 1D. This is a type of deadband filter.
|
227
|
+
|
228
|
+
It uses a weighted averaging mechanism in conjunction with standard deviation-based
|
229
|
+
thresholding to determine whether to maintain or update the fixation value. The purpose
|
230
|
+
of this filter is to smooth out noise while preserving significant data trends.
|
231
|
+
|
232
|
+
The noise will be reduced by checking the standard deviation of the data in the buffer
|
233
|
+
and comparing the latest data value with the current fixation value. If both checks pass,
|
234
|
+
the current fixation value is returned. Otherwise, a new fixation value is computed
|
235
|
+
using a weighted average of the data in the buffer.
|
236
|
+
|
237
|
+
Note that this filter cuts out noise totally. It will not smooth out noise.
|
238
|
+
|
239
|
+
An example for the threshold would be calculated using the following formula:
|
240
|
+
``{screen_width_px} * 0.004 + sqrt({window_size})``.
|
241
|
+
Where `screen_width_px` is the width of the screen in pixels and
|
242
|
+
`window_size` is the size of the sliding window used for the filter.
|
243
|
+
This could be used for eye-tracking data to smooth out noise when you fixate on a point
|
244
|
+
for a longer period of time.
|
245
|
+
|
246
|
+
:ivar window_size: The size of the sliding window for the filter.
|
247
|
+
:type window_size: int
|
248
|
+
:ivar threshold: The threshold value used for standard deviation and fixation value difference
|
249
|
+
checks.
|
250
|
+
:type threshold: float
|
251
|
+
:ivar fixation_value: The current fixation value being tracked by the filter.
|
252
|
+
:type fixation_value: float
|
253
|
+
:ivar average_weights: A numpy array of weights used for computing weighted averages
|
254
|
+
over the input data buffer.
|
255
|
+
:type average_weights: numpy.ndarray
|
256
|
+
"""
|
257
|
+
def __init__(self, window_size: int, threshold: float):
|
258
|
+
super().__init__(window_size)
|
259
|
+
self.threshold = threshold
|
260
|
+
self.fixation_value = 0.0
|
261
|
+
self.average_weights = np.linspace(0.2, 1.0, window_size)
|
262
|
+
|
263
|
+
def _process_next(self, buffer: np.array) -> float | int:
|
264
|
+
std_dev = np.std(buffer)
|
265
|
+
latest_data_value = buffer[-1]
|
266
|
+
|
267
|
+
if (abs(std_dev) <= self.threshold
|
268
|
+
and abs(self.fixation_value - latest_data_value) <= self.threshold):
|
269
|
+
return self.fixation_value
|
270
|
+
|
271
|
+
if len(buffer) < self.window_size:
|
272
|
+
offset = self.window_size - len(buffer)
|
273
|
+
self.fixation_value = np.average(buffer, weights=self.average_weights[offset:])
|
274
|
+
else:
|
275
|
+
self.fixation_value = np.average(buffer, weights=self.average_weights)
|
276
|
+
return latest_data_value
|
277
|
+
|
278
|
+
|
279
|
+
class MultiPassMovingAverage1D(Filter1D):
|
280
|
+
"""
|
281
|
+
This class implements a one-dimensional multi-pass moving average filter.
|
282
|
+
|
283
|
+
The MultiPassMovingAverage1D class applies a user-defined number of passes
|
284
|
+
over the data using the specified moving average filter type. It allows
|
285
|
+
different types of moving averages, such as simple, weighted, Gaussian,
|
286
|
+
and median.
|
287
|
+
|
288
|
+
:param window_size: The size of the sliding window for the filter.
|
289
|
+
:type window_size: int
|
290
|
+
:param num_passes: Number of passes to apply to the moving average filter.
|
291
|
+
:type num_passes: int
|
292
|
+
:param average_filter_type: The type of moving average filter to use.
|
293
|
+
:type average_filter_type: MovingAverageType
|
294
|
+
"""
|
295
|
+
def __init__(self, window_size: int, num_passes: int,
|
296
|
+
average_filter_type: MovingAverageType = MovingAverageType.SIMPLE):
|
297
|
+
super().__init__(window_size=1)
|
298
|
+
if num_passes <= 0:
|
299
|
+
raise ValueError("num_passes must be greater than 0")
|
300
|
+
|
301
|
+
self.num_passes = num_passes
|
302
|
+
self.average_filter_type = average_filter_type
|
303
|
+
|
304
|
+
smoother = (
|
305
|
+
SmootherBuilder()
|
306
|
+
.one_dimensional()
|
307
|
+
.continuous()
|
308
|
+
)
|
309
|
+
for _ in range(num_passes):
|
310
|
+
if average_filter_type == MovingAverageType.SIMPLE:
|
311
|
+
smoother.attach_filter(SimpleMovingAverageFilter1D(window_size))
|
312
|
+
elif average_filter_type == MovingAverageType.WEIGHTED:
|
313
|
+
smoother.attach_filter(WeightedMovingAverageFilter1D(window_size))
|
314
|
+
elif average_filter_type == MovingAverageType.GAUSSIAN:
|
315
|
+
smoother.attach_filter(GaussianAverageFilter1D(window_size,
|
316
|
+
std_dev=window_size / 3))
|
317
|
+
elif average_filter_type == MovingAverageType.MEDIAN:
|
318
|
+
smoother.attach_filter(MedianAverageFilter1D(window_size))
|
319
|
+
else:
|
320
|
+
raise ValueError(f"Unsupported average filter type: {average_filter_type}")
|
321
|
+
self.smoother = smoother.build()
|
322
|
+
|
323
|
+
def _process_next(self, buffer: np.array) -> float | int:
|
324
|
+
return self.smoother.add_and_get(buffer[0])
|
@@ -0,0 +1,41 @@
|
|
1
|
+
"""
|
2
|
+
Contains the two-dimensional filters used for signal processing.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import numpy as np
|
6
|
+
|
7
|
+
from smoothiepy.filter.basefilter import Filter2D
|
8
|
+
|
9
|
+
|
10
|
+
class OffsetFilter2D(Filter2D):
|
11
|
+
"""
|
12
|
+
A 2D filter that applies a fixed offset to input data on both x and y axes.
|
13
|
+
Can be used in scenarios where a shift in data coordinates is required.
|
14
|
+
|
15
|
+
:param offset: Offset value to be applied to the x-axis data.
|
16
|
+
If `offset_y` is not provided, it will be set to the same value as `offset`.
|
17
|
+
:type offset: int | float
|
18
|
+
:param offset_y: Offset value to be applied to the y-axis data.
|
19
|
+
If not provided, it defaults to the same value as `offset`.
|
20
|
+
:type offset_y: int | float
|
21
|
+
"""
|
22
|
+
def __init__(self, offset, offset_y = None):
|
23
|
+
if offset_y is None:
|
24
|
+
offset_y = offset
|
25
|
+
super().__init__(window_size_x=1, window_size_y=1)
|
26
|
+
self.offset_x = offset
|
27
|
+
self.offset_y = offset_y
|
28
|
+
|
29
|
+
def _process_next(self, buffer_x: np.array, buffer_y: np.array) \
|
30
|
+
-> tuple[float | int, float | int]:
|
31
|
+
"""
|
32
|
+
Processes the next data point by applying an offset to the input buffers.
|
33
|
+
|
34
|
+
:param buffer_x: The current buffer data for the x-axis.
|
35
|
+
:type buffer_x: np.array
|
36
|
+
:param buffer_y: The current buffer data for the y-axis.
|
37
|
+
:type buffer_y: np.array
|
38
|
+
:return: A tuple containing the processed values for x and y axes.
|
39
|
+
:rtype: tuple[float | int, float | int]
|
40
|
+
"""
|
41
|
+
return buffer_x[0] + self.offset_x, buffer_y[0] + self.offset_y
|