ybouteiller commited on
Commit
9adbe4f
·
1 Parent(s): 2e1823f

online filtering

Browse files
portiloop/capture.py CHANGED
@@ -9,7 +9,6 @@ import multiprocessing as mp
9
  import warnings
10
  import shutil
11
  from threading import Thread, Lock
12
- from scipy import signal
13
 
14
  import matplotlib.pyplot as plt
15
  from EDFlib.edfwriter import EDFwriter
@@ -117,7 +116,7 @@ def mod_config(config, datarate, channel_modes):
117
  pass # PDn = 0 and normal electrode (000)
118
  elif chan_mode == 'disabled':
119
  mod = mod | 0x81 # PDn = 1 and input shorted (001)
120
- elif chan_mode == 'bias in':
121
  bit_i = 1 << chan_i
122
  config[13] = config[13] | bit_i
123
  config[14] = config[14] | bit_i
@@ -149,6 +148,33 @@ def filter_np(value):
149
  return filter_24(filter_2scomplement_np(value))
150
 
151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  class FilterPipeline:
153
  def __init__(self, power_line_fq=60):
154
  assert power_line_fq in [50, 60], f"The only supported power line frequencies are 50Hz and 60Hz"
@@ -194,13 +220,14 @@ class FilterPipeline:
194
  0.021287595318265635502275046064823982306,
195
  0.014988684599373741992978104065059596905,
196
  0.001623780150148094927192721215192250384]
197
- self.z = signal.lfilter_zi(self.fir_30_coef, 1)
198
 
199
  def filter(self, value):
 
200
  result = np.zeros(value.size)
201
  for i, x in enumerate(value):
202
  # FIR:
203
- x, self.z = signal.lfilter(self.fir_30_coef, 1, [x], zi=self.z)
204
  # notch:
205
  denAccum = (x - self.notch_coeff1 * self.dfs[0]) - self.notch_coeff2 * self.dfs[1]
206
  x = (self.notch_coeff3 * denAccum + self.notch_coeff4 * self.dfs[0]) + self.notch_coeff5 * self.dfs[1]
@@ -348,6 +375,7 @@ class Capture:
348
  self.__capture_on = False
349
  self.frequency = 250
350
  self.duration = 10
 
351
  self.record = False
352
  self.display = False
353
  self.python_clock = True
@@ -361,56 +389,56 @@ class Capture:
361
  self._lock_msg_out = Lock()
362
  self._msg_out = None
363
  self._t_capture = None
364
- self.channel_states = ['disabled', 'simple', 'disabled', 'disabled', 'disabled', 'disabled', 'disabled', 'disabled']
365
 
366
  # widgets ===============================
367
 
368
  # CHANNELS ------------------------------
369
 
370
  self.b_radio_ch1 = widgets.RadioButtons(
371
- options=['disabled', 'simple', 'bias in', 'bias out'],
372
  value='disabled',
373
  disabled=True
374
  )
375
 
376
  self.b_radio_ch2 = widgets.RadioButtons(
377
- options=['disabled', 'simple', 'bias in', 'bias out'],
378
- value='simple',
379
  disabled=False
380
  )
381
 
382
  self.b_radio_ch3 = widgets.RadioButtons(
383
- options=['disabled', 'simple', 'bias in', 'bias out'],
384
  value='disabled',
385
  disabled=False
386
  )
387
 
388
  self.b_radio_ch4 = widgets.RadioButtons(
389
- options=['disabled', 'simple', 'bias in', 'bias out'],
390
  value='disabled',
391
  disabled=False
392
  )
393
 
394
  self.b_radio_ch5 = widgets.RadioButtons(
395
- options=['disabled', 'simple', 'bias in', 'bias out'],
396
  value='disabled',
397
  disabled=False
398
  )
399
 
400
  self.b_radio_ch6 = widgets.RadioButtons(
401
- options=['disabled', 'simple', 'bias in', 'bias out'],
402
  value='disabled',
403
  disabled=False
404
  )
405
 
406
  self.b_radio_ch7 = widgets.RadioButtons(
407
- options=['disabled', 'simple', 'bias in', 'bias out'],
408
  value='disabled',
409
  disabled=False
410
  )
411
 
412
  self.b_radio_ch8 = widgets.RadioButtons(
413
- options=['disabled', 'simple', 'bias in', 'bias out'],
414
  value='disabled',
415
  disabled=True
416
  )
