Yann Bouteiller commited on
Commit
0894c08
·
1 Parent(s): 5c3b17f

new options

Browse files
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 66
69
  0x60, # CH3SET
70
  0x60, # CH4SET
71
  0x60, # CH5SET
72
  0x60, # CH6SET
73
  0x60, # CH7SET
74
  0x60, # CH8SET
75
- 0x04, # BIAS_SENSP 04
76
- 0x04, # BIAS_SENSN 04
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"DEBUG: new config[{n}]:\t{c:08b}\t({hex(c)})")
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
- x = self.fir.filter(x)
 
257
  # notch:
258
- denAccum = (x - self.notch_coeff1 * self.dfs[0]) - self.notch_coeff2 * self.dfs[1]
259
- x = (self.notch_coeff3 * denAccum + self.notch_coeff4 * self.dfs[0]) + self.notch_coeff5 * self.dfs[1]
260
- self.dfs[1] = self.dfs[0]
261
- self.dfs[0] = denAccum
 
262
  # standardization:
263
- if self.moving_average is not None:
264
- delta = x - self.moving_average
265
- self.moving_average = self.moving_average + self.ALPHA_AVG * delta
266
- self.moving_variance = (1 - self.ALPHA_STD) * (self.moving_variance + self.ALPHA_STD * delta**2)
267
- moving_std = np.sqrt(self.moving_variance)
268
- x = (x - self.moving_average) / (moving_std + self.EPSILON)
269
- else:
270
- self.moving_average = x
 
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
- self.pp.update_with_datapoints(disp_list)
 
 
 
 
 
 
 
 
 
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 cannot be established."
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": 1,
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",