Spaces:
Sleeping
Sleeping
Yann Bouteiller
commited on
Commit
·
0894c08
1
Parent(s):
5c3b17f
new options
Browse files- portiloop/capture.py +103 -26
- portiloop/notebooks/tests.ipynb +2 -15
portiloop/capture.py
CHANGED
@@ -65,15 +65,15 @@ FRONTEND_CONFIG = [
|
|
65 |
0xE0, # CONFIG3 [E0] [PD_REFBUF(bar), 1, 1, BIAS_MEAS, BIASREF_INT, PD_BIAS(bar), BIAS_LOFF_SENS, BIAS_STAT] : Power-down reference buffer, no bias
|
66 |
0x00, # No lead-off
|
67 |
0x60, # CH1SET [60] [PD1, GAIN1[2:0], SRB2, MUX1[2:0]]
|
68 |
-
0x60, # CH2SET
|
69 |
0x60, # CH3SET
|
70 |
0x60, # CH4SET
|
71 |
0x60, # CH5SET
|
72 |
0x60, # CH6SET
|
73 |
0x60, # CH7SET
|
74 |
0x60, # CH8SET
|
75 |
-
|
76 |
-
|
77 |
0x00, # LOFF_SENSP Lead-off on all positive pins?
|
78 |
0x00, # LOFF_SENSN Lead-off on all negative pins?
|
79 |
0x00, # Normal lead-off
|
@@ -130,6 +130,7 @@ def mod_config(config, datarate, channel_modes):
|
|
130 |
pass # PDn = 0 and normal electrode (000)
|
131 |
elif chan_mode == 'disabled':
|
132 |
mod = mod | 0x81 # PDn = 1 and input shorted (001)
|
|
|
133 |
elif chan_mode == 'with bias':
|
134 |
bias_active = True
|
135 |
bit_i = 1 << chan_i
|
@@ -142,9 +143,9 @@ def mod_config(config, datarate, channel_modes):
|
|
142 |
assert False, f"Wrong key: {chan_mode}."
|
143 |
config[n] = mod
|
144 |
if bias_active:
|
145 |
-
config[3] = config[3] | 0x1c
|
146 |
-
for n, c in enumerate(config):
|
147 |
-
print(f"
|
148 |
return config
|
149 |
|
150 |
|
@@ -197,7 +198,17 @@ class FilterPipeline:
|
|
197 |
custom_fir_cutoff=30,
|
198 |
alpha_avg=0.1,
|
199 |
alpha_std=0.001,
|
200 |
-
epsilon=0.000001
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
self.nb_channels = nb_channels
|
202 |
assert power_line_fq in [50, 60], f"The only supported power line frequencies are 50 Hz and 60 Hz"
|
203 |
if power_line_fq == 60:
|
@@ -253,21 +264,24 @@ class FilterPipeline:
|
|
253 |
"""
|
254 |
for i, x in enumerate(value): # loop over the data series
|
255 |
# FIR:
|
256 |
-
|
|
|
257 |
# notch:
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
|
|
262 |
# standardization:
|
263 |
-
if self.
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
|
|
271 |
value[i] = x
|
272 |
return value
|
273 |
|
@@ -275,7 +289,9 @@ class FilterPipeline:
|
|
275 |
class LiveDisplay():
|
276 |
def __init__(self, channel_names, window_len=100):
|
277 |
self.datapoint_dim = len(channel_names)
|
|
|
278 |
self.pp = ProgressPlot(plot_names=channel_names, max_window_len=window_len)
|
|
|
279 |
|
280 |
def add_datapoints(self, datapoints):
|
281 |
"""
|
@@ -284,11 +300,22 @@ class LiveDisplay():
|
|
284 |
Args:
|
285 |
datapoints: list of 8 lists of floats (or list of 8 floats)
|
286 |
"""
|
|
|
|
|
287 |
disp_list = []
|
288 |
for datapoint in datapoints:
|
289 |
d = [[elt] for elt in datapoint]
|
290 |
disp_list.append(d)
|
291 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
292 |
|
293 |
def add_datapoint(self, datapoint):
|
294 |
disp_list = [[elt] for elt in datapoint]
|
@@ -317,7 +344,7 @@ def _capture_process(p_data_o, p_msg_io, duration, frequency, python_clock, time
|
|
317 |
|
318 |
try:
|
319 |
data = frontend.read_regs(0x00, 1)
|
320 |
-
assert data == [0x3E], "The communication with the ADS
|
321 |
leds.led2(Color.BLUE)
|
322 |
|
323 |
config = FRONTEND_CONFIG
|
@@ -383,9 +410,8 @@ def _capture_process(p_data_o, p_msg_io, duration, frequency, python_clock, time
|
|
383 |
|
384 |
p_msg_io.send(("PRT", f"Average frequency: {1 / tot} Hz for {it} samples"))
|
385 |
|
386 |
-
leds.aquisition(False)
|
387 |
-
|
388 |
finally:
|
|
|
389 |
leds.close()
|
390 |
frontend.close()
|
391 |
p_msg_io.send('STOP')
|
@@ -420,6 +446,7 @@ class Capture:
|
|
420 |
self.custom_fir_order = 20
|
421 |
self.custom_fir_cutoff = 30
|
422 |
self.filter = True
|
|
|
423 |
self.record = False
|
424 |
self.detect = False
|
425 |
self.stimulate = False
|
@@ -622,6 +649,27 @@ class Capture:
|
|
622 |
disabled=True
|
623 |
)
|
624 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
625 |
self.b_accordion_filter = widgets.Accordion(
|
626 |
children=[
|
627 |
widgets.VBox([
|
@@ -630,7 +678,12 @@ class Capture:
|
|
630 |
self.b_custom_fir_cutoff,
|
631 |
self.b_polyak_mean,
|
632 |
self.b_polyak_std,
|
633 |
-
self.b_epsilon
|
|
|
|
|
|
|
|
|
|
|
634 |
])
|
635 |
])
|
636 |
self.b_accordion_filter.set_title(index = 0, title = 'Filtering')
|
@@ -707,6 +760,9 @@ class Capture:
|
|
707 |
self.b_threshold.observe(self.on_b_threshold, 'value')
|
708 |
self.b_duration.observe(self.on_b_duration, 'value')
|
709 |
self.b_filter.observe(self.on_b_filter, 'value')
|
|
|
|
|
|
|
710 |
self.b_detect.observe(self.on_b_detect, 'value')
|
711 |
self.b_stimulate.observe(self.on_b_stimulate, 'value')
|
712 |
self.b_record.observe(self.on_b_record, 'value')
|
@@ -767,6 +823,9 @@ class Capture:
|
|
767 |
self.b_polyak_mean.disabled = False
|
768 |
self.b_polyak_std.disabled = False
|
769 |
self.b_epsilon.disabled = False
|
|
|
|
|
|
|
770 |
self.b_custom_fir.disabled = False
|
771 |
self.b_custom_fir_order.disabled = not self.custom_fir
|
772 |
self.b_custom_fir_cutoff.disabled = not self.custom_fir
|
@@ -796,6 +855,9 @@ class Capture:
|
|
796 |
self.b_polyak_mean.disabled = True
|
797 |
self.b_polyak_std.disabled = True
|
798 |
self.b_epsilon.disabled = True
|
|
|
|
|
|
|
799 |
self.b_custom_fir.disabled = True
|
800 |
self.b_custom_fir_order.disabled = True
|
801 |
self.b_custom_fir_cutoff.disabled = True
|
@@ -839,6 +901,7 @@ class Capture:
|
|
839 |
|
840 |
self._t_capture = Thread(target=self.start_capture,
|
841 |
args=(self.filter,
|
|
|
842 |
detector_cls,
|
843 |
self.threshold,
|
844 |
stimulator_cls,
|
@@ -946,6 +1009,18 @@ class Capture:
|
|
946 |
val = value['new']
|
947 |
self.filter = val
|
948 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
949 |
def on_b_stimulate(self, value):
|
950 |
val = value['new']
|
951 |
self.stimulate = val
|
@@ -1015,6 +1090,7 @@ class Capture:
|
|
1015 |
|
1016 |
def start_capture(self,
|
1017 |
filter,
|
|
|
1018 |
detector_cls,
|
1019 |
threshold,
|
1020 |
stimulator_cls,
|
@@ -1041,7 +1117,8 @@ class Capture:
|
|
1041 |
custom_fir_cutoff=self.custom_fir_cutoff,
|
1042 |
alpha_avg=self.polyak_mean,
|
1043 |
alpha_std=self.polyak_std,
|
1044 |
-
epsilon=self.epsilon
|
|
|
1045 |
|
1046 |
detector = detector_cls(threshold) if detector_cls is not None else None
|
1047 |
stimulator = stimulator_cls() if stimulator_cls is not None else None
|
|
|
65 |
0xE0, # CONFIG3 [E0] [PD_REFBUF(bar), 1, 1, BIAS_MEAS, BIASREF_INT, PD_BIAS(bar), BIAS_LOFF_SENS, BIAS_STAT] : Power-down reference buffer, no bias
|
66 |
0x00, # No lead-off
|
67 |
0x60, # CH1SET [60] [PD1, GAIN1[2:0], SRB2, MUX1[2:0]]
|
68 |
+
0x60, # CH2SET
|
69 |
0x60, # CH3SET
|
70 |
0x60, # CH4SET
|
71 |
0x60, # CH5SET
|
72 |
0x60, # CH6SET
|
73 |
0x60, # CH7SET
|
74 |
0x60, # CH8SET
|
75 |
+
0x00, # BIAS_SENSP 00
|
76 |
+
0x00, # BIAS_SENSN 00
|
77 |
0x00, # LOFF_SENSP Lead-off on all positive pins?
|
78 |
0x00, # LOFF_SENSN Lead-off on all negative pins?
|
79 |
0x00, # Normal lead-off
|
|
|
130 |
pass # PDn = 0 and normal electrode (000)
|
131 |
elif chan_mode == 'disabled':
|
132 |
mod = mod | 0x81 # PDn = 1 and input shorted (001)
|
133 |
+
mod = 0xe1
|
134 |
elif chan_mode == 'with bias':
|
135 |
bias_active = True
|
136 |
bit_i = 1 << chan_i
|
|
|
143 |
assert False, f"Wrong key: {chan_mode}."
|
144 |
config[n] = mod
|
145 |
if bias_active:
|
146 |
+
config[3] = config[3] | 0x1c # activate the bias mechanism
|
147 |
+
for n, c in enumerate(config): # print ADS1299 configuration registers
|
148 |
+
print(f"config[{n}]:\t{c:08b}\t({hex(c)})")
|
149 |
return config
|
150 |
|
151 |
|
|
|
198 |
custom_fir_cutoff=30,
|
199 |
alpha_avg=0.1,
|
200 |
alpha_std=0.001,
|
201 |
+
epsilon=0.000001,
|
202 |
+
filter_args=[]):
|
203 |
+
if len(filter_args) > 0:
|
204 |
+
use_fir, use_notch, use_std = filter_args
|
205 |
+
else:
|
206 |
+
use_fir=True,
|
207 |
+
use_notch=True,
|
208 |
+
use_std=True
|
209 |
+
self.use_fir = use_fir
|
210 |
+
self.use_notch = use_notch
|
211 |
+
self.use_std = use_std
|
212 |
self.nb_channels = nb_channels
|
213 |
assert power_line_fq in [50, 60], f"The only supported power line frequencies are 50 Hz and 60 Hz"
|
214 |
if power_line_fq == 60:
|
|
|
264 |
"""
|
265 |
for i, x in enumerate(value): # loop over the data series
|
266 |
# FIR:
|
267 |
+
if self.use_fir:
|
268 |
+
x = self.fir.filter(x)
|
269 |
# notch:
|
270 |
+
if self.use_notch:
|
271 |
+
denAccum = (x - self.notch_coeff1 * self.dfs[0]) - self.notch_coeff2 * self.dfs[1]
|
272 |
+
x = (self.notch_coeff3 * denAccum + self.notch_coeff4 * self.dfs[0]) + self.notch_coeff5 * self.dfs[1]
|
273 |
+
self.dfs[1] = self.dfs[0]
|
274 |
+
self.dfs[0] = denAccum
|
275 |
# standardization:
|
276 |
+
if self.use_std:
|
277 |
+
if self.moving_average is not None:
|
278 |
+
delta = x - self.moving_average
|
279 |
+
self.moving_average = self.moving_average + self.ALPHA_AVG * delta
|
280 |
+
self.moving_variance = (1 - self.ALPHA_STD) * (self.moving_variance + self.ALPHA_STD * delta**2)
|
281 |
+
moving_std = np.sqrt(self.moving_variance)
|
282 |
+
x = (x - self.moving_average) / (moving_std + self.EPSILON)
|
283 |
+
else:
|
284 |
+
self.moving_average = x
|
285 |
value[i] = x
|
286 |
return value
|
287 |
|
|
|
289 |
class LiveDisplay():
|
290 |
def __init__(self, channel_names, window_len=100):
|
291 |
self.datapoint_dim = len(channel_names)
|
292 |
+
self.history = []
|
293 |
self.pp = ProgressPlot(plot_names=channel_names, max_window_len=window_len)
|
294 |
+
self.matplotlib = False
|
295 |
|
296 |
def add_datapoints(self, datapoints):
|
297 |
"""
|
|
|
300 |
Args:
|
301 |
datapoints: list of 8 lists of floats (or list of 8 floats)
|
302 |
"""
|
303 |
+
if self.matplotlib:
|
304 |
+
import matplotlib.pyplot as plt
|
305 |
disp_list = []
|
306 |
for datapoint in datapoints:
|
307 |
d = [[elt] for elt in datapoint]
|
308 |
disp_list.append(d)
|
309 |
+
|
310 |
+
if self.matplotlib:
|
311 |
+
self.history += d[1]
|
312 |
+
|
313 |
+
if not self.matplotlib:
|
314 |
+
self.pp.update_with_datapoints(disp_list)
|
315 |
+
elif len(self.history) == 1000:
|
316 |
+
plt.plot(self.history)
|
317 |
+
plt.show()
|
318 |
+
self.history = []
|
319 |
|
320 |
def add_datapoint(self, datapoint):
|
321 |
disp_list = [[elt] for elt in datapoint]
|
|
|
344 |
|
345 |
try:
|
346 |
data = frontend.read_regs(0x00, 1)
|
347 |
+
assert data == [0x3E], "The communication with the ADS failed, please try again."
|
348 |
leds.led2(Color.BLUE)
|
349 |
|
350 |
config = FRONTEND_CONFIG
|
|
|
410 |
|
411 |
p_msg_io.send(("PRT", f"Average frequency: {1 / tot} Hz for {it} samples"))
|
412 |
|
|
|
|
|
413 |
finally:
|
414 |
+
leds.aquisition(False)
|
415 |
leds.close()
|
416 |
frontend.close()
|
417 |
p_msg_io.send('STOP')
|
|
|
446 |
self.custom_fir_order = 20
|
447 |
self.custom_fir_cutoff = 30
|
448 |
self.filter = True
|
449 |
+
self.filter_args = [True, True, True]
|
450 |
self.record = False
|
451 |
self.detect = False
|
452 |
self.stimulate = False
|
|
|
649 |
disabled=True
|
650 |
)
|
651 |
|
652 |
+
self.b_use_fir = widgets.Checkbox(
|
653 |
+
value=self.filter_args[0],
|
654 |
+
description='Use FIR',
|
655 |
+
disabled=False,
|
656 |
+
indent=False
|
657 |
+
)
|
658 |
+
|
659 |
+
self.b_use_notch = widgets.Checkbox(
|
660 |
+
value=self.filter_args[1],
|
661 |
+
description='Use notch',
|
662 |
+
disabled=False,
|
663 |
+
indent=False
|
664 |
+
)
|
665 |
+
|
666 |
+
self.b_use_std = widgets.Checkbox(
|
667 |
+
value=self.filter_args[2],
|
668 |
+
description='Use standardization',
|
669 |
+
disabled=False,
|
670 |
+
indent=False
|
671 |
+
)
|
672 |
+
|
673 |
self.b_accordion_filter = widgets.Accordion(
|
674 |
children=[
|
675 |
widgets.VBox([
|
|
|
678 |
self.b_custom_fir_cutoff,
|
679 |
self.b_polyak_mean,
|
680 |
self.b_polyak_std,
|
681 |
+
self.b_epsilon,
|
682 |
+
widgets.HBox([
|
683 |
+
self.b_use_fir,
|
684 |
+
self.b_use_notch,
|
685 |
+
self.b_use_std
|
686 |
+
])
|
687 |
])
|
688 |
])
|
689 |
self.b_accordion_filter.set_title(index = 0, title = 'Filtering')
|
|
|
760 |
self.b_threshold.observe(self.on_b_threshold, 'value')
|
761 |
self.b_duration.observe(self.on_b_duration, 'value')
|
762 |
self.b_filter.observe(self.on_b_filter, 'value')
|
763 |
+
self.b_use_fir.observe(self.on_b_use_fir, 'value')
|
764 |
+
self.b_use_notch.observe(self.on_b_use_notch, 'value')
|
765 |
+
self.b_use_std.observe(self.on_b_use_std, 'value')
|
766 |
self.b_detect.observe(self.on_b_detect, 'value')
|
767 |
self.b_stimulate.observe(self.on_b_stimulate, 'value')
|
768 |
self.b_record.observe(self.on_b_record, 'value')
|
|
|
823 |
self.b_polyak_mean.disabled = False
|
824 |
self.b_polyak_std.disabled = False
|
825 |
self.b_epsilon.disabled = False
|
826 |
+
self.b_use_fir.disabled = False
|
827 |
+
self.b_use_notch.disabled = False
|
828 |
+
self.b_use_std.disabled = False
|
829 |
self.b_custom_fir.disabled = False
|
830 |
self.b_custom_fir_order.disabled = not self.custom_fir
|
831 |
self.b_custom_fir_cutoff.disabled = not self.custom_fir
|
|
|
855 |
self.b_polyak_mean.disabled = True
|
856 |
self.b_polyak_std.disabled = True
|
857 |
self.b_epsilon.disabled = True
|
858 |
+
self.b_use_fir.disabled = True
|
859 |
+
self.b_use_notch.disabled = True
|
860 |
+
self.b_use_std.disabled = True
|
861 |
self.b_custom_fir.disabled = True
|
862 |
self.b_custom_fir_order.disabled = True
|
863 |
self.b_custom_fir_cutoff.disabled = True
|
|
|
901 |
|
902 |
self._t_capture = Thread(target=self.start_capture,
|
903 |
args=(self.filter,
|
904 |
+
self.filter_args,
|
905 |
detector_cls,
|
906 |
self.threshold,
|
907 |
stimulator_cls,
|
|
|
1009 |
val = value['new']
|
1010 |
self.filter = val
|
1011 |
|
1012 |
+
def on_b_use_fir(self, value):
|
1013 |
+
val = value['new']
|
1014 |
+
self.filter_args[0] = val
|
1015 |
+
|
1016 |
+
def on_b_use_notch(self, value):
|
1017 |
+
val = value['new']
|
1018 |
+
self.filter_args[1] = val
|
1019 |
+
|
1020 |
+
def on_b_use_std(self, value):
|
1021 |
+
val = value['new']
|
1022 |
+
self.filter_args[2] = val
|
1023 |
+
|
1024 |
def on_b_stimulate(self, value):
|
1025 |
val = value['new']
|
1026 |
self.stimulate = val
|
|
|
1090 |
|
1091 |
def start_capture(self,
|
1092 |
filter,
|
1093 |
+
filter_args,
|
1094 |
detector_cls,
|
1095 |
threshold,
|
1096 |
stimulator_cls,
|
|
|
1117 |
custom_fir_cutoff=self.custom_fir_cutoff,
|
1118 |
alpha_avg=self.polyak_mean,
|
1119 |
alpha_std=self.polyak_std,
|
1120 |
+
epsilon=self.epsilon,
|
1121 |
+
filter_args=filter_args)
|
1122 |
|
1123 |
detector = detector_cls(threshold) if detector_cls is not None else None
|
1124 |
stimulator = stimulator_cls() if stimulator_cls is not None else None
|
portiloop/notebooks/tests.ipynb
CHANGED
@@ -2,25 +2,12 @@
|
|
2 |
"cells": [
|
3 |
{
|
4 |
"cell_type": "code",
|
5 |
-
"execution_count":
|
6 |
"id": "16651843",
|
7 |
"metadata": {
|
8 |
"scrolled": false
|
9 |
},
|
10 |
-
"outputs": [
|
11 |
-
{
|
12 |
-
"ename": "ALSAAudioError",
|
13 |
-
"evalue": "No such file or directory [default]",
|
14 |
-
"output_type": "error",
|
15 |
-
"traceback": [
|
16 |
-
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
17 |
-
"\u001b[0;31mALSAAudioError\u001b[0m Traceback (most recent call last)",
|
18 |
-
"\u001b[0;32m<ipython-input-1-c2044ba37313>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mmy_stimulator_class\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mSleepSpindleRealTimeStimulator\u001b[0m \u001b[0;31m# you may also want to implement yours\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0mcap\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mCapture\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdetector_cls\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmy_detector_class\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstimulator_cls\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmy_stimulator_class\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
19 |
-
"\u001b[0;32m~/portiloop-software/portiloop/capture.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, detector_cls, stimulator_cls)\u001b[0m\n\u001b[1;32m 446\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_test_stimulus\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 447\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 448\u001b[0;31m \u001b[0mmixers\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0malsaaudio\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmixers\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 449\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmixers\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m<=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 450\u001b[0m \u001b[0mwarnings\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwarn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"No ALSA mixer found.\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
20 |
-
"\u001b[0;31mALSAAudioError\u001b[0m: No such file or directory [default]"
|
21 |
-
]
|
22 |
-
}
|
23 |
-
],
|
24 |
"source": [
|
25 |
"from portiloop.capture import Capture\n",
|
26 |
"from portiloop.detection import SleepSpindleRealTimeDetector\n",
|
|
|
2 |
"cells": [
|
3 |
{
|
4 |
"cell_type": "code",
|
5 |
+
"execution_count": null,
|
6 |
"id": "16651843",
|
7 |
"metadata": {
|
8 |
"scrolled": false
|
9 |
},
|
10 |
+
"outputs": [],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
"source": [
|
12 |
"from portiloop.capture import Capture\n",
|
13 |
"from portiloop.detection import SleepSpindleRealTimeDetector\n",
|