smoothiepy 0.0.1__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.
@@ -0,0 +1,266 @@
1
+ """
2
+ Contains the two-dimensional naive filter implementation using
3
+ two 1D filters in x and y directions.
4
+ """
5
+ from abc import ABC
6
+ import numpy as np
7
+
8
+ from smoothiepy.filter.basefilter import Filter2D, Filter1D, MovingAverageType
9
+ from smoothiepy.filter.filter1d import (
10
+ SimpleMovingAverageFilter1D, WeightedMovingAverageFilter1D,
11
+ GaussianAverageFilter1D, MedianAverageFilter1D,
12
+ ExponentialMovingAverageFilter1D, CumulativeMovingAverageFilter1D,
13
+ FixationSmoothFilter1D, MultiPassMovingAverage1D
14
+ )
15
+
16
+
17
+ class NaiveFilter2D(Filter2D, ABC):
18
+ """
19
+ Provides a basic 2D filtering mechanism, where input data is processed
20
+ through two 1D filters (`filter_x` and `filter_y`). It is intended to serve as
21
+ a straightforward 2D filtering implementation that deals with basic
22
+ use cases of filtering in two dimensions.
23
+
24
+ :ivar filter_x: A 1D filter is applied on the x-buffer data.
25
+ :type filter_x: Filter1D | None
26
+ :ivar filter_y: A 1D filter is applied on the y-buffer data.
27
+ :type filter_y: Filter1D | None
28
+ """
29
+ def __init__(self):
30
+ super().__init__(window_size_x=1, window_size_y=1)
31
+ self.filter_x: Filter1D | None = None
32
+ self.filter_y: Filter1D | None = None
33
+
34
+ def _process_next(self, buffer_x: np.array, buffer_y: np.array) \
35
+ -> tuple[float | int, float | int]:
36
+ avg_x = self.filter_x.next(buffer_x[0])
37
+ avg_y = self.filter_y.next(buffer_y[0])
38
+ return avg_x, avg_y
39
+
40
+
41
+ class NaiveSimpleMovingAverageFilter2D(NaiveFilter2D):
42
+ """
43
+ The NaiveSimpleMovingAverageFilter2D class implements a 2D simple moving
44
+ average filter. It smooths sequential data points along both x and y
45
+ axes using separate 1D simple moving average filters.
46
+
47
+ :param window_size: The size of the moving average window for the x-axis.
48
+ Must be a positive integer.
49
+ If the y-axis window size is not provided, the
50
+ x-axis window size will be used for both axes.
51
+ :type window_size: int
52
+ :param window_size_y: The size of the moving average window for the y-axis.
53
+ Must be a positive integer.
54
+ If not provided, it defaults to the x-axis window size.
55
+ :type window_size_y: int
56
+ """
57
+ def __init__(self, window_size, window_size_y=None):
58
+ if window_size_y is None:
59
+ window_size_y = window_size
60
+ super().__init__()
61
+ self.filter_x = SimpleMovingAverageFilter1D(window_size=window_size)
62
+ self.filter_y = SimpleMovingAverageFilter1D(window_size=window_size_y)
63
+
64
+
65
+ class NaiveWeightedMovingAverageFilter2D(NaiveFilter2D):
66
+ """
67
+ A 2D filter that computes a weighted average of the input data over a specified window size
68
+ in both x and y directions. The weights are linearly decreasing from 1 to 0, applied to the
69
+ most recent data points.
70
+
71
+ :param window_size: The size of the weighted moving average window for the x-axis.
72
+ Must be a positive integer.
73
+ If the y-axis window size is not provided, the
74
+ x-axis window size will be used for both axes.
75
+ :type window_size: int
76
+ :param window_size_y: The size of the weighted moving average window for the y-axis.
77
+ :type window_size_y: int
78
+ """
79
+ def __init__(self, window_size: int, window_size_y: int = None):
80
+ if window_size_y is None:
81
+ window_size_y = window_size
82
+ super().__init__()
83
+ self.filter_x = WeightedMovingAverageFilter1D(window_size=window_size)
84
+ self.filter_y = WeightedMovingAverageFilter1D(window_size=window_size_y)
85
+
86
+
87
+ class NaiveGaussianAverageFilter2D(NaiveFilter2D):
88
+ """
89
+ A 2D filter that implements a Gaussian Average Filter for two-dimensional data.
90
+ It applies a Gaussian weighting function over
91
+ a sliding window of data in both x and y directions.
92
+
93
+ :param window_size: The size of the Gaussian window for the x-axis.
94
+ Must be a positive integer.
95
+ If the y-axis window size is not provided, the
96
+ x-axis window size will be used for both axes.
97
+ :type window_size: int
98
+ :param window_size_y: The size of the Gaussian window for the y-axis.
99
+ Must be a positive integer.
100
+ If not provided, it defaults to the x-axis window size.
101
+ :type window_size_y: int
102
+ :param std_dev_x: The standard deviation of the Gaussian distribution for the x-axis.
103
+ Must be a positive value. If not provided, it defaults to one-third of the window size.
104
+ :type std_dev_x: float
105
+ :param std_dev_y: The standard deviation of the Gaussian distribution for the y-axis.
106
+ Must be a positive value. If not provided, it defaults to one-third of the window size_y.
107
+ :type std_dev_y: float
108
+ """
109
+ def __init__(self, window_size: int, window_size_y: int = None,
110
+ std_dev_x: float = None, std_dev_y: float = None):
111
+ if window_size_y is None:
112
+ window_size_y = window_size
113
+ super().__init__()
114
+ self.filter_x = GaussianAverageFilter1D(window_size=window_size, std_dev=std_dev_x)
115
+ self.filter_y = GaussianAverageFilter1D(window_size=window_size_y, std_dev=std_dev_y)
116
+
117
+
118
+ class NaiveMedianAverageFilter2D(NaiveFilter2D):
119
+ """
120
+ A 2D filter that computes the median of the input data over a specified
121
+ window size in both x and y directions.
122
+
123
+ :param window_size: The size of the median window for the x-axis.
124
+ Must be a positive integer.
125
+ If the y-axis window size is not provided, the
126
+ x-axis window size will be used for both axes.
127
+ :type window_size: int
128
+ :param window_size_y: The size of the median window for the y-axis.
129
+ Must be a positive integer.
130
+ If not provided, it defaults to the x-axis window size.
131
+ :type window_size_y: int
132
+ """
133
+ def __init__(self, window_size: int, window_size_y: int = None):
134
+ if window_size_y is None:
135
+ window_size_y = window_size
136
+ super().__init__()
137
+ self.filter_x = MedianAverageFilter1D(window_size=window_size)
138
+ self.filter_y = MedianAverageFilter1D(window_size=window_size_y)
139
+
140
+
141
+ class NaiveExponentialMovingAverageFilter2D(NaiveFilter2D):
142
+ """
143
+ A 2D filter that implements an exponential moving average filter for two-dimensional data.
144
+ It applies exponential moving average weights to the current and
145
+ the previous filtered data points in both x and y directions.
146
+
147
+ :param alpha: The smoothing factor for the x-axis. Must be between 0 and 1 (inclusive).
148
+ If `alpha_y` is not provided, it will be set to the same value as `alpha`.
149
+ :type alpha: float
150
+ :param alpha_y: The smoothing factor for the y-axis. Must be between 0 and 1 (inclusive).
151
+ If not provided, it defaults to the x-axis alpha value.
152
+ :type alpha_y: float
153
+ """
154
+ def __init__(self, alpha: float, alpha_y: float = None):
155
+ if alpha_y is None:
156
+ alpha_y = alpha
157
+ super().__init__()
158
+ self.filter_x = ExponentialMovingAverageFilter1D(alpha=alpha)
159
+ self.filter_y = ExponentialMovingAverageFilter1D(alpha=alpha_y)
160
+
161
+
162
+ class NaiveCumulativeMovingAverageFilter2D(NaiveFilter2D):
163
+ """
164
+ A 2D filter that implements a cumulative moving average filter for two-dimensional data.
165
+ It computes the cumulative average of the input data points as they are processed,
166
+ updating the average with each new data point in both x and y directions.
167
+ """
168
+ def __init__(self):
169
+ super().__init__()
170
+ self.filter_x = CumulativeMovingAverageFilter1D()
171
+ self.filter_y = CumulativeMovingAverageFilter1D()
172
+
173
+
174
+ class NaiveFixationSmoothFilter2D(NaiveFilter2D):
175
+ """
176
+ A 2D filter that implements a fixation smooth filter for two-dimensional data.
177
+ It uses a weighted averaging mechanism in conjunction with
178
+ standard deviation-based thresholding to determine whether to maintain
179
+ or update the fixation value in both x and y directions.
180
+
181
+ An example for the threshold would be calculated using the following formula:
182
+ ``{screen_width_px} * 0.004 + sqrt({window_size})``.
183
+ Where `screen_width_px` is the width of the screen in pixels and
184
+ `window_size` is the size of the sliding window used for the filter.
185
+ This could be used for eye-tracking data to smooth out noise when you fixate on a point
186
+ for a longer period of time.
187
+
188
+ :param window_size: The size of the sliding window for the x-axis.
189
+ Must be a positive integer.
190
+ If the y-axis window size is not provided, it will be set to the
191
+ same value as `window_size`.
192
+ :type window_size: int
193
+ :param window_size_y: The size of the sliding window for the y-axis.
194
+ Must be a positive integer.
195
+ If not provided, it defaults to the x-axis window size.
196
+ :type window_size_y: int
197
+ :param threshold: The threshold value for the x-axis.
198
+ :type threshold: float
199
+ :param threshold_y: The threshold value for the y-axis.
200
+ :type threshold_y: float
201
+ """
202
+ def __init__(self, window_size: int, threshold: float,
203
+ window_size_y: int = None, threshold_y: float = None):
204
+ if window_size_y is None:
205
+ window_size_y = window_size
206
+ if threshold_y is None:
207
+ threshold_y = threshold
208
+ super().__init__()
209
+ self.filter_x = FixationSmoothFilter1D(window_size=window_size, threshold=threshold)
210
+ self.filter_y = FixationSmoothFilter1D(window_size=window_size_y, threshold=threshold_y)
211
+
212
+
213
+ class NaiveMultiPassMovingAverage2D(NaiveFilter2D):
214
+ """
215
+ A 2D filter that implements a multi-pass moving average filter for two-dimensional data.
216
+ It applies a user-defined number of passes over the data using the
217
+ specified moving average filter type in both x and y directions.
218
+
219
+ :param window_size: The size of the sliding window for the x-axis.
220
+ Must be a positive integer.
221
+ If the y-axis window size is not provided, it will be set to the
222
+ same value as `window_size`.
223
+ :type window_size: int
224
+ :param window_size_y: The size of the sliding window for the y-axis.
225
+ Must be a positive integer.
226
+ If not provided, it defaults to the x-axis window size.
227
+ :type window_size_y: int
228
+ :param num_passes: Number of passes to apply to the moving average filter for the x-axis.
229
+ Must be a positive integer.
230
+ If `num_passes_y` is not provided, it will be set to the same value as `num_passes`.
231
+ :type num_passes: int
232
+ :param num_passes_y: Number of passes to apply to the moving average filter for the y-axis.
233
+ Must be a positive integer.
234
+ If not provided, it defaults to the x-axis number of passes.
235
+ :type num_passes_y: int
236
+ :param average_filter_type_x: The type of moving average filter to use for the x-axis.
237
+ Defaults to ``MovingAverageType.SIMPLE``.
238
+ If `average_filter_type_y` is not provided, it will be set to the
239
+ same value as `average_filter_type_x`.
240
+ :type average_filter_type_x: MovingAverageType
241
+ :param average_filter_type_y: The type of moving average filter to use for the y-axis.
242
+ If not provided, it defaults to the x-axis average filter type.
243
+ :type average_filter_type_y: MovingAverageType
244
+ """
245
+ def __init__(self, window_size: int, num_passes: int,
246
+ window_size_y: int = None, num_passes_y: int = None,
247
+ average_filter_type_x: MovingAverageType = MovingAverageType.SIMPLE,
248
+ average_filter_type_y: MovingAverageType = None):
249
+ if window_size_y is None:
250
+ window_size_y = window_size
251
+ if num_passes_y is None:
252
+ num_passes_y = num_passes
253
+ if average_filter_type_y is None:
254
+ average_filter_type_y = average_filter_type_x
255
+
256
+ super().__init__()
257
+ self.filter_x = MultiPassMovingAverage1D(
258
+ window_size=window_size,
259
+ num_passes=num_passes,
260
+ average_filter_type=average_filter_type_x
261
+ )
262
+ self.filter_y = MultiPassMovingAverage1D(
263
+ window_size=window_size_y,
264
+ num_passes=num_passes_y,
265
+ average_filter_type=average_filter_type_y
266
+ )
File without changes
@@ -0,0 +1,110 @@
1
+ """
2
+ Contains the helper classes to build a signal smoother.
3
+ """
4
+ from smoothiepy.filter.basefilter import Filter1D, Filter2D
5
+ from smoothiepy.smoother.smoother import Smoother1DContinuous, Smoother2DContinuous
6
+
7
+
8
+ class SmootherBuilder:
9
+ """
10
+ Provides utilities for building smoother objects for data.
11
+
12
+ This class provides methods to construct smoother objects based on
13
+ specified dimensional requirements. It currently supports
14
+ one-dimensional smoother creation.
15
+ """
16
+ @staticmethod
17
+ def one_dimensional() -> 'Smoother1DBuilder':
18
+ return Smoother1DBuilder()
19
+
20
+ @staticmethod
21
+ def two_dimensional() -> 'Smoother2DBuilder':
22
+ return Smoother2DBuilder()
23
+
24
+ def set_dimensions(self, dimensions: int) -> 'Smoother1DBuilder | Smoother2DBuilder':
25
+ if dimensions == 1:
26
+ return self.one_dimensional()
27
+ if dimensions == 2:
28
+ return self.two_dimensional()
29
+
30
+ raise ValueError("Unsupported dimensions. Currently only 1D and 2D is supported.")
31
+
32
+
33
+ class Smoother1DBuilder:
34
+ """
35
+ Provides a builder for creating 1D signal smoother instances.
36
+
37
+ This class offers static methods to initialize builders for smoothing
38
+ operations tailored to different types of data. It simplifies the
39
+ creation of signal smoother objects by providing predefined builder
40
+ methods.
41
+ """
42
+ @staticmethod
43
+ def continuous() -> 'Smoother1DContinuousBuilder':
44
+ return Smoother1DContinuousBuilder()
45
+
46
+
47
+ class Smoother2DBuilder:
48
+ """
49
+ Provides a builder for creating 2D signal smoother instances.
50
+
51
+ This class offers static methods to initialize builders for smoothing
52
+ operations tailored to different types of data. It simplifies the
53
+ creation of signal smoother objects by providing predefined builder
54
+ methods.
55
+ """
56
+ @staticmethod
57
+ def set_continuous() -> 'Smoother2DContinuousBuilder':
58
+ return Smoother2DContinuousBuilder()
59
+
60
+
61
+ class Smoother1DContinuousBuilder:
62
+ """
63
+ A builder class for creating 1D continuous signal smoother.
64
+
65
+ This class facilitates the creation and configuration of a 1D continuous
66
+ signal smoother by allowing filters to be attached and then constructing
67
+ the smoother object.
68
+ """
69
+ def __init__(self):
70
+ self.__smoother = Smoother1DContinuous()
71
+
72
+ def attach_filter(self, filter_obj: Filter1D) -> 'Smoother1DContinuousBuilder':
73
+ self.__smoother.attach_filter(filter_obj)
74
+ return self
75
+
76
+ def build(self) -> Smoother1DContinuous:
77
+ self.__smoother.build()
78
+ return self.__smoother
79
+
80
+
81
+ class Smoother2DContinuousBuilder:
82
+ """
83
+ A builder class for creating 2D continuous signal smoother.
84
+
85
+ This class facilitates the creation and configuration of a 2D continuous
86
+ signal smoother by allowing filters to be attached and then constructing
87
+ the smoother object.
88
+ """
89
+ def __init__(self):
90
+ self.__smoother = Smoother2DContinuous()
91
+
92
+ def attach_filter(self, filter_obj: Filter2D) -> 'Smoother2DContinuousBuilder':
93
+ self.__smoother.attach_filter(filter_obj)
94
+ return self
95
+
96
+ def build(self) -> Smoother2DContinuous:
97
+ self.__smoother.build()
98
+ return self.__smoother
99
+
100
+
101
+ # class SignalSmootherBuilder1DList:
102
+ # def __init__(self):
103
+ # self.smoother = SignalSmootherSeparateLists1D()
104
+ #
105
+ # def attach_filter(self, filter_obj: BaseFilter1D):
106
+ # self.smoother.attach_filter(filter_obj)
107
+ #
108
+ # def build(self):
109
+ # self.smoother.build()
110
+ # return self.smoother
@@ -0,0 +1,200 @@
1
+ """
2
+ Contains the Signal Smoother class which is the main class to smooth signals.
3
+ """
4
+ from abc import ABC, abstractmethod
5
+ from smoothiepy.filter.basefilter import Filter, Filter1D, Filter2D
6
+
7
+
8
+ class Smoother(ABC):
9
+ """
10
+ Represents an abstract base class for smoothing signals by attaching and managing filters.
11
+
12
+ This class is designed as a foundation for implementing specific signal smoothing
13
+ strategies. It provides a structure for managing filters and implementing custom
14
+ signal smoothing logic.
15
+
16
+ :ivar filter_list: A list that holds the filters attached to this smoother.
17
+ :type filter_list: list[Filter]
18
+ """
19
+ def __init__(self):
20
+ self.filter_list: list[Filter] = []
21
+
22
+ @abstractmethod
23
+ def attach_filter(self, filter_obj: Filter) -> None:
24
+ """
25
+ Attaches a filter object to the implementing class.
26
+
27
+ :param filter_obj: The filter object to attach.
28
+ :type filter_obj: Filter
29
+ """
30
+
31
+ @abstractmethod
32
+ def build(self):
33
+ """
34
+ Builds the final configuration of the smoother, setting up the filters
35
+ and their parameters as needed.
36
+
37
+ :raises NotImplementedError: If the subclass does not implement this method.
38
+ """
39
+
40
+
41
+ class Smoother1D(Smoother, ABC):
42
+ """
43
+ Provides one-dimensional signal smoothing functionality.
44
+
45
+ This class is an extension of the ``Smoother`` class designed specifically
46
+ for one-dimensional signal data. It maintains a list of 1D filters and allows
47
+ attachment of additional filters which must be of type Filter1D.
48
+
49
+ :ivar filter_list: Stores instances of Filter1D for smoothing operations.
50
+ :type filter_list: list[Filter1D]
51
+ """
52
+ def __init__(self):
53
+ super().__init__()
54
+ self.filter_list: list[Filter1D] = []
55
+
56
+ def attach_filter(self, filter_obj: Filter) -> None:
57
+ if not isinstance(filter_obj, Filter1D):
58
+ raise TypeError("filter_obj must be an 1D filter")
59
+
60
+ self.filter_list.append(filter_obj)
61
+
62
+
63
+ class Smoother1DContinuous(Smoother1D):
64
+ """
65
+ A class for continuously smoothing one-dimensional data.
66
+
67
+ Provides functionality to smooth incoming data points in a continuous manner
68
+ using a list of filters.
69
+ The smoothed value is updated as new data points are added.
70
+
71
+ :ivar last_filtered_value: Stores the most recent smoothed value.
72
+ :type last_filtered_value: float | int
73
+ """
74
+ def __init__(self):
75
+ super().__init__()
76
+ self.last_filtered_value: float | int = 0.0
77
+
78
+ def build(self):
79
+ pass
80
+ # self.filter_list[-1].set_buffer_size(1)
81
+ # for cur_filter, next_filter in zip(self.filter_list, self.filter_list[1:]):
82
+ # cur_filter.set_buffer_size(next_filter.window_size)
83
+
84
+ def add_and_get(self, data: float | int) -> float | int:
85
+ """
86
+ Adds the given data to the signal smoother and returns the filtered value.
87
+
88
+ :param data: The numeric value to be added.
89
+ :type data: float | int
90
+ :return: Filtered value after the input has been added.
91
+ :rtype: float | int
92
+ """
93
+ self.add(data)
94
+ return self.get()
95
+
96
+ def add(self, data: float | int) -> None:
97
+ """
98
+ Adds a new data point to the signal smoother.
99
+
100
+ :param data: The data point to be added.
101
+ :type data: float | int
102
+ """
103
+ temp_filtered_value = data
104
+ for cur_filter in self.filter_list:
105
+ temp_filtered_value = cur_filter.next(temp_filtered_value)
106
+
107
+ self.last_filtered_value = temp_filtered_value
108
+
109
+ def get(self) -> float | int:
110
+ """
111
+ Retrieves the smoothed value from the signal smoother.
112
+
113
+ :return: The smoothed value.
114
+ :rtype: float | int
115
+ """
116
+ return self.last_filtered_value
117
+
118
+
119
+ class Smoother2D(Smoother, ABC):
120
+ """
121
+ Provides two-dimensional signal smoothing functionality.
122
+
123
+ This class is an extension of the ``Smoother`` class designed specifically
124
+ for two-dimensional signal data. It maintains a list of 2D filters and allows
125
+ attachment of additional filters which must be of type Filter2D.
126
+
127
+ :ivar filter_list: Stores instances of Filter2D for smoothing operations.
128
+ :type filter_list: list[Filter2D]
129
+ """
130
+ def __init__(self):
131
+ super().__init__()
132
+ self.filter_list: list[Filter2D] = []
133
+
134
+ def attach_filter(self, filter_obj: Filter) -> None:
135
+ if not isinstance(filter_obj, Filter2D):
136
+ raise TypeError("filter_obj must be an 2D filter")
137
+
138
+ self.filter_list.append(filter_obj)
139
+
140
+
141
+ class Smoother2DContinuous(Smoother2D):
142
+ """
143
+ A class for continuously smoothing two-dimensional data.
144
+
145
+ Provides functionality to smooth incoming data points in a continuous manner
146
+ using a list of filters.
147
+ The smoothed value is updated as new data points are added.
148
+
149
+ :ivar last_filtered_value: Stores the most recent smoothed value.
150
+ :type last_filtered_value: float | int
151
+ """
152
+ def __init__(self):
153
+ super().__init__()
154
+ self.last_filtered_value: tuple[float | int, float | int] = (0.0, 0.0)
155
+
156
+ def build(self):
157
+ pass
158
+
159
+ def add_and_get(self, data_x: float | int, data_y: float | int) \
160
+ -> tuple[float | int, float | int]:
161
+ """
162
+ Adds the given data to the signal smoother and returns the filtered value.
163
+
164
+ :param data_x: The first numeric value to be added.
165
+ :type data_x: float | int
166
+ :param data_y: The second numeric value to be added.
167
+ :type data_y: float | int
168
+ :return: Filtered value after the input has been added.
169
+ :rtype: float | int
170
+ """
171
+ self.add(data_x=data_x, data_y=data_y)
172
+ return self.get()
173
+
174
+ def add(self, data_x: float | int, data_y: float | int) -> None:
175
+ """
176
+ Adds a new data point to the signal smoother.
177
+
178
+ :param data_x: The first numeric value to be added.
179
+ :type data_x: float | int
180
+ :param data_y: The second numeric value to be added.
181
+ :type data_y: float | int
182
+ """
183
+ temp_filtered_value = data_x, data_y
184
+ for cur_filter in self.filter_list:
185
+ temp_filtered_value = cur_filter.next(temp_filtered_value)
186
+
187
+ self.last_filtered_value = temp_filtered_value
188
+
189
+ def get(self) -> tuple[float | int, float | int]:
190
+ """
191
+ Retrieves the smoothed value from the signal smoother.
192
+
193
+ :return: The smoothed value.
194
+ :rtype: float | int
195
+ """
196
+ return self.last_filtered_value
197
+
198
+ # class SignalSmootherSeparateLists1D:
199
+ # def filter_list(self, signal: list[float]) -> list[float]:
200
+ # return signal