Spaces:
Sleeping
Sleeping
Merge pull request #6 from Portiloop/milo/dev
Browse filesAdded ability to read from file and fixed delayer bugs
- portiloop/src/capture.py +20 -8
- portiloop/src/stimulation.py +12 -6
- portiloop/src/utils.py +21 -3
portiloop/src/capture.py
CHANGED
@@ -20,7 +20,7 @@ from portiloop.src.stimulation import UpStateDelayer
|
|
20 |
|
21 |
from portiloop.src.processing import FilterPipeline, int_to_float
|
22 |
from portiloop.src.config import mod_config, LEADOFF_CONFIG, FRONTEND_CONFIG, to_ads_frequency
|
23 |
-
from portiloop.src.utils import FileReader, LiveDisplay, DummyAlsaMixer, EDFRecorder, EDF_PATH
|
24 |
from IPython.display import clear_output, display
|
25 |
import ipywidgets as widgets
|
26 |
|
@@ -500,6 +500,7 @@ class Capture:
|
|
500 |
|
501 |
self.b_capture.observe(self.on_b_capture, 'value')
|
502 |
self.b_clock.observe(self.on_b_clock, 'value')
|
|
|
503 |
self.b_frequency.observe(self.on_b_frequency, 'value')
|
504 |
self.b_threshold.observe(self.on_b_threshold, 'value')
|
505 |
self.b_duration.observe(self.on_b_duration, 'value')
|
@@ -909,6 +910,8 @@ class Capture:
|
|
909 |
self.__capture_on = True
|
910 |
p_msg_io, p_msg_io_2 = mp.Pipe()
|
911 |
p_data_i, p_data_o = mp.Pipe(duplex=False)
|
|
|
|
|
912 |
|
913 |
# Initialize filtering pipeline
|
914 |
if filter:
|
@@ -941,7 +944,7 @@ class Capture:
|
|
941 |
self._p_capture.start()
|
942 |
print(f"PID capture: {self._p_capture.pid}")
|
943 |
else:
|
944 |
-
filename =
|
945 |
file_reader = FileReader(filename)
|
946 |
|
947 |
# Initialize display if requested
|
@@ -974,7 +977,7 @@ class Capture:
|
|
974 |
|
975 |
# Initialize stimulation delayer if requested
|
976 |
if not self.spindle_detection_mode == 'Fast' and stimulator is not None:
|
977 |
-
stimulation_delayer = UpStateDelayer(self.frequency, self.
|
978 |
stimulator.add_delayer(stimulation_delayer)
|
979 |
else:
|
980 |
stimulation_delayer = None
|
@@ -1006,7 +1009,14 @@ class Capture:
|
|
1006 |
# Convert point from int to corresponding value in microvolts
|
1007 |
n_array_raw = int_to_float(np.array([point]))
|
1008 |
elif self.signal_input == "File":
|
1009 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1010 |
|
1011 |
# Go through filtering pipeline
|
1012 |
if filter:
|
@@ -1025,7 +1035,7 @@ class Capture:
|
|
1025 |
|
1026 |
# Adds point to buffer for delayed stimulation
|
1027 |
if stimulation_delayer is not None:
|
1028 |
-
stimulation_delayer.
|
1029 |
|
1030 |
# Check if detection is on or off
|
1031 |
with self._pause_detect_lock:
|
@@ -1045,8 +1055,10 @@ class Capture:
|
|
1045 |
if test_stimulus:
|
1046 |
stimulator.test_stimulus()
|
1047 |
|
1048 |
-
|
1049 |
-
|
|
|
|
|
1050 |
|
1051 |
# Add point to the buffer to send to viz and recorder
|
1052 |
buffer += filtered_point
|
@@ -1070,7 +1082,7 @@ class Capture:
|
|
1070 |
p_data_i.close()
|
1071 |
p_msg_io.close()
|
1072 |
self._p_capture.join()
|
1073 |
-
|
1074 |
|
1075 |
if record:
|
1076 |
recorder.close_recording_file()
|
|
|
20 |
|
21 |
from portiloop.src.processing import FilterPipeline, int_to_float
|
22 |
from portiloop.src.config import mod_config, LEADOFF_CONFIG, FRONTEND_CONFIG, to_ads_frequency
|
23 |
+
from portiloop.src.utils import FileReader, LiveDisplay, DummyAlsaMixer, EDFRecorder, EDF_PATH, RECORDING_PATH
|
24 |
from IPython.display import clear_output, display
|
25 |
import ipywidgets as widgets
|
26 |
|
|
|
500 |
|
501 |
self.b_capture.observe(self.on_b_capture, 'value')
|
502 |
self.b_clock.observe(self.on_b_clock, 'value')
|
503 |
+
self.b_signal_input.observe(self.on_b_signal_input, 'value')
|
504 |
self.b_frequency.observe(self.on_b_frequency, 'value')
|
505 |
self.b_threshold.observe(self.on_b_threshold, 'value')
|
506 |
self.b_duration.observe(self.on_b_duration, 'value')
|
|
|
910 |
self.__capture_on = True
|
911 |
p_msg_io, p_msg_io_2 = mp.Pipe()
|
912 |
p_data_i, p_data_o = mp.Pipe(duplex=False)
|
913 |
+
else:
|
914 |
+
p_msg_io, _ = mp.Pipe()
|
915 |
|
916 |
# Initialize filtering pipeline
|
917 |
if filter:
|
|
|
944 |
self._p_capture.start()
|
945 |
print(f"PID capture: {self._p_capture.pid}")
|
946 |
else:
|
947 |
+
filename = RECORDING_PATH / 'test_recording.csv'
|
948 |
file_reader = FileReader(filename)
|
949 |
|
950 |
# Initialize display if requested
|
|
|
977 |
|
978 |
# Initialize stimulation delayer if requested
|
979 |
if not self.spindle_detection_mode == 'Fast' and stimulator is not None:
|
980 |
+
stimulation_delayer = UpStateDelayer(self.frequency, self.spindle_detection_mode == 'Peak', 0.3)
|
981 |
stimulator.add_delayer(stimulation_delayer)
|
982 |
else:
|
983 |
stimulation_delayer = None
|
|
|
1009 |
# Convert point from int to corresponding value in microvolts
|
1010 |
n_array_raw = int_to_float(np.array([point]))
|
1011 |
elif self.signal_input == "File":
|
1012 |
+
# Check if the message to stop has been sent
|
1013 |
+
with self._lock_msg_out:
|
1014 |
+
if self._msg_out == "STOP":
|
1015 |
+
break
|
1016 |
+
|
1017 |
+
index, raw_point, off_filtered_point, past_stimulation, lacourse_stimulation = file_reader.get_point()
|
1018 |
+
n_array_raw = np.array([0, raw_point, 0, 0, 0, 0, 0, 0])
|
1019 |
+
n_array_raw = np.reshape(n_array_raw, (1, 8))
|
1020 |
|
1021 |
# Go through filtering pipeline
|
1022 |
if filter:
|
|
|
1035 |
|
1036 |
# Adds point to buffer for delayed stimulation
|
1037 |
if stimulation_delayer is not None:
|
1038 |
+
stimulation_delayer.step_timesteps(filtered_point[0][channel-1])
|
1039 |
|
1040 |
# Check if detection is on or off
|
1041 |
with self._pause_detect_lock:
|
|
|
1055 |
if test_stimulus:
|
1056 |
stimulator.test_stimulus()
|
1057 |
|
1058 |
+
# Send the stimulation from the file reader
|
1059 |
+
if stimulator is not None:
|
1060 |
+
if self.signal_input == "File" and lacourse_stimulation:
|
1061 |
+
stimulator.send_stimulation("GROUND_TRUTH_STIM", False)
|
1062 |
|
1063 |
# Add point to the buffer to send to viz and recorder
|
1064 |
buffer += filtered_point
|
|
|
1082 |
p_data_i.close()
|
1083 |
p_msg_io.close()
|
1084 |
self._p_capture.join()
|
1085 |
+
self.__capture_on = False
|
1086 |
|
1087 |
if record:
|
1088 |
recorder.close_recording_file()
|
portiloop/src/stimulation.py
CHANGED
@@ -14,6 +14,11 @@ if ADS:
|
|
14 |
|
15 |
import wave
|
16 |
from scipy.signal import find_peaks
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
|
19 |
# Abstract interface for developers:
|
@@ -54,14 +59,14 @@ class SleepSpindleRealTimeStimulator(Stimulator):
|
|
54 |
channel_format='string',
|
55 |
source_id='portiloop1') # TODO: replace this by unique device identifier
|
56 |
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
|
63 |
self.lsl_outlet_markers = pylsl.StreamOutlet(lsl_markers_info)
|
64 |
-
|
65 |
|
66 |
# Initialize Alsa stuff
|
67 |
# Open WAV file and set PCM device
|
@@ -121,6 +126,7 @@ class SleepSpindleRealTimeStimulator(Stimulator):
|
|
121 |
self.last_detected_ts = ts
|
122 |
|
123 |
def send_stimulation(self, lsl_text, sound):
|
|
|
124 |
# Send lsl stimulation
|
125 |
self.lsl_outlet_markers.push_sample([lsl_text])
|
126 |
# Send sound to patient
|
|
|
14 |
|
15 |
import wave
|
16 |
from scipy.signal import find_peaks
|
17 |
+
import numpy as np
|
18 |
+
import matplotlib.pyplot as plt
|
19 |
+
|
20 |
+
import alsaaudio
|
21 |
+
import pylsl
|
22 |
|
23 |
|
24 |
# Abstract interface for developers:
|
|
|
59 |
channel_format='string',
|
60 |
source_id='portiloop1') # TODO: replace this by unique device identifier
|
61 |
|
62 |
+
# lsl_markers_info_fast = pylsl.StreamInfo(name='Portiloop_stimuli_fast',
|
63 |
+
# type='Markers',
|
64 |
+
# channel_count=1,
|
65 |
+
# channel_format='string',
|
66 |
+
# source_id='portiloop1') # TODO: replace this by unique device identifier
|
67 |
|
68 |
self.lsl_outlet_markers = pylsl.StreamOutlet(lsl_markers_info)
|
69 |
+
# self.lsl_outlet_markers_fast = pylsl.StreamOutlet(lsl_markers_info_fast)
|
70 |
|
71 |
# Initialize Alsa stuff
|
72 |
# Open WAV file and set PCM device
|
|
|
126 |
self.last_detected_ts = ts
|
127 |
|
128 |
def send_stimulation(self, lsl_text, sound):
|
129 |
+
print(f"Stimulating with text: {lsl_text}")
|
130 |
# Send lsl stimulation
|
131 |
self.lsl_outlet_markers.push_sample([lsl_text])
|
132 |
# Send sound to patient
|
portiloop/src/utils.py
CHANGED
@@ -2,9 +2,13 @@ from EDFlib.edfwriter import EDFwriter
|
|
2 |
from portilooplot.jupyter_plot import ProgressPlot
|
3 |
from pathlib import Path
|
4 |
import numpy as np
|
|
|
|
|
5 |
|
6 |
-
EDF_PATH = Path.home() / 'workspace' / 'edf_recording'
|
7 |
|
|
|
|
|
|
|
8 |
|
9 |
|
10 |
class DummyAlsaMixer:
|
@@ -102,7 +106,21 @@ class LiveDisplay():
|
|
102 |
|
103 |
class FileReader:
|
104 |
def __init__(self, filename):
|
105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
|
107 |
def get_point(self):
|
108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
from portilooplot.jupyter_plot import ProgressPlot
|
3 |
from pathlib import Path
|
4 |
import numpy as np
|
5 |
+
import csv
|
6 |
+
import time
|
7 |
|
|
|
8 |
|
9 |
+
EDF_PATH = Path.home() / 'workspace' / 'edf_recording'
|
10 |
+
# Path to the recordings
|
11 |
+
RECORDING_PATH = Path.home() / 'portiloop-software' / 'portiloop' / 'recordings'
|
12 |
|
13 |
|
14 |
class DummyAlsaMixer:
|
|
|
106 |
|
107 |
class FileReader:
|
108 |
def __init__(self, filename):
|
109 |
+
file = open(filename, 'r')
|
110 |
+
# Open a csv file
|
111 |
+
print(f"Reading from file {filename}")
|
112 |
+
self.csv_reader = csv.reader(file, delimiter=',')
|
113 |
+
self.wait_time = 1/250.0
|
114 |
+
self.index = -1
|
115 |
+
self.last_time = time.time()
|
116 |
|
117 |
def get_point(self):
|
118 |
+
"""
|
119 |
+
Returns the next point in the file
|
120 |
+
"""
|
121 |
+
point = next(self.csv_reader)
|
122 |
+
self.index += 1
|
123 |
+
while time.time() - self.last_time < self.wait_time:
|
124 |
+
continue
|
125 |
+
self.last_time = time.time()
|
126 |
+
return self.index, float(point[0]), float(point[1]), point[2] == '1', point[3] == '1'
|