@@ -477,6 +505,13 @@ class Capture:
477
  disabled=False
478
  )
479
 
 
 
 
 
 
 
 
480
  self.b_record = widgets.Checkbox(
481
  value=False,
482
  description='Record',
@@ -497,6 +532,7 @@ class Capture:
497
  self.b_clock.observe(self.on_b_clock, 'value')
498
  self.b_frequency.observe(self.on_b_frequency, 'value')
499
  self.b_duration.observe(self.on_b_duration, 'value')
 
500
  self.b_record.observe(self.on_b_record, 'value')
501
  self.b_display.observe(self.on_b_display, 'value')
502
  self.b_filename.observe(self.on_b_filename, 'value')
@@ -517,7 +553,7 @@ class Capture:
517
  self.b_frequency,
518
  self.b_duration,
519
  self.b_filename,
520
- widgets.HBox([self.b_record, self.b_display]),
521
  self.b_clock,
522
  self.b_capture]))
523
 
@@ -525,6 +561,7 @@ class Capture:
525
  self.b_frequency.disabled = False
526
  self.b_duration.disabled = False
527
  self.b_filename.disabled = False
 
528
  self.b_record.disabled = False
529
  self.b_display.disabled = False
530
  self.b_clock.disabled = False
@@ -539,6 +576,7 @@ class Capture:
539
  self.b_frequency.disabled = True
540
  self.b_duration.disabled = True
541
  self.b_filename.disabled = True
 
542
  self.b_record.disabled = True
543
  self.b_display.disabled = True
544
  self.b_clock.disabled = True
@@ -579,7 +617,7 @@ class Capture:
579
  warnings.warn("Capture already running, operation aborted.")
580
  return
581
  self._t_capture = Thread(target=self.start_capture,
582
- args=(self.record, self.display, 500, self.python_clock))
583
  self._t_capture.start()
584
  elif val == 'Stop':
585
  with self._lock_msg_out:
@@ -616,6 +654,10 @@ class Capture:
616
  if val > 0:
617
  self.duration = val
618
 
 
 
 
 
619
  def on_b_record(self, value):
620
  val = value['new']
621
  self.record = val
@@ -661,6 +703,7 @@ class Capture:
661
  assert self.edf_writer.writeSamples(d) == 0
662
 
663
  def start_capture(self,
 
664
  record,
665
  viz,
666
  width,
@@ -684,6 +727,7 @@ class Capture:
684
  self.channel_states)
685
  )
686
  self._p_capture.start()
 
687
 
688
  if viz:
689
  live_disp = LiveDisplay(channel_names = self.signal_labels, window_len=width)
@@ -691,6 +735,8 @@ class Capture:
691
  if record:
692
  self.open_recording_file()
693
 
 
 
694
  while True:
695
  with self._lock_msg_out:
696
  if self._msg_out is not None:
@@ -704,31 +750,30 @@ class Capture:
704
  print(mess[1])
705
 
706
  # retrieve all data points from p_data and put them in a list of np.array:
707
- res = []
708
- c = True
709
- while c and len(res) < 25:
710
- if p_data_i.poll(timeout=SAMPLE_TIME):
711
- point = p_data_i.recv()
712
- res.append(point)
713
- else:
714
- c = False
715
- if len(res) == 0:
716
  continue
717
-
718
- n_array = np.array(res)
719
  n_array = filter_np(n_array)
720
 
721
- if True:
722
  n_array = np.swapaxes(n_array, 0, 1)
723
- n_array = np.array([fp_vec[i].filter(a) for i, a in enumerate(n_array)])
724
  n_array = np.swapaxes(n_array, 0, 1)
725
-
726
- to_add = n_array.tolist()
727
 
728
- if viz:
729
- live_disp.add_datapoints(to_add)
730
- if record:
731
- self.add_recording_data(to_add)
 
 
 
 
 
 
732
 
733
  # empty pipes
734
  while True:
 
9
  import warnings
10
  import shutil
11
  from threading import Thread, Lock
 
12
 
13
  import matplotlib.pyplot as plt
14
  from EDFlib.edfwriter import EDFwriter
 
116
  pass # PDn = 0 and normal electrode (000)
117
  elif chan_mode == 'disabled':
118
  mod = mod | 0x81 # PDn = 1 and input shorted (001)
119
+ elif chan_mode == 'with bias':
120
  bit_i = 1 << chan_i
121
  config[13] = config[13] | bit_i
122
  config[14] = config[14] | bit_i
 
148
  return filter_24(filter_2scomplement_np(value))
149
 
150
 
151
+ def shift_numpy(arr, num, fill_value=np.nan):
152
+ result = np.empty_like(arr)
153
+ if num > 0:
154
+ result[:num] = fill_value
155
+ result[num:] = arr[:-num]
156
+ elif num < 0:
157
+ result[num:] = fill_value
158
+ result[:num] = arr[-num:]
159
+ else:
160
+ result[:] = arr
161
+ return result
162
+
163
+
164
+ class FIR:
165
+ def __init__(self, coefficients, buffer=None):
166
+ self.coefficients = coefficients
167
+ self.taps = len(self.coefficients)
168
+ if buffer is not None:
169
+ self.buffer = np.array(z)
170
+ else:
171
+ self.buffer = np.zeros(self.taps)
172
+
173
+ def filter(self, x):
174
+ self.buffer = shift_numpy(self.buffer, 1, x)
175
+ return np.sum(self.buffer * self.coefficients)
176
+
177
+
178
  class FilterPipeline:
179
  def __init__(self, power_line_fq=60):
180
  assert power_line_fq in [50, 60], f"The only supported power line frequencies are 50Hz and 60Hz"
 
220
  0.021287595318265635502275046064823982306,
221
  0.014988684599373741992978104065059596905,
222
  0.001623780150148094927192721215192250384]
223
+ self.fir = FIR(self.fir_30_coef)
224
 
225
  def filter(self, value):
226
+
227
  result = np.zeros(value.size)
228
  for i, x in enumerate(value):
229
  # FIR:
230
+ x = self.fir.filter(x)
231
  # notch:
232
  denAccum = (x - self.notch_coeff1 * self.dfs[0]) - self.notch_coeff2 * self.dfs[1]
233
  x = (self.notch_coeff3 * denAccum + self.notch_coeff4 * self.dfs[0]) + self.notch_coeff5 * self.dfs[1]
 
375
  self.__capture_on = False
376
  self.frequency = 250
377
  self.duration = 10
378
+ self.filter = True
379
  self.record = False
380
  self.display = False
381
  self.python_clock = True
 
389
  self._lock_msg_out = Lock()
390
  self._msg_out = None
391
  self._t_capture = None
392
+ self.channel_states = ['disabled', 'disabled', 'disabled', 'disabled', 'disabled', 'disabled', 'disabled', 'disabled']
393
 
394
  # widgets ===============================
395
 
396
  # CHANNELS ------------------------------
397
 
398
  self.b_radio_ch1 = widgets.RadioButtons(
399
+ options=['disabled', 'simple', 'with bias', 'bias out'],
400
  value='disabled',
401
  disabled=True
402
  )
403
 
404
  self.b_radio_ch2 = widgets.RadioButtons(
405
+ options=['disabled', 'simple', 'with bias', 'bias out'],
406
+ value='disabled',
407
  disabled=False
408
  )
409
 
410
  self.b_radio_ch3 = widgets.RadioButtons(
411
+ options=['disabled', 'simple', 'with bias', 'bias out'],
412
  value='disabled',
413
  disabled=False
414
  )
415
 
416
  self.b_radio_ch4 = widgets.RadioButtons(
417
+ options=['disabled', 'simple', 'with bias', 'bias out'],
418
  value='disabled',
419
  disabled=False
420
  )
421
 
422
  self.b_radio_ch5 = widgets.RadioButtons(
423
+ options=['disabled', 'simple', 'with bias', 'bias out'],
424
  value='disabled',
425
  disabled=False
426
  )
427
 
428
  self.b_radio_ch6 = widgets.RadioButtons(
429
+ options=['disabled', 'simple', 'with bias', 'bias out'],
430
  value='disabled',
431
  disabled=False
432
  )
433
 
434
  self.b_radio_ch7 = widgets.RadioButtons(
435
+ options=['disabled', 'simple', 'with bias', 'bias out'],
436
  value='disabled',
437
  disabled=False
438
  )
439
 
440
  self.b_radio_ch8 = widgets.RadioButtons(
441
+ options=['disabled', 'simple', 'with bias', 'bias out'],
442
  value='disabled',
443
  disabled=True
444
  )
 
505
  disabled=False
506
  )
507
 
508
+ self.b_filter = widgets.Checkbox(
509
+ value=True,
510
+ description='Filter',
511
+ disabled=False,
512
+ indent=False
513
+ )
514
+
515
  self.b_record = widgets.Checkbox(
516
  value=False,
517
  description='Record',
 
532
  self.b_clock.observe(self.on_b_clock, 'value')
533
  self.b_frequency.observe(self.on_b_frequency, 'value')
534
  self.b_duration.observe(self.on_b_duration, 'value')
535
+ self.b_filter.observe(self.on_b_filter, 'value')
536
  self.b_record.observe(self.on_b_record, 'value')
537
  self.b_display.observe(self.on_b_display, 'value')
538
  self.b_filename.observe(self.on_b_filename, 'value')
 
553
  self.b_frequency,
554
  self.b_duration,
555
  self.b_filename,
556
+ widgets.HBox([self.b_filter, self.b_record, self.b_display]),
557
  self.b_clock,
558
  self.b_capture]))
559
 
 
561
  self.b_frequency.disabled = False
562
  self.b_duration.disabled = False
563
  self.b_filename.disabled = False
564
+ self.b_filter.disabled = False
565
  self.b_record.disabled = False
566
  self.b_display.disabled = False
567
  self.b_clock.disabled = False
 
576
  self.b_frequency.disabled = True
577
  self.b_duration.disabled = True
578
  self.b_filename.disabled = True
579
+ self.b_filter.disabled = True
580
  self.b_record.disabled = True
581
  self.b_display.disabled = True
582
  self.b_clock.disabled = True
 
617
  warnings.warn("Capture already running, operation aborted.")
618
  return
619
  self._t_capture = Thread(target=self.start_capture,
620
+ args=(self.filter, self.record, self.display, 500, self.python_clock))
621
  self._t_capture.start()
622
  elif val == 'Stop':
623
  with self._lock_msg_out:
 
654
  if val > 0:
655
  self.duration = val
656
 
657
+ def on_b_filter(self, value):
658
+ val = value['new']
659
+ self.filter = val
660
+
661
  def on_b_record(self, value):
662
  val = value['new']
663
  self.record = val
 
703
  assert self.edf_writer.writeSamples(d) == 0
704
 
705
  def start_capture(self,
706
+ filter,
707
  record,
708
  viz,
709
  width,
 
727
  self.channel_states)
728
  )
729
  self._p_capture.start()
730
+ # print(f"PID capture: {self._p_capture.pid}")
731
 
732
  if viz:
733
  live_disp = LiveDisplay(channel_names = self.signal_labels, window_len=width)
 
735
  if record:
736
  self.open_recording_file()
737
 
738
+ buffer = []
739
+
740
  while True:
741
  with self._lock_msg_out:
742
  if self._msg_out is not None:
 
750
  print(mess[1])
751
 
752
  # retrieve all data points from p_data and put them in a list of np.array:
753
+ point = None
754
+ if p_data_i.poll(timeout=SAMPLE_TIME):
755
+ point = p_data_i.recv()
756
+ else:
 
 
 
 
 
757
  continue
758
+
759
+ n_array = np.array([point])
760
  n_array = filter_np(n_array)
761
 
762
+ if filter:
763
  n_array = np.swapaxes(n_array, 0, 1)
764
+ n_array = np.array([fp_vec[i].filter(a) if self.channel_states[i] != 'disabled' else [0] for i, a in enumerate(n_array)])
765
  n_array = np.swapaxes(n_array, 0, 1)
 
 
766
 
767
+ buffer += n_array.tolist()
768
+ if len(buffer) >= 50:
769
+
770
+ if viz:
771
+ live_disp.add_datapoints(buffer)
772
+
773
+ if record:
774
+ self.add_recording_data(buffer)
775
+
776
+ buffer = []
777
 
778
  # empty pipes
779
  while True:
portiloop/notebooks/tests.ipynb CHANGED
The diff for this file is too large to render. See raw diff
 
setup.py CHANGED
@@ -12,7 +12,6 @@ setup(
12
  'portilooplot',
13
  'ipywidgets',
14
  'python-periphery',
15
- 'spidev',
16
- 'scipy'
17
  ]
18
  )
 
12
  'portilooplot',
13
  'ipywidgets',
14
  'python-periphery',
15
+ 'spidev'
 
16
  ]
17
  )