Skip to content

Commit b6eaa6d

Browse files
authored
Merge branch 'develop' into bugflix-imp-cutoff-freq
2 parents 3338b73 + 69ce5cc commit b6eaa6d

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
logger = logging.getLogger(__name__)
2423

@@ -137,7 +136,6 @@ def __init__(self, stream_proc, in_topic):
137136
self.instantiate_buffers()
138137
self.is_initialized = True
139138
self.lifecycle_state = State.STABLE
140-
self.filter = None
141139

142140
@property
143141
def cutoff(self):
@@ -186,12 +184,11 @@ def on_calib_data_received(self, packet):
186184
"Error writing calibration packet, timer has not been set correctly or calibration length is invalid!"
187185
)
188186
if (time.time() - self.calib_started_at) > self.calibration_length:
189-
self.calibration_data_available = True
190187
self.stop_calibration()
191188
return
192189
self.calibration_data_input = np.append(
193190
self.calibration_data_input,
194-
self.filter.apply(packet, in_place=False).get_data()[1],
191+
packet.get_data()[1],
195192
axis=1,
196193
)
197194

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

270263
def stop_calibration(self):
@@ -277,13 +270,16 @@ def stop_calibration(self):
277270

278271
def set_state_from_calibration_data(self, calib_data):
279272
self.lifecycle_state = State.CLEANING
280-
cleaned, state = clean_calib_data(calib_data, self.sr)
273+
filt_sig = bp_filter(calib_data, 45, 55, btype='bandstop', fs=self.sr)
274+
filtered_data = bp_filter(filt_sig, .1, 40, self.sr)
275+
cleaned, state = clean_calib_data(filtered_data, self.sr)
281276
if cleaned is None:
282277
self.calibration_data_available = False
283278
self.lifecycle_state = state
284279
return
285280
try:
286281
self._state = asr_calibrate(cleaned, self.sr, cutoff=self._cutoff)
282+
self.calibration_data_available = True
287283
self.lifecycle_state = state
288284
except np.linalg.LinAlgError as e:
289285
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)