Spaces:
Sleeping
Sleeping
Yann Bouteiller
commited on
Commit
·
1a609ad
1
Parent(s):
6da664d
Changed to new BIAS mechanism and added Leadoff detection
Browse files- portiloop/capture.py +80 -44
portiloop/capture.py
CHANGED
@@ -57,14 +57,13 @@ DEFAULT_FRONTEND_CONFIG = [
|
|
57 |
0x00, # CONFIG4 [00] [0, 0, 0, 0, SINGLE_SHOT, 0, PD_LOFF_COMP(bar), 0] : Single-shot, lead-off comparator disabled
|
58 |
]
|
59 |
|
60 |
-
|
61 |
FRONTEND_CONFIG = [
|
62 |
0x3E, # ID (RO)
|
63 |
0x95, # CONFIG1 [95] [1, DAISY_EN(bar), CLK_EN, 1, 0, DR[2:0]] : Datarate = 500 SPS
|
64 |
0xC0, # CONFIG2 [C0] [1, 1, 0, INT_CAL, 0, CAL_AMP0, CAL_FREQ[1:0]]
|
65 |
-
|
66 |
0x00, # No lead-off
|
67 |
-
|
68 |
0x60, # CH2SET
|
69 |
0x60, # CH3SET
|
70 |
0x60, # CH4SET
|
@@ -95,7 +94,6 @@ def to_ads_frequency(frequency):
|
|
95 |
break
|
96 |
return dr
|
97 |
|
98 |
-
|
99 |
def mod_config(config, datarate, channel_modes):
|
100 |
|
101 |
# datarate:
|
@@ -119,30 +117,22 @@ def mod_config(config, datarate, channel_modes):
|
|
119 |
|
120 |
# bias:
|
121 |
|
122 |
-
assert len(channel_modes) ==
|
123 |
config[13] = 0x00 # clear BIAS_SENSP
|
124 |
config[14] = 0x00 # clear BIAS_SENSN
|
125 |
-
bias_active = False
|
126 |
for chan_i, chan_mode in enumerate(channel_modes):
|
127 |
n = 5 + chan_i
|
128 |
mod = config[n] & 0x78 # clear PDn and MUX[2:0]
|
129 |
if chan_mode == 'simple':
|
130 |
-
|
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
|
136 |
config[13] = config[13] | bit_i
|
137 |
config[14] = config[14] | bit_i
|
138 |
-
elif chan_mode == '
|
139 |
-
|
140 |
-
mod = mod | 0x06 # MUX[2:0] = BIAS_DRP (110)
|
141 |
else:
|
142 |
assert False, f"Wrong key: {chan_mode}."
|
143 |
config[n] = mod
|
144 |
-
if bias_active:
|
145 |
-
config[3] = config[3] | 0x1c # activate the bias mechanism
|
146 |
for n, c in enumerate(config): # print ADS1299 configuration registers
|
147 |
print(f"config[{n}]:\t{c:08b}\t({hex(c)})")
|
148 |
return config
|
@@ -478,8 +468,7 @@ class UpStateDelayer:
|
|
478 |
last_peak = count
|
479 |
return self.spindle_timesteps - last_peak
|
480 |
count += 1
|
481 |
-
return -1
|
482 |
-
|
483 |
|
484 |
class Capture:
|
485 |
def __init__(self, detector_cls=None, stimulator_cls=None):
|
@@ -511,11 +500,11 @@ class Capture:
|
|
511 |
self.samples_per_datarecord_array = self.frequency
|
512 |
self.physical_max = 5
|
513 |
self.physical_min = -5
|
514 |
-
self.signal_labels = ['
|
515 |
self._lock_msg_out = Lock()
|
516 |
self._msg_out = None
|
517 |
self._t_capture = None
|
518 |
-
self.channel_states = ['disabled', 'disabled', 'disabled', 'disabled', 'disabled', 'disabled', 'disabled'
|
519 |
self.channel_detection = 2
|
520 |
self.spindle_detection_mode = 'Fast'
|
521 |
self.spindle_freq = 10
|
@@ -550,50 +539,50 @@ class Capture:
|
|
550 |
|
551 |
# CHANNELS ------------------------------
|
552 |
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
|
559 |
self.b_radio_ch2 = widgets.RadioButtons(
|
560 |
-
options=['disabled', 'simple'
|
561 |
value='disabled',
|
562 |
disabled=False
|
563 |
)
|
564 |
|
565 |
self.b_radio_ch3 = widgets.RadioButtons(
|
566 |
-
options=['disabled', 'simple'
|
567 |
value='disabled',
|
568 |
disabled=False
|
569 |
)
|
570 |
|
571 |
self.b_radio_ch4 = widgets.RadioButtons(
|
572 |
-
options=['disabled', 'simple'
|
573 |
value='disabled',
|
574 |
disabled=False
|
575 |
)
|
576 |
|
577 |
self.b_radio_ch5 = widgets.RadioButtons(
|
578 |
-
options=['disabled', 'simple'
|
579 |
value='disabled',
|
580 |
disabled=False
|
581 |
)
|
582 |
|
583 |
self.b_radio_ch6 = widgets.RadioButtons(
|
584 |
-
options=['disabled', 'simple'
|
585 |
value='disabled',
|
586 |
disabled=False
|
587 |
)
|
588 |
|
589 |
self.b_radio_ch7 = widgets.RadioButtons(
|
590 |
-
options=['disabled', 'simple'
|
591 |
value='disabled',
|
592 |
disabled=False
|
593 |
)
|
594 |
|
595 |
self.b_radio_ch8 = widgets.RadioButtons(
|
596 |
-
options=['disabled', 'simple'
|
597 |
value='disabled',
|
598 |
disabled=False
|
599 |
)
|
@@ -624,7 +613,6 @@ class Capture:
|
|
624 |
self.b_accordion_channels = widgets.Accordion(
|
625 |
children=[
|
626 |
widgets.GridBox([
|
627 |
-
widgets.Label('CH1'),
|
628 |
widgets.Label('CH2'),
|
629 |
widgets.Label('CH3'),
|
630 |
widgets.Label('CH4'),
|
@@ -632,14 +620,13 @@ class Capture:
|
|
632 |
widgets.Label('CH6'),
|
633 |
widgets.Label('CH7'),
|
634 |
widgets.Label('CH8'),
|
635 |
-
self.b_radio_ch1,
|
636 |
self.b_radio_ch2,
|
637 |
self.b_radio_ch3,
|
638 |
self.b_radio_ch4,
|
639 |
self.b_radio_ch5,
|
640 |
self.b_radio_ch6,
|
641 |
self.b_radio_ch7,
|
642 |
-
self.b_radio_ch8], layout=widgets.Layout(grid_template_columns="repeat(
|
643 |
)
|
644 |
])
|
645 |
self.b_accordion_channels.set_title(index = 0, title = 'Channels')
|
@@ -840,6 +827,13 @@ class Capture:
|
|
840 |
tooltip='Send a test stimulus'
|
841 |
)
|
842 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
843 |
# CALLBACKS ----------------------
|
844 |
|
845 |
self.b_capture.observe(self.on_b_capture, 'value')
|
@@ -876,6 +870,7 @@ class Capture:
|
|
876 |
self.b_epsilon.observe(self.on_b_epsilon, 'value')
|
877 |
self.b_volume.observe(self.on_b_volume, 'value')
|
878 |
self.b_test_stimulus.on_click(self.on_b_test_stimulus)
|
|
|
879 |
self.b_pause.observe(self.on_b_pause, 'value')
|
880 |
|
881 |
self.display_buttons()
|
@@ -895,6 +890,7 @@ class Capture:
|
|
895 |
widgets.HBox([self.b_threshold, self.b_test_stimulus]),
|
896 |
self.b_volume,
|
897 |
widgets.HBox([self.b_spindle_mode, self.b_spindle_freq]),
|
|
|
898 |
self.b_accordion_filter,
|
899 |
self.b_capture,
|
900 |
self.b_pause]))
|
@@ -933,6 +929,7 @@ class Capture:
|
|
933 |
self.b_threshold.disabled = not self.detect
|
934 |
self.b_pause.disabled = not self.detect
|
935 |
self.b_test_stimulus.disabled = True # only enabled when running
|
|
|
936 |
|
937 |
def disable_buttons(self):
|
938 |
self.b_frequency.disabled = True
|
@@ -968,27 +965,28 @@ class Capture:
|
|
968 |
self.b_custom_fir_cutoff.disabled = True
|
969 |
self.b_threshold.disabled = True
|
970 |
self.b_test_stimulus.disabled = not self.stimulate # only enabled when running
|
|
|
971 |
|
972 |
def on_b_radio_ch2(self, value):
|
973 |
-
self.channel_states[
|
974 |
|
975 |
def on_b_radio_ch3(self, value):
|
976 |
-
self.channel_states[
|
977 |
|
978 |
def on_b_radio_ch4(self, value):
|
979 |
-
self.channel_states[
|
980 |
|
981 |
def on_b_radio_ch5(self, value):
|
982 |
-
self.channel_states[
|
983 |
|
984 |
def on_b_radio_ch6(self, value):
|
985 |
-
self.channel_states[
|
986 |
|
987 |
def on_b_radio_ch7(self, value):
|
988 |
-
self.channel_states[
|
989 |
|
990 |
def on_b_radio_ch8(self, value):
|
991 |
-
self.channel_states[
|
992 |
|
993 |
def on_b_channel_detect(self, value):
|
994 |
self.channel_detection = value['new']
|
@@ -1175,6 +1173,37 @@ class Capture:
|
|
1175 |
def on_b_test_stimulus(self, b):
|
1176 |
with self._test_stimulus_lock:
|
1177 |
self._test_stimulus = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1178 |
|
1179 |
def on_b_pause(self, value):
|
1180 |
val = value['new']
|
@@ -1319,8 +1348,15 @@ class Capture:
|
|
1319 |
point = p_data_i.recv()
|
1320 |
else:
|
1321 |
continue
|
1322 |
-
|
1323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1324 |
n_array = filter_np(n_array)
|
1325 |
|
1326 |
if filter:
|
|
|
57 |
0x00, # CONFIG4 [00] [0, 0, 0, 0, SINGLE_SHOT, 0, PD_LOFF_COMP(bar), 0] : Single-shot, lead-off comparator disabled
|
58 |
]
|
59 |
|
|
|
60 |
FRONTEND_CONFIG = [
|
61 |
0x3E, # ID (RO)
|
62 |
0x95, # CONFIG1 [95] [1, DAISY_EN(bar), CLK_EN, 1, 0, DR[2:0]] : Datarate = 500 SPS
|
63 |
0xC0, # CONFIG2 [C0] [1, 1, 0, INT_CAL, 0, CAL_AMP0, CAL_FREQ[1:0]]
|
64 |
+
0xFC, # 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
|
65 |
0x00, # No lead-off
|
66 |
+
0x62, # CH1SET [60] [PD1, GAIN1[2:0], SRB2, MUX1[2:0]] set to measure BIAS signal
|
67 |
0x60, # CH2SET
|
68 |
0x60, # CH3SET
|
69 |
0x60, # CH4SET
|
|
|
94 |
break
|
95 |
return dr
|
96 |
|
|
|
97 |
def mod_config(config, datarate, channel_modes):
|
98 |
|
99 |
# datarate:
|
|
|
117 |
|
118 |
# bias:
|
119 |
|
120 |
+
assert len(channel_modes) == 7
|
121 |
config[13] = 0x00 # clear BIAS_SENSP
|
122 |
config[14] = 0x00 # clear BIAS_SENSN
|
|
|
123 |
for chan_i, chan_mode in enumerate(channel_modes):
|
124 |
n = 5 + chan_i
|
125 |
mod = config[n] & 0x78 # clear PDn and MUX[2:0]
|
126 |
if chan_mode == 'simple':
|
127 |
+
# If channel is activated, we send the channel's output to the BIAS mechanism
|
|
|
|
|
|
|
|
|
128 |
bit_i = 1 << chan_i
|
129 |
config[13] = config[13] | bit_i
|
130 |
config[14] = config[14] | bit_i
|
131 |
+
elif chan_mode == 'disabled':
|
132 |
+
mod = mod | 0x81 # PDn = 1 and input shorted (001)
|
|
|
133 |
else:
|
134 |
assert False, f"Wrong key: {chan_mode}."
|
135 |
config[n] = mod
|
|
|
|
|
136 |
for n, c in enumerate(config): # print ADS1299 configuration registers
|
137 |
print(f"config[{n}]:\t{c:08b}\t({hex(c)})")
|
138 |
return config
|
|
|
468 |
last_peak = count
|
469 |
return self.spindle_timesteps - last_peak
|
470 |
count += 1
|
471 |
+
return -1
|
|
|
472 |
|
473 |
class Capture:
|
474 |
def __init__(self, detector_cls=None, stimulator_cls=None):
|
|
|
500 |
self.samples_per_datarecord_array = self.frequency
|
501 |
self.physical_max = 5
|
502 |
self.physical_min = -5
|
503 |
+
self.signal_labels = ['Common Mode', 'ch2', 'ch3', 'ch4', 'ch5', 'ch6', 'ch7', 'ch8']
|
504 |
self._lock_msg_out = Lock()
|
505 |
self._msg_out = None
|
506 |
self._t_capture = None
|
507 |
+
self.channel_states = ['disabled', 'disabled', 'disabled', 'disabled', 'disabled', 'disabled', 'disabled']
|
508 |
self.channel_detection = 2
|
509 |
self.spindle_detection_mode = 'Fast'
|
510 |
self.spindle_freq = 10
|
|
|
539 |
|
540 |
# CHANNELS ------------------------------
|
541 |
|
542 |
+
# self.b_radio_ch1 = widgets.RadioButtons(
|
543 |
+
# options=['disabled', 'simple'],
|
544 |
+
# value='disabled',
|
545 |
+
# disabled=True
|
546 |
+
# )
|
547 |
|
548 |
self.b_radio_ch2 = widgets.RadioButtons(
|
549 |
+
options=['disabled', 'simple'],
|
550 |
value='disabled',
|
551 |
disabled=False
|
552 |
)
|
553 |
|
554 |
self.b_radio_ch3 = widgets.RadioButtons(
|
555 |
+
options=['disabled', 'simple'],
|
556 |
value='disabled',
|
557 |
disabled=False
|
558 |
)
|
559 |
|
560 |
self.b_radio_ch4 = widgets.RadioButtons(
|
561 |
+
options=['disabled', 'simple'],
|
562 |
value='disabled',
|
563 |
disabled=False
|
564 |
)
|
565 |
|
566 |
self.b_radio_ch5 = widgets.RadioButtons(
|
567 |
+
options=['disabled', 'simple'],
|
568 |
value='disabled',
|
569 |
disabled=False
|
570 |
)
|
571 |
|
572 |
self.b_radio_ch6 = widgets.RadioButtons(
|
573 |
+
options=['disabled', 'simple'],
|
574 |
value='disabled',
|
575 |
disabled=False
|
576 |
)
|
577 |
|
578 |
self.b_radio_ch7 = widgets.RadioButtons(
|
579 |
+
options=['disabled', 'simple'],
|
580 |
value='disabled',
|
581 |
disabled=False
|
582 |
)
|
583 |
|
584 |
self.b_radio_ch8 = widgets.RadioButtons(
|
585 |
+
options=['disabled', 'simple'],
|
586 |
value='disabled',
|
587 |
disabled=False
|
588 |
)
|
|
|
613 |
self.b_accordion_channels = widgets.Accordion(
|
614 |
children=[
|
615 |
widgets.GridBox([
|
|
|
616 |
widgets.Label('CH2'),
|
617 |
widgets.Label('CH3'),
|
618 |
widgets.Label('CH4'),
|
|
|
620 |
widgets.Label('CH6'),
|
621 |
widgets.Label('CH7'),
|
622 |
widgets.Label('CH8'),
|
|
|
623 |
self.b_radio_ch2,
|
624 |
self.b_radio_ch3,
|
625 |
self.b_radio_ch4,
|
626 |
self.b_radio_ch5,
|
627 |
self.b_radio_ch6,
|
628 |
self.b_radio_ch7,
|
629 |
+
self.b_radio_ch8], layout=widgets.Layout(grid_template_columns="repeat(7, 90px)")
|
630 |
)
|
631 |
])
|
632 |
self.b_accordion_channels.set_title(index = 0, title = 'Channels')
|
|
|
827 |
tooltip='Send a test stimulus'
|
828 |
)
|
829 |
|
830 |
+
self.b_test_impedance = widgets.Button(
|
831 |
+
description='Impedance Check',
|
832 |
+
disabled=False,
|
833 |
+
button_style='', # 'success', 'info', 'warning', 'danger' or ''
|
834 |
+
tooltip='Check if electrodes are properly connected'
|
835 |
+
)
|
836 |
+
|
837 |
# CALLBACKS ----------------------
|
838 |
|
839 |
self.b_capture.observe(self.on_b_capture, 'value')
|
|
|
870 |
self.b_epsilon.observe(self.on_b_epsilon, 'value')
|
871 |
self.b_volume.observe(self.on_b_volume, 'value')
|
872 |
self.b_test_stimulus.on_click(self.on_b_test_stimulus)
|
873 |
+
self.b_test_impedance.on_click(self.on_b_test_impedance)
|
874 |
self.b_pause.observe(self.on_b_pause, 'value')
|
875 |
|
876 |
self.display_buttons()
|
|
|
890 |
widgets.HBox([self.b_threshold, self.b_test_stimulus]),
|
891 |
self.b_volume,
|
892 |
widgets.HBox([self.b_spindle_mode, self.b_spindle_freq]),
|
893 |
+
self.b_test_impedance,
|
894 |
self.b_accordion_filter,
|
895 |
self.b_capture,
|
896 |
self.b_pause]))
|
|
|
929 |
self.b_threshold.disabled = not self.detect
|
930 |
self.b_pause.disabled = not self.detect
|
931 |
self.b_test_stimulus.disabled = True # only enabled when running
|
932 |
+
self.b_test_impedance.disabled = False
|
933 |
|
934 |
def disable_buttons(self):
|
935 |
self.b_frequency.disabled = True
|
|
|
965 |
self.b_custom_fir_cutoff.disabled = True
|
966 |
self.b_threshold.disabled = True
|
967 |
self.b_test_stimulus.disabled = not self.stimulate # only enabled when running
|
968 |
+
self.b_test_impedance.disabled = True
|
969 |
|
970 |
def on_b_radio_ch2(self, value):
|
971 |
+
self.channel_states[0] = value['new']
|
972 |
|
973 |
def on_b_radio_ch3(self, value):
|
974 |
+
self.channel_states[1] = value['new']
|
975 |
|
976 |
def on_b_radio_ch4(self, value):
|
977 |
+
self.channel_states[2] = value['new']
|
978 |
|
979 |
def on_b_radio_ch5(self, value):
|
980 |
+
self.channel_states[3] = value['new']
|
981 |
|
982 |
def on_b_radio_ch6(self, value):
|
983 |
+
self.channel_states[4] = value['new']
|
984 |
|
985 |
def on_b_radio_ch7(self, value):
|
986 |
+
self.channel_states[5] = value['new']
|
987 |
|
988 |
def on_b_radio_ch8(self, value):
|
989 |
+
self.channel_states[6] = value['new']
|
990 |
|
991 |
def on_b_channel_detect(self, value):
|
992 |
self.channel_detection = value['new']
|
|
|
1173 |
def on_b_test_stimulus(self, b):
|
1174 |
with self._test_stimulus_lock:
|
1175 |
self._test_stimulus = True
|
1176 |
+
|
1177 |
+
def on_b_test_impedance(self, b):
|
1178 |
+
frontend = Frontend()
|
1179 |
+
|
1180 |
+
def is_set(x, n):
|
1181 |
+
return x & 1 << n != 0
|
1182 |
+
|
1183 |
+
try:
|
1184 |
+
frontend.write_regs(0x00, FRONTEND_CONFIG)
|
1185 |
+
frontend.start()
|
1186 |
+
new_config = frontend.read_regs(0x00, len(FRONTEND_CONFIG))
|
1187 |
+
leadoff_p = new_config[18]
|
1188 |
+
leadoff_n = new_config[19]
|
1189 |
+
|
1190 |
+
# Check if any of the negative bits are set and initialize the impedance array
|
1191 |
+
impedance_check = [any([is_set(leadoff_n, i) for i in range(2, 9)])]
|
1192 |
+
|
1193 |
+
# Check all other values for all electrodes
|
1194 |
+
for i in range(2, 9):
|
1195 |
+
impedance_check.append(is_set(leadoff_p, i))
|
1196 |
+
|
1197 |
+
def print_impedance(impedance):
|
1198 |
+
names = ["Ref", "Ch2", "Ch3", "Ch4", "Ch5", "Ch6", "Ch7", "Ch8"]
|
1199 |
+
vals = [' Y ' if val else ' N ' for val in impedance]
|
1200 |
+
print(' '.join(str(name) for name in names))
|
1201 |
+
print(' '.join(str(val) for val in vals))
|
1202 |
+
|
1203 |
+
print_impedance(impedance_check)
|
1204 |
+
|
1205 |
+
finally:
|
1206 |
+
frontend.close()
|
1207 |
|
1208 |
def on_b_pause(self, value):
|
1209 |
val = value['new']
|
|
|
1348 |
point = p_data_i.recv()
|
1349 |
else:
|
1350 |
continue
|
1351 |
+
|
1352 |
+
new_point = point
|
1353 |
+
avg = 0
|
1354 |
+
for idx, p in enumerate(point):
|
1355 |
+
if idx > 0 and idx < 5:
|
1356 |
+
avg += p
|
1357 |
+
new_point[0] = avg // 4
|
1358 |
+
new_point[5] = new_point[1] - new_point[0]
|
1359 |
+
n_array = np.array([new_point])
|
1360 |
n_array = filter_np(n_array)
|
1361 |
|
1362 |
if filter:
|