Spaces:
Build error
Build error
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'
|