Yann Bouteiller commited on
Commit
1a609ad
·
1 Parent(s): 6da664d

Changed to new BIAS mechanism and added Leadoff detection

Browse files
Files changed (1) hide show
  1. 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
- 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
@@ -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) == 8
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
- 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
136
  config[13] = config[13] | bit_i
137
  config[14] = config[14] | bit_i
138
- elif chan_mode == 'bias out':
139
- bias_active = True
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 = ['ch1', 'ch2', 'ch3', 'ch4', 'ch5', 'ch6', 'ch7', 'ch8']
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', '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
- self.b_radio_ch1 = widgets.RadioButtons(
554
- options=['disabled', 'simple', 'with bias', 'bias out'],
555
- value='disabled',
556
- disabled=True
557
- )
558
 
559
  self.b_radio_ch2 = widgets.RadioButtons(
560
- options=['disabled', 'simple', 'with bias', 'bias out'],
561
  value='disabled',
562
  disabled=False
563
  )
564
 
565
  self.b_radio_ch3 = widgets.RadioButtons(
566
- options=['disabled', 'simple', 'with bias', 'bias out'],
567
  value='disabled',
568
  disabled=False
569
  )
570
 
571
  self.b_radio_ch4 = widgets.RadioButtons(
572
- options=['disabled', 'simple', 'with bias', 'bias out'],
573
  value='disabled',
574
  disabled=False
575
  )
576
 
577
  self.b_radio_ch5 = widgets.RadioButtons(
578
- options=['disabled', 'simple', 'with bias', 'bias out'],
579
  value='disabled',
580
  disabled=False
581
  )
582
 
583
  self.b_radio_ch6 = widgets.RadioButtons(
584
- options=['disabled', 'simple', 'with bias', 'bias out'],
585
  value='disabled',
586
  disabled=False
587
  )
588
 
589
  self.b_radio_ch7 = widgets.RadioButtons(
590
- options=['disabled', 'simple', 'with bias', 'bias out'],
591
  value='disabled',
592
  disabled=False
593
  )
594
 
595
  self.b_radio_ch8 = widgets.RadioButtons(
596
- options=['disabled', 'simple', 'with bias', 'bias out'],
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(8, 90px)")
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[1] = value['new']
974
 
975
  def on_b_radio_ch3(self, value):
976
- self.channel_states[2] = value['new']
977
 
978
  def on_b_radio_ch4(self, value):
979
- self.channel_states[3] = value['new']
980
 
981
  def on_b_radio_ch5(self, value):
982
- self.channel_states[4] = value['new']
983
 
984
  def on_b_radio_ch6(self, value):
985
- self.channel_states[5] = value['new']
986
 
987
  def on_b_radio_ch7(self, value):
988
- self.channel_states[6] = value['new']
989
 
990
  def on_b_radio_ch8(self, value):
991
- self.channel_states[7] = value['new']
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
- n_array = np.array([point])
 
 
 
 
 
 
 
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: