emg-gui 0.0.5__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.
Files changed (40) hide show
  1. emg_gui/__init__.py +0 -0
  2. emg_gui/acquisition/__init__.py +0 -0
  3. emg_gui/acquisition/data_source.py +384 -0
  4. emg_gui/acquisition/dataset_files.py +20 -0
  5. emg_gui/acquisition/sensor_worker.py +108 -0
  6. emg_gui/app.py +21 -0
  7. emg_gui/config/Roboto-Black.ttf +0 -0
  8. emg_gui/config/__init__.py +0 -0
  9. emg_gui/config/constants.py +17 -0
  10. emg_gui/core/__init__.py +0 -0
  11. emg_gui/core/enums.py +28 -0
  12. emg_gui/core/logger.py +50 -0
  13. emg_gui/core/types.py +5 -0
  14. emg_gui/processing/__init__.py +0 -0
  15. emg_gui/processing/filter.py +44 -0
  16. emg_gui/processing/window_functions.py +16 -0
  17. emg_gui/ui/__init__.py +0 -0
  18. emg_gui/ui/control_panel.py +84 -0
  19. emg_gui/ui/window.py +95 -0
  20. emg_gui/visualizer/__init__.py +0 -0
  21. emg_gui/visualizer/open_gl_widget.py +139 -0
  22. emg_gui/visualizer/shaders/__init__.py +0 -0
  23. emg_gui/visualizer/shaders/shader_loader.py +26 -0
  24. emg_gui/visualizer/shaders/spec/fragment.glsl +9 -0
  25. emg_gui/visualizer/shaders/spec/vertex.glsl +13 -0
  26. emg_gui/visualizer/shaders/text/fragment.glsl +13 -0
  27. emg_gui/visualizer/shaders/text/vertex.glsl +13 -0
  28. emg_gui/visualizer/shaders/ticks/fragment.glsl +8 -0
  29. emg_gui/visualizer/shaders/ticks/vertex.glsl +9 -0
  30. emg_gui/visualizer/shaders/time_series/fragment.glsl +8 -0
  31. emg_gui/visualizer/shaders/time_series/vertex.glsl +8 -0
  32. emg_gui/visualizer/spectrogram_renderer.py +175 -0
  33. emg_gui/visualizer/text_renderer.py +172 -0
  34. emg_gui/visualizer/ticks_renderer.py +77 -0
  35. emg_gui/visualizer/time_series_renderer.py +144 -0
  36. emg_gui-0.0.5.dist-info/METADATA +206 -0
  37. emg_gui-0.0.5.dist-info/RECORD +40 -0
  38. emg_gui-0.0.5.dist-info/WHEEL +4 -0
  39. emg_gui-0.0.5.dist-info/entry_points.txt +3 -0
  40. emg_gui-0.0.5.dist-info/licenses/LICENSE.md +24 -0
emg_gui/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,384 @@
1
+ import platform
2
+ import sys
3
+ import time
4
+ from typing import Protocol
5
+
6
+ import numpy as np
7
+ from brainflow.board_shim import BoardIds, BoardShim, BrainFlowInputParams
8
+ from brainflow.data_filter import DataFilter
9
+ from serial.tools import list_ports
10
+
11
+ from emg_gui.acquisition.dataset_files import get_all_files
12
+ from emg_gui.config.constants import (
13
+ IS_SYNTHETIC_BOARD,
14
+ MARKER_END_ACTIVITY,
15
+ MARKER_START_ACTIVITY,
16
+ SENSOR_POLL_INTERVAL_MS,
17
+ )
18
+ from emg_gui.core.enums import ActivityState, RecordingState, StreamingState
19
+ from emg_gui.core.logger import Logger
20
+ from emg_gui.core.types import EMGArray
21
+
22
+
23
+ class DataSource(Protocol):
24
+
25
+ def start_stream(self) -> None: ...
26
+
27
+ def stop_stream(self) -> None: ...
28
+
29
+ def start_recording(self) -> None: ...
30
+
31
+ def stop_recording(self) -> None: ...
32
+
33
+ def record(self, data) -> None: ...
34
+
35
+ def insert_start_marker(self) -> None: ...
36
+
37
+ def insert_stop_marker(self) -> None: ...
38
+
39
+ def get_data(self) -> EMGArray: ...
40
+
41
+ def extract_emg_data(self, emg_with_marker_data: EMGArray) -> EMGArray: ...
42
+
43
+ def write_to_csv(self, datasplit: str, folder: str) -> None: ...
44
+
45
+ def release(self) -> None: ...
46
+
47
+ @property
48
+ def emg_channel_count(self) -> int: ...
49
+
50
+ @property
51
+ def is_streaming(self) -> bool: ...
52
+
53
+ @property
54
+ def is_recording(self) -> bool: ...
55
+
56
+ @property
57
+ def is_active(self) -> bool: ...
58
+
59
+ @property
60
+ def sampling_rate(self) -> int: ...
61
+
62
+
63
+ class OpenBCIBoard:
64
+ _BOARD_ID = BoardIds.SYNTHETIC_BOARD if IS_SYNTHETIC_BOARD else BoardIds.CYTON_BOARD
65
+
66
+ _PORT_NAMES = ("FT231X USB UART", "VCP")
67
+
68
+ _instance = None
69
+
70
+ @classmethod
71
+ def _get_serial_port(cls) -> str:
72
+ ports = []
73
+
74
+ for port in list_ports.comports():
75
+ description = port.description or ""
76
+
77
+ if description.startswith(cls._PORT_NAMES):
78
+ if platform.system() == "Darwin" and port.device.startswith("/dev/tty"):
79
+ continue
80
+
81
+ ports.append(port.device)
82
+
83
+ if not ports:
84
+ raise RuntimeError("DATASOURCE: No OpenBCI Cyton dongle found")
85
+
86
+ return ports[0]
87
+
88
+ def __init__(self, logger: Logger) -> None:
89
+ BoardShim.enable_dev_board_logger()
90
+ self._logger = logger
91
+
92
+ _params = BrainFlowInputParams()
93
+
94
+ if not IS_SYNTHETIC_BOARD:
95
+ _params.serial_port = self._get_serial_port()
96
+ self._logger.info(f"DATASOURCE: Port - {_params.serial_port}")
97
+
98
+ self._brainflow_emg_channels = BoardShim.get_emg_channels(
99
+ board_id=self._BOARD_ID
100
+ )
101
+ brainflow_marker_channel = BoardShim.get_marker_channel(board_id=self._BOARD_ID)
102
+
103
+ self._data_channels = self._brainflow_emg_channels + [brainflow_marker_channel]
104
+ self._emg_channel_count = len(self._brainflow_emg_channels)
105
+
106
+ self._board = BoardShim(board_id=self._BOARD_ID, input_params=_params)
107
+ self._board.prepare_session()
108
+ if not self._board.is_prepared():
109
+ self._logger.error("DATASOURCE: Board cannot be initialized")
110
+ sys.exit(1)
111
+
112
+ self._turn_off_srb(self._board)
113
+ self._logger.info("DATASOURCE: Board initialized and SRB channels turned-off")
114
+
115
+ self._streaming_state = StreamingState.IDLE
116
+ self._recording_state = RecordingState.IDLE
117
+ self._activity_state = ActivityState.INACTIVE
118
+
119
+ self._emg_recording: list[EMGArray] = []
120
+
121
+ @classmethod
122
+ def get_instance(cls, logger: Logger) -> DataSource:
123
+ if cls._instance is None:
124
+ cls._instance = cls(logger)
125
+ return cls._instance
126
+
127
+ @property
128
+ def emg_channel_count(self) -> int:
129
+ return self._emg_channel_count
130
+
131
+ def start_stream(self) -> None:
132
+ if self.is_streaming:
133
+ raise RuntimeError("DATASOURCE: Streaming has already started")
134
+
135
+ if self._board.is_prepared():
136
+ self._board.start_stream()
137
+ self._logger.info(
138
+ f"DATASOURCE: Data Stream Started (synthetic data: {IS_SYNTHETIC_BOARD})"
139
+ )
140
+ else:
141
+ self._logger.error("DATASOURCE: Unable to connect with OpenBCI board")
142
+ raise RuntimeError("DATASOURCE: Unable to connect with OpenBCI board")
143
+
144
+ self._streaming_state = StreamingState.STREAMING
145
+
146
+ def stop_stream(self) -> None:
147
+ if not self.is_streaming:
148
+ raise RuntimeError("DATASOURCE: Streaming has already stopped")
149
+
150
+ self._board.stop_stream()
151
+ self._logger.info("DATASOURCE: Data Stream closed successfully")
152
+
153
+ flush_data = self._board.get_board_data()
154
+ self._logger.info(
155
+ f"DATASOURCE: Brainflow ring buffer cleaned: {flush_data.shape[1]}"
156
+ )
157
+
158
+ self._logger.info(
159
+ f"DATASOURCE: Brainflow ring buffer after cleaning: {self._board.get_board_data_count()}"
160
+ )
161
+
162
+ self._streaming_state = StreamingState.IDLE
163
+
164
+ def start_recording(self) -> None:
165
+ if self.is_recording:
166
+ raise RuntimeError("DATASOURCE: Recording has already started")
167
+
168
+ self._recording_state = RecordingState.RECORDING
169
+
170
+ def stop_recording(self) -> None:
171
+ if not self.is_recording:
172
+ raise RuntimeError("DATASOURCE: Recording has already stopped")
173
+
174
+ self._recording_state = RecordingState.IDLE
175
+
176
+ def record(self, data) -> None:
177
+ if self.is_recording:
178
+ self._emg_recording.append(data)
179
+
180
+ def insert_start_marker(self) -> None:
181
+ if self.is_active:
182
+ raise RuntimeError("DATASOURCE: Activity has already started")
183
+
184
+ self._board.insert_marker(MARKER_START_ACTIVITY)
185
+ self._activity_state = ActivityState.ACTIVE
186
+
187
+ def insert_stop_marker(self) -> None:
188
+ if not self.is_active:
189
+ raise RuntimeError("DATASOURCE: Activity has already stopped")
190
+
191
+ self._board.insert_marker(MARKER_END_ACTIVITY)
192
+ self._activity_state = ActivityState.INACTIVE
193
+
194
+ def get_data(self) -> EMGArray:
195
+ all_board_data = self._board.get_board_data()
196
+ active_channel_data = all_board_data[self._data_channels, :]
197
+ return active_channel_data
198
+
199
+ def extract_emg_data(self, emg_with_marker_data: EMGArray) -> EMGArray:
200
+ emg_data = emg_with_marker_data[
201
+ : self.emg_channel_count, :
202
+ ] # assumes marker is last
203
+ return emg_data
204
+
205
+ def write_to_csv(self, datasplit: str, folder: str) -> None:
206
+ emg_numpy = np.concatenate(self._emg_recording, axis=1)
207
+ file_path = (
208
+ f"data/csv/{datasplit}/{folder}/{str(int(time.time()))}__{folder}.csv"
209
+ )
210
+ DataFilter.write_file(data=emg_numpy, file_name=file_path, file_mode="w")
211
+ file_ds = get_all_files(
212
+ pattern=f"data/csv/{datasplit}/{folder}/*.csv", shuffle_flag=False
213
+ ) # hard-coded: [TECH DEBT]
214
+ self._logger.info(
215
+ f"DATASOURCE: File written @ {file_path} (Total files: {sum(1 for _ in file_ds)})"
216
+ )
217
+ self._emg_recording.clear()
218
+ self._logger.info("DATASOURCE: Source data storage cleaned(Reset)")
219
+
220
+ def release(self) -> None:
221
+ if self.is_streaming:
222
+ self.stop_stream()
223
+ if self._board.is_prepared():
224
+ self._board.release_session()
225
+
226
+ @property
227
+ def sampling_rate(self) -> int:
228
+ return self._board.get_sampling_rate(self._BOARD_ID)
229
+
230
+ @property
231
+ def is_streaming(self) -> bool:
232
+ return self._streaming_state is StreamingState.STREAMING
233
+
234
+ @property
235
+ def is_recording(self) -> bool:
236
+ return self._recording_state is RecordingState.RECORDING
237
+
238
+ @property
239
+ def is_active(self) -> bool:
240
+ return self._activity_state is ActivityState.ACTIVE
241
+
242
+ def _turn_off_srb(self, board):
243
+ for channel in self._brainflow_emg_channels:
244
+ board_response = board.config_board(f"x{channel}060100X")[:1]
245
+ if board_response not in {"S", "C"}:
246
+ sys.exit(1)
247
+
248
+
249
+ class PlaybackRecording:
250
+ _instance = None
251
+
252
+ def __init__(self, logger: Logger, file_path: str) -> None:
253
+ self._logger = logger
254
+ self._file_path = file_path
255
+
256
+ self._data = np.loadtxt(self._file_path, delimiter="\t").T
257
+ self._current_idx = 0
258
+ self._max_idx = self._data.shape[1]
259
+
260
+ self._data_channels = list(range(self._data.shape[0]))
261
+ self._emg_channel_count = len(self._data_channels) - 1 # assumes marker is last
262
+
263
+ self._logger.info(
264
+ f"DATASOURCE: PlaybackRecording - file: {self._file_path} "
265
+ f"- Channels: {self._data_channels} "
266
+ f"- EMG Channels Count: {self._emg_channel_count}"
267
+ )
268
+
269
+ self._streaming_state = StreamingState.IDLE
270
+ self._recording_state = RecordingState.IDLE
271
+ self._activity_state = ActivityState.INACTIVE
272
+
273
+ @classmethod
274
+ def get_instance(cls, logger: Logger, file_path: str) -> DataSource:
275
+ if cls._instance is None:
276
+ cls._instance = cls(logger, file_path)
277
+ return cls._instance
278
+
279
+ def start_stream(self) -> None:
280
+ if self.is_streaming:
281
+ raise RuntimeError("DATASOURCE: Playback has already started")
282
+
283
+ self._logger.info("DATASOURCE: Playback Started (playing-recording)")
284
+ self._current_idx = 0
285
+ self._streaming_state = StreamingState.STREAMING
286
+
287
+ def stop_stream(self) -> None:
288
+ if not self.is_streaming:
289
+ raise RuntimeError("DATASOURCE: Playback has already stopped")
290
+
291
+ self._logger.info("DATASOURCE: Playback closed successfully")
292
+
293
+ self._streaming_state = StreamingState.IDLE
294
+
295
+ def start_recording(self) -> None:
296
+ if self.is_recording:
297
+ raise RuntimeError("DATASOURCE: Recording has already started")
298
+
299
+ self._recording_state = RecordingState.RECORDING
300
+
301
+ def stop_recording(self) -> None:
302
+ if not self.is_recording:
303
+ raise RuntimeError("DATASOURCE: Recording has already stopped")
304
+
305
+ self._recording_state = RecordingState.IDLE
306
+
307
+ def record(self, data) -> None:
308
+ pass
309
+
310
+ def insert_start_marker(self) -> None:
311
+ if self.is_active:
312
+ raise RuntimeError("DATASOURCE: Activity has already started")
313
+
314
+ self._activity_state = ActivityState.ACTIVE
315
+
316
+ def insert_stop_marker(self) -> None:
317
+ if not self.is_active:
318
+ raise RuntimeError("DATASOURCE: Activity has already stopped")
319
+
320
+ self._activity_state = ActivityState.INACTIVE
321
+
322
+ def get_data(self) -> EMGArray:
323
+ expected_sample_count = round(
324
+ self.sampling_rate * SENSOR_POLL_INTERVAL_MS / 1000
325
+ ) # playback speed
326
+
327
+ end_idx = min(self._current_idx + expected_sample_count, self._max_idx)
328
+ board_data = self._data[:, self._current_idx : end_idx]
329
+ self._current_idx = end_idx
330
+
331
+ return self._add_padding(
332
+ board_data, len(self._data_channels), expected_sample_count
333
+ )
334
+
335
+ def extract_emg_data(self, emg_with_marker_data: EMGArray) -> EMGArray:
336
+ emg_data = emg_with_marker_data[
337
+ : self.emg_channel_count, :
338
+ ] # assumes marker is last
339
+ return emg_data
340
+
341
+ @staticmethod
342
+ def _add_padding(
343
+ board_data: EMGArray,
344
+ channel_count: int,
345
+ expected_samples: int,
346
+ ) -> EMGArray:
347
+ received_samples = board_data.shape[-1]
348
+
349
+ if received_samples == expected_samples:
350
+ return board_data
351
+
352
+ padded_data = np.zeros(
353
+ (channel_count, expected_samples),
354
+ dtype=board_data.dtype,
355
+ )
356
+ padded_data[:, :received_samples] = board_data
357
+
358
+ return padded_data
359
+
360
+ def write_to_csv(self, datasplit: str, folder: str) -> None:
361
+ return None
362
+
363
+ def release(self) -> None:
364
+ return None
365
+
366
+ @property
367
+ def sampling_rate(self) -> int:
368
+ return 250 # assumes cyton board
369
+
370
+ @property
371
+ def emg_channel_count(self) -> int:
372
+ return self._emg_channel_count
373
+
374
+ @property
375
+ def is_streaming(self) -> bool:
376
+ return self._streaming_state is StreamingState.STREAMING
377
+
378
+ @property
379
+ def is_recording(self) -> bool:
380
+ return self._recording_state is RecordingState.RECORDING
381
+
382
+ @property
383
+ def is_active(self) -> bool:
384
+ return self._activity_state is ActivityState.ACTIVE
@@ -0,0 +1,20 @@
1
+ import glob
2
+ import os
3
+ import random
4
+ from pathlib import Path
5
+
6
+ _CSV_PATH = Path("data/csv")
7
+
8
+
9
+ def get_all_labels(selected_type="train") -> list[str]: # Default from train
10
+ train_path = os.path.join(_CSV_PATH, selected_type)
11
+ return os.listdir(path=train_path)
12
+
13
+
14
+ def get_all_files(pattern: str, shuffle_flag: bool) -> list[str]:
15
+ files = glob.glob(pattern)
16
+
17
+ if shuffle_flag:
18
+ random.shuffle(files)
19
+
20
+ return files
@@ -0,0 +1,108 @@
1
+ import numpy as np
2
+ from dvg_ringbuffer import RingBuffer
3
+ from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
4
+
5
+ from emg_gui.acquisition.data_source import DataSource
6
+ from emg_gui.config.constants import (
7
+ EDGE_ARTIFACT_BUFFER,
8
+ SENSOR_POLL_INTERVAL_MS,
9
+ TIME_WINDOW_SAMPLES,
10
+ )
11
+ from emg_gui.core.logger import Logger
12
+ from emg_gui.core.types import EMGArray
13
+
14
+
15
+ class SensorWorker(QObject):
16
+
17
+ stream_changed = pyqtSignal(str)
18
+ record_changed = pyqtSignal(str)
19
+ marker_changed = pyqtSignal(str)
20
+ reset_changed = pyqtSignal(str)
21
+
22
+ def __init__(self, logger: Logger, board: DataSource) -> None:
23
+ super().__init__()
24
+ self._logger = logger
25
+ self._board = board
26
+
27
+ _buffer_length = TIME_WINDOW_SAMPLES + EDGE_ARTIFACT_BUFFER
28
+ self._emg_channel_count = board.emg_channel_count
29
+
30
+ self._zero_window = np.zeros(
31
+ (_buffer_length, self._emg_channel_count), dtype=np.float64
32
+ )
33
+
34
+ self._ring_buffer = RingBuffer(
35
+ capacity=_buffer_length,
36
+ dtype=(
37
+ np.float64,
38
+ self._emg_channel_count,
39
+ ), # pyright: ignore[reportArgumentType]
40
+ )
41
+ self._ring_buffer.extend(self._zero_window)
42
+
43
+ self._latest_snapshot = self._zero_window.T.copy(order="C")
44
+
45
+ self._sensor_timer = QTimer(self)
46
+ self._sensor_timer.timeout.connect(self._receive_sensor_data)
47
+ self._sensor_timer.setInterval(SENSOR_POLL_INTERVAL_MS)
48
+
49
+ @pyqtSlot()
50
+ def _receive_sensor_data(self) -> None:
51
+ emg_with_marker_data = self._board.get_data()
52
+ self._board.record(emg_with_marker_data)
53
+ emg_data = self._board.extract_emg_data(emg_with_marker_data)
54
+ self._ring_buffer.extend(emg_data.T)
55
+ buffer_copy = self._ring_buffer[:].T.copy(order="C")
56
+ self._latest_snapshot = (
57
+ buffer_copy # atomic assignment swaps without locks: thread safe
58
+ )
59
+
60
+ @pyqtSlot()
61
+ def publish_buffer_snapshot(self) -> tuple[EMGArray, bool]:
62
+ """Called/Runs directly by the GUI thread without any locks."""
63
+ return (self._latest_snapshot, self._board.is_streaming)
64
+
65
+ @pyqtSlot()
66
+ def stream(self) -> None:
67
+ if self._board.is_streaming:
68
+ self._sensor_timer.stop()
69
+ self._ring_buffer.extend(self._zero_window)
70
+ self._latest_snapshot = self._zero_window.T.copy(order="C")
71
+ self._board.stop_stream()
72
+ self.stream_changed.emit("Stream")
73
+ return
74
+
75
+ self._board.start_stream()
76
+ self._sensor_timer.start()
77
+ self.stream_changed.emit("Streaming")
78
+
79
+ @pyqtSlot()
80
+ def shutdown_timer(self) -> None:
81
+ if self._sensor_timer.isActive():
82
+ self._logger.info("SENSORWORKER: Killed QTimer in sensor thread")
83
+ self._sensor_timer.stop()
84
+
85
+ @pyqtSlot(str, str)
86
+ def record(self, selected_datasplit: str, selected_folder: str) -> None:
87
+ if self._board.is_recording:
88
+ self._board.stop_recording()
89
+ self._board.write_to_csv(selected_datasplit, selected_folder)
90
+ self.record_changed.emit("Record")
91
+ return
92
+
93
+ self._board.start_recording()
94
+ self.record_changed.emit("Recording")
95
+
96
+ @pyqtSlot()
97
+ def marker(self) -> None:
98
+ if self._board.is_active:
99
+ self._board.insert_stop_marker()
100
+ self.marker_changed.emit("Start Movement")
101
+ return
102
+
103
+ self._board.insert_start_marker()
104
+ self.marker_changed.emit("Stop Movement")
105
+
106
+ @pyqtSlot()
107
+ def reset(self) -> None:
108
+ self._ring_buffer.extend(self._zero_window)
emg_gui/app.py ADDED
@@ -0,0 +1,21 @@
1
+ from emg_gui.acquisition.data_source import DataSource, OpenBCIBoard
2
+ from emg_gui.core.logger import ConsoleLogger, Logger
3
+ from emg_gui.ui.window import EMGVisualizerWindow
4
+
5
+
6
+ def main() -> None:
7
+ logger: Logger = ConsoleLogger.get_instance()
8
+ board: DataSource = OpenBCIBoard.get_instance(logger)
9
+ # board: DataSource = PlaybackRecording.get_instance(
10
+ # logger, "data/csv/train/sample/1785181476__sample.csv"
11
+ # )
12
+
13
+ try:
14
+ EMGVisualizerWindow.run(logger, board)
15
+ finally:
16
+ board.release()
17
+ logger.release()
18
+
19
+
20
+ if __name__ == "__main__":
21
+ main()
Binary file
File without changes
@@ -0,0 +1,17 @@
1
+ IS_SYNTHETIC_BOARD = True
2
+
3
+ SENSOR_POLL_INTERVAL_MS = 40
4
+
5
+ TIME_WINDOW_SAMPLES = 1200
6
+ SPECTROGRAM_WINDOW = 100
7
+ EDGE_ARTIFACT_BUFFER = 500
8
+
9
+ MARKER_START_ACTIVITY = 1.12345
10
+ MARKER_END_ACTIVITY = 2.12345
11
+
12
+ MAJOR_PIXEL_TICKS = 20
13
+ MINOR_PIXEL_TICKS = 10
14
+
15
+ TEXT_PIXEL_TICKS = 20
16
+ TEXT_SCALE = 2
17
+ TEXT_FONT_SIZE = 12
File without changes
emg_gui/core/enums.py ADDED
@@ -0,0 +1,28 @@
1
+ from enum import Enum, auto
2
+
3
+
4
+ class StreamingState(Enum):
5
+ IDLE = auto()
6
+ STREAMING = auto()
7
+
8
+
9
+ class RecordingState(Enum):
10
+ IDLE = auto()
11
+ RECORDING = auto()
12
+
13
+
14
+ class ActivityState(Enum):
15
+ ACTIVE = auto()
16
+ INACTIVE = auto()
17
+
18
+
19
+ class DatasetSplit(str, Enum):
20
+ TRAIN = "train"
21
+ VALIDATE = "validate"
22
+ TEST = "test"
23
+
24
+
25
+ class DataSourceType(str, Enum):
26
+ REAL = "real"
27
+ SYNTHETIC = "synthetic"
28
+ PLAYBACK = "playback"
emg_gui/core/logger.py ADDED
@@ -0,0 +1,50 @@
1
+ import logging
2
+ from typing import Protocol
3
+
4
+
5
+ class Logger(Protocol):
6
+ def info(self, message: str) -> None: ...
7
+
8
+ def error(self, message: str) -> None: ...
9
+
10
+ def release(self) -> None: ...
11
+
12
+
13
+ class ConsoleLogger:
14
+ _instance = None
15
+
16
+ @classmethod
17
+ def get_instance(cls) -> Logger:
18
+ if cls._instance is None:
19
+ cls._instance = cls()
20
+ return cls._instance
21
+
22
+ def __init__(self) -> None:
23
+ self._logger = logging.getLogger("emg-gui")
24
+ self._logger.setLevel(logging.INFO)
25
+ self._logger.propagate = False
26
+
27
+ if not self._logger.handlers:
28
+ self._logger.addHandler(ConsoleLogger._get_handler())
29
+
30
+ def info(self, message: str) -> None:
31
+ self._logger.info(msg=message)
32
+
33
+ def error(self, message: str) -> None:
34
+ self._logger.error(msg=message)
35
+
36
+ def release(self) -> None:
37
+ handlers = self._logger.handlers[:]
38
+ for handler in handlers:
39
+ self._logger.removeHandler(handler)
40
+ handler.close()
41
+ ConsoleLogger._instance = None
42
+ print("Logger resources released successfully.")
43
+
44
+ @staticmethod
45
+ def _get_handler() -> logging.StreamHandler:
46
+ handler = logging.StreamHandler()
47
+ _format = "%(asctime)s - %(levelname)s - %(filename)s - %(message)s"
48
+ _formatter = logging.Formatter(_format)
49
+ handler.setFormatter(_formatter)
50
+ return handler
emg_gui/core/types.py ADDED
@@ -0,0 +1,5 @@
1
+ from typing import Any
2
+
3
+ import numpy as np
4
+
5
+ EMGArray = np.ndarray[Any, np.dtype[np.float64]]
File without changes
@@ -0,0 +1,44 @@
1
+ from brainflow.board_shim import BoardIds, BoardShim
2
+ from brainflow.data_filter import AggOperations, DataFilter, FilterTypes, NoiseTypes
3
+
4
+ from emg_gui.config.constants import IS_SYNTHETIC_BOARD
5
+ from emg_gui.core.types import EMGArray
6
+
7
+ _BOARD_ID = BoardIds.SYNTHETIC_BOARD if IS_SYNTHETIC_BOARD else BoardIds.CYTON_BOARD
8
+
9
+
10
+ def filter_data(time_series: EMGArray) -> None:
11
+
12
+ DataFilter.perform_rolling_filter(time_series, 3, AggOperations.MEAN.value)
13
+
14
+ DataFilter.perform_bandstop(
15
+ data=time_series,
16
+ sampling_rate=BoardShim.get_sampling_rate(_BOARD_ID),
17
+ start_freq=58.0,
18
+ stop_freq=62.0,
19
+ order=4,
20
+ filter_type=FilterTypes.BUTTERWORTH,
21
+ ripple=1.0,
22
+ )
23
+
24
+ DataFilter.perform_bandpass(
25
+ data=time_series,
26
+ sampling_rate=BoardShim.get_sampling_rate(_BOARD_ID),
27
+ start_freq=10.0,
28
+ stop_freq=125.0,
29
+ order=4,
30
+ filter_type=FilterTypes.BUTTERWORTH,
31
+ ripple=1.0,
32
+ )
33
+
34
+ DataFilter.remove_environmental_noise(
35
+ data=time_series,
36
+ sampling_rate=BoardShim.get_sampling_rate(_BOARD_ID),
37
+ noise_type=NoiseTypes.FIFTY.value,
38
+ )
39
+
40
+ DataFilter.remove_environmental_noise(
41
+ data=time_series,
42
+ sampling_rate=BoardShim.get_sampling_rate(_BOARD_ID),
43
+ noise_type=NoiseTypes.SIXTY.value,
44
+ )