Skip to content

Commit da82063

Browse files
committed
Merge branch 'bugflix-imp-cutoff-freq' of github.com:Mentalab-hub/explorepy into bugflix-imp-cutoff-freq
2 parents 80aa507 + b6eaa6d commit da82063

6 files changed

Lines changed: 36 additions & 16 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ dependencies = [
4242
'pyyaml',
4343
'bleak==0.22.3',
4444
'pylsl',
45-
'numba']
45+
'numba',
46+
'eegprep']
4647

4748
[tool.setuptools]
4849
package-dir = {"" = "src"}

src/explorepy/_exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ class BleDisconnectionFailedError(Exception):
6262
pass
6363

6464

65+
class ImpedanceModeActiveError(Exception):
66+
"""
67+
Exception for ASR, raised when impedance is running
68+
"""
69+
70+
def __init__(self, message="ASR can not run because impedance mode is active.\n"
71+
"Please disable impedance and try again\n"):
72+
super().__init__(message)
73+
74+
6575
class ExplorePyDeprecationError(Exception):
6676
def __init__(self, message="Explorepy support for legacy devices is deprecated.\n"
6777
"Please install explorepy 3.2.1 from Github or use the following command from Anaconda "

src/explorepy/asr_processor.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
asr_process
1818
)
1919

20-
from explorepy.filters import ExGFilter
21-
20+
from explorepy.filters import bp_filter
2221

2322

2423
logger = logging.getLogger(__name__)
@@ -138,7 +137,6 @@ def __init__(self, stream_proc, in_topic):
138137
self.instantiate_buffers()
139138
self.is_initialized = True
140139
self.lifecycle_state = State.STABLE
141-
self.filter = None
142140

143141
@property
144142
def cutoff(self):
@@ -187,12 +185,11 @@ def on_calib_data_received(self, packet):
187185
"Error writing calibration packet, timer has not been set correctly or calibration length is invalid!"
188186
)
189187
if (time.time() - self.calib_started_at) > self.calibration_length:
190-
self.calibration_data_available = True
191188
self.stop_calibration()
192189
return
193190
self.calibration_data_input = np.append(
194191
self.calibration_data_input,
195-
self.filter.apply(packet, in_place=False).get_data()[1],
192+
packet.get_data()[1],
196193
axis=1,
197194
)
198195

@@ -223,8 +220,10 @@ def clean_data(self):
223220
return
224221
if self.to_clean_ts[0][0] <= 1.0:
225222
return
223+
filt_sig = bp_filter(self.to_clean.copy(), 45, 55, btype='bandstop', fs=self.sr)
224+
filtered_data = bp_filter(filt_sig, .1, 40, fs=self.sr)
226225
try:
227-
ret = asr_pipeline(self.to_clean, self.sr, self.ch_count, self._state)
226+
ret = asr_pipeline(filtered_data, self.sr, self.ch_count, self._state)
228227
self.cleaned_data_available = True
229228
idx = np.searchsorted(self.to_clean_ts[0], self.last_cleaned_timestamp)
230229
self.cleaned_data = ret[:, idx:]
@@ -260,12 +259,6 @@ def start_calibration(self, calib_length: float = -1.0):
260259
f"[{self._min_calibration_length},{self._max_calibration_length}]")
261260
logger.info(f"Starting ASR calibration for {self.calibration_length}s...")
262261
self.calib_started_at = time.time()
263-
self.filter = ExGFilter(
264-
cutoff_freq=(1, 45),
265-
filter_type='bandpass',
266-
s_rate=self.sr,
267-
n_chan=self.ch_count,
268-
)
269262
self.stream_processor.subscribe(self.on_calib_data_received, topic=self.in_topic)
270263

271264
def stop_calibration(self):
@@ -278,13 +271,16 @@ def stop_calibration(self):
278271

279272
def set_state_from_calibration_data(self, calib_data):
280273
self.lifecycle_state = State.CLEANING
281-
cleaned, state = clean_calib_data(calib_data, self.sr)
274+
filt_sig = bp_filter(calib_data, 45, 55, btype='bandstop', fs=self.sr)
275+
filtered_data = bp_filter(filt_sig, .1, 40, self.sr)
276+
cleaned, state = clean_calib_data(filtered_data, self.sr)
282277
if cleaned is None:
283278
self.calibration_data_available = False
284279
self.lifecycle_state = state
285280
return
286281
try:
287282
self._state = asr_calibrate(cleaned, self.sr, cutoff=self._cutoff)
283+
self.calibration_data_available = True
288284
self.lifecycle_state = state
289285
except np.linalg.LinAlgError as e:
290286
self.calibration_data_available = False

src/explorepy/explore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,7 @@ def get_channel_mask(self):
734734
return SettingsManager(self.device_name).get_adc_mask()
735735

736736
def is_asr_processor_available(self):
737-
return self.stream_processor.asr_processor is not None and self.stream_processor.asr_processor.is_initialized
737+
return self.stream_processor.ensure_asr_processor_available()
738738

739739
def calibrate_asr(self, length=-1.0):
740740
if self.is_asr_processor_available():

src/explorepy/filters.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55

66
import numpy as np
7+
from scipy import signal
78
from scipy.signal import (
89
butter,
910
iirfilter,
@@ -17,6 +18,13 @@
1718
logger = logging.getLogger(__name__)
1819

1920

21+
@staticmethod
22+
def bp_filter(exg, lf, hf, fs, btype='bandpass'):
23+
N = 5
24+
b, a = signal.butter(N, [lf, hf], btype=btype, fs=fs)
25+
return signal.filtfilt(b, a, exg, axis=1)
26+
27+
2028
class ExGFilter:
2129
"""Filter class for ExG signals"""
2230
def __init__(self, cutoff_freq, filter_type, s_rate, n_chan, order=5):

src/explorepy/stream_processor.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import numpy as np
1717

18+
from explorepy._exceptions import ImpedanceModeActiveError
1819
from explorepy.asr_processor import AsrProcessor
1920
from explorepy.command import (
2021
DeviceConfiguration,
@@ -617,5 +618,9 @@ def get_power_line_freq(self):
617618
(item for item in self.filters if item.filter_type == 'notch'),
618619
None
619620
)
620-
621621
return match.get_cutoff_freq() if match else None
622+
623+
def ensure_asr_processor_available(self):
624+
if self.is_imp_running():
625+
raise ImpedanceModeActiveError()
626+
return self.asr_processor is not None and self.asr_processor.is_initialized

0 commit comments

Comments
 (0)