Spaces:
Sleeping
Sleeping
Commit
•
35af352
1
Parent(s):
2e2e678
Implemented stimulators for trains of spindles
Browse files- portiloop/notebooks/tests.ipynb +10 -39
- portiloop/src/capture.py +5 -2
- portiloop/src/detection.py +15 -8
- portiloop/src/stimulation.py +49 -2
- portiloop/src/utils.py +9 -6
- setup.py +31 -19
portiloop/notebooks/tests.ipynb
CHANGED
@@ -2,52 +2,23 @@
|
|
2 |
"cells": [
|
3 |
{
|
4 |
"cell_type": "code",
|
5 |
-
"execution_count":
|
6 |
"id": "16651843",
|
7 |
"metadata": {
|
8 |
"scrolled": false
|
9 |
},
|
10 |
-
"outputs": [
|
11 |
-
{
|
12 |
-
"data": {
|
13 |
-
"application/vnd.jupyter.widget-view+json": {
|
14 |
-
"model_id": "f46843d136af4c79a73841b997fa3284",
|
15 |
-
"version_major": 2,
|
16 |
-
"version_minor": 0
|
17 |
-
},
|
18 |
-
"text/plain": [
|
19 |
-
"VBox(children=(Accordion(children=(GridBox(children=(Label(value='CH2'), Label(value='CH3'), Label(value='CH4'…"
|
20 |
-
]
|
21 |
-
},
|
22 |
-
"metadata": {},
|
23 |
-
"output_type": "display_data"
|
24 |
-
},
|
25 |
-
{
|
26 |
-
"name": "stderr",
|
27 |
-
"output_type": "stream",
|
28 |
-
"text": [
|
29 |
-
"Exception in thread Thread-3:\n",
|
30 |
-
"Traceback (most recent call last):\n",
|
31 |
-
" File \"C:\\Users\\milos\\AppData\\Local\\Programs\\Python\\Python37\\lib\\threading.py\", line 917, in _bootstrap_inner\n",
|
32 |
-
" self.run()\n",
|
33 |
-
" File \"C:\\Users\\milos\\AppData\\Local\\Programs\\Python\\Python37\\lib\\threading.py\", line 865, in run\n",
|
34 |
-
" self._target(*self._args, **self._kwargs)\n",
|
35 |
-
" File \"c:\\users\\milos\\documents\\github\\portiloop-software\\portiloop\\src\\capture.py\", line 927, in start_capture\n",
|
36 |
-
" detector = detector_cls(threshold, channel=channel) if detector_cls is not None else None\n",
|
37 |
-
" File \"c:\\users\\milos\\documents\\github\\portiloop-software\\portiloop\\src\\detection.py\", line 56, in __init__\n",
|
38 |
-
" self.interpreters.append(edgetpu.make_interpreter(model_path))\n",
|
39 |
-
"NameError: name 'edgetpu' is not defined\n",
|
40 |
-
"\n"
|
41 |
-
]
|
42 |
-
}
|
43 |
-
],
|
44 |
"source": [
|
45 |
"from portiloop.src.capture import Capture\n",
|
46 |
"from portiloop.src.detection import SleepSpindleRealTimeDetector\n",
|
47 |
-
"from portiloop.src.stimulation import SleepSpindleRealTimeStimulator
|
|
|
|
|
48 |
"\n",
|
49 |
"my_detector_class = SleepSpindleRealTimeDetector # you may want to implement yours\n",
|
50 |
-
"my_stimulator_class = SleepSpindleRealTimeStimulator #
|
|
|
|
|
51 |
"\n",
|
52 |
"cap = Capture(detector_cls=my_detector_class, stimulator_cls=my_stimulator_class)"
|
53 |
]
|
@@ -55,7 +26,7 @@
|
|
55 |
],
|
56 |
"metadata": {
|
57 |
"kernelspec": {
|
58 |
-
"display_name": "Python 3
|
59 |
"language": "python",
|
60 |
"name": "python3"
|
61 |
},
|
@@ -69,7 +40,7 @@
|
|
69 |
"name": "python",
|
70 |
"nbconvert_exporter": "python",
|
71 |
"pygments_lexer": "ipython3",
|
72 |
-
"version": "3.7.
|
73 |
},
|
74 |
"vscode": {
|
75 |
"interpreter": {
|
|
|
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.src.capture import Capture\n",
|
13 |
"from portiloop.src.detection import SleepSpindleRealTimeDetector\n",
|
14 |
+
"from portiloop.src.stimulation import (SleepSpindleRealTimeStimulator,\n",
|
15 |
+
" SpindleTrainRealTimeStimulator,\n",
|
16 |
+
" IsolatedSpindleRealTimeStimulator)\n",
|
17 |
"\n",
|
18 |
"my_detector_class = SleepSpindleRealTimeDetector # you may want to implement yours\n",
|
19 |
+
"my_stimulator_class = SleepSpindleRealTimeStimulator # all spindles\n",
|
20 |
+
"# my_stimulator_class = SpindleTrainRealTimeStimulator # uncomment for spindle trains only\n",
|
21 |
+
"# my_stimulator_class = IsolatedSpindleRealTimeStimulator # uncomment for isolated spindles only\n",
|
22 |
"\n",
|
23 |
"cap = Capture(detector_cls=my_detector_class, stimulator_cls=my_stimulator_class)"
|
24 |
]
|
|
|
26 |
],
|
27 |
"metadata": {
|
28 |
"kernelspec": {
|
29 |
+
"display_name": "Python 3",
|
30 |
"language": "python",
|
31 |
"name": "python3"
|
32 |
},
|
|
|
40 |
"name": "python",
|
41 |
"nbconvert_exporter": "python",
|
42 |
"pygments_lexer": "ipython3",
|
43 |
+
"version": "3.7.3"
|
44 |
},
|
45 |
"vscode": {
|
46 |
"interpreter": {
|
portiloop/src/capture.py
CHANGED
@@ -1013,8 +1013,11 @@ class Capture:
|
|
1013 |
with self._lock_msg_out:
|
1014 |
if self._msg_out == "STOP":
|
1015 |
break
|
1016 |
-
|
1017 |
-
|
|
|
|
|
|
|
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 |
|
|
|
1013 |
with self._lock_msg_out:
|
1014 |
if self._msg_out == "STOP":
|
1015 |
break
|
1016 |
+
|
1017 |
+
file_point = file_reader.get_point()
|
1018 |
+
if file_point is None:
|
1019 |
+
break
|
1020 |
+
index, raw_point, off_filtered_point, past_stimulation, lacourse_stimulation = file_point
|
1021 |
n_array_raw = np.array([0, raw_point, 0, 0, 0, 0, 0, 0])
|
1022 |
n_array_raw = np.reshape(n_array_raw, (1, 8))
|
1023 |
|
portiloop/src/detection.py
CHANGED
@@ -14,14 +14,12 @@ import numpy as np
|
|
14 |
|
15 |
class Detector(ABC):
|
16 |
|
17 |
-
def __init__(self, threshold=None):
|
18 |
"""
|
19 |
-
|
20 |
-
This is the value of the threshold that the user can set in the Portiloop GUI.
|
21 |
-
Caution: even if you don't need this manual threshold in your application,
|
22 |
-
your implementation of __init__() still needs to have this keyword argument.
|
23 |
"""
|
24 |
self.threshold = threshold
|
|
|
25 |
|
26 |
@abstractmethod
|
27 |
def detect(self, datapoints):
|
@@ -47,10 +45,16 @@ DEFAULT_MODEL_PATH = str(Path(__file__).parent.parent / "models/portiloop_model_
|
|
47 |
# print(DEFAULT_MODEL_PATH)
|
48 |
|
49 |
class SleepSpindleRealTimeDetector(Detector):
|
50 |
-
def __init__(self,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
model_path = DEFAULT_MODEL_PATH if model_path is None else model_path
|
52 |
self.verbose = verbose
|
53 |
-
self.channel = channel
|
54 |
self.num_models_parallel = num_models_parallel
|
55 |
|
56 |
self.interpreters = []
|
@@ -78,12 +82,15 @@ class SleepSpindleRealTimeDetector(Detector):
|
|
78 |
|
79 |
self.current_stride_counter = self.stride_counters[0] - 1
|
80 |
|
81 |
-
super().__init__(threshold)
|
82 |
|
83 |
def detect(self, datapoints):
|
84 |
"""
|
85 |
Takes datapoints as input and outputs a detection signal.
|
86 |
datapoints is a list of lists of n channels: may contain several datapoints.
|
|
|
|
|
|
|
87 |
"""
|
88 |
res = []
|
89 |
for inp in datapoints:
|
|
|
14 |
|
15 |
class Detector(ABC):
|
16 |
|
17 |
+
def __init__(self, threshold=None, channel=None):
|
18 |
"""
|
19 |
+
Mandatory arguments are from the in the Portiloop GUI.
|
|
|
|
|
|
|
20 |
"""
|
21 |
self.threshold = threshold
|
22 |
+
self.channel = channel
|
23 |
|
24 |
@abstractmethod
|
25 |
def detect(self, datapoints):
|
|
|
45 |
# print(DEFAULT_MODEL_PATH)
|
46 |
|
47 |
class SleepSpindleRealTimeDetector(Detector):
|
48 |
+
def __init__(self,
|
49 |
+
threshold=0.5,
|
50 |
+
num_models_parallel=8,
|
51 |
+
window_size=54,
|
52 |
+
seq_stride=42,
|
53 |
+
model_path=None,
|
54 |
+
verbose=False,
|
55 |
+
channel=2):
|
56 |
model_path = DEFAULT_MODEL_PATH if model_path is None else model_path
|
57 |
self.verbose = verbose
|
|
|
58 |
self.num_models_parallel = num_models_parallel
|
59 |
|
60 |
self.interpreters = []
|
|
|
82 |
|
83 |
self.current_stride_counter = self.stride_counters[0] - 1
|
84 |
|
85 |
+
super().__init__(threshold, channel)
|
86 |
|
87 |
def detect(self, datapoints):
|
88 |
"""
|
89 |
Takes datapoints as input and outputs a detection signal.
|
90 |
datapoints is a list of lists of n channels: may contain several datapoints.
|
91 |
+
|
92 |
+
The output signal is a list of tuples (is_spindle, is_train_of_spindles).
|
93 |
+
|
94 |
"""
|
95 |
res = []
|
96 |
for inp in datapoints:
|
portiloop/src/stimulation.py
CHANGED
@@ -17,8 +17,6 @@ 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:
|
@@ -153,6 +151,55 @@ class SleepSpindleRealTimeStimulator(Stimulator):
|
|
153 |
self.delayer = delayer
|
154 |
self.delayer.stimulate = lambda: self.send_stimulation("DELAY_STIM", True)
|
155 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
# Class that delays stimulation to always stimulate peak or through
|
157 |
class UpStateDelayer:
|
158 |
def __init__(self, sample_freq, peak, time_to_buffer, stimulate=None):
|
|
|
17 |
import numpy as np
|
18 |
import matplotlib.pyplot as plt
|
19 |
|
|
|
|
|
20 |
|
21 |
|
22 |
# Abstract interface for developers:
|
|
|
151 |
self.delayer = delayer
|
152 |
self.delayer.stimulate = lambda: self.send_stimulation("DELAY_STIM", True)
|
153 |
|
154 |
+
|
155 |
+
class SpindleTrainRealTimeStimulator(SleepSpindleRealTimeStimulator):
|
156 |
+
def __init__(self):
|
157 |
+
self.max_spindle_train_t = 6.0
|
158 |
+
super().__init__()
|
159 |
+
|
160 |
+
def stimulate(self, detection_signal):
|
161 |
+
for sig in detection_signal:
|
162 |
+
# We detect a stimulation
|
163 |
+
if sig:
|
164 |
+
# Record time of stimulation
|
165 |
+
ts = time.time()
|
166 |
+
|
167 |
+
# Check if time since last stimulation is long enough
|
168 |
+
elapsed = ts - self.last_detected_ts
|
169 |
+
if self.wait_t < elapsed < self.max_spindle_train_t:
|
170 |
+
if self.delayer is not None:
|
171 |
+
# If we have a delayer, notify it
|
172 |
+
self.delayer.detected()
|
173 |
+
# Send the LSL marer for the fast stimulation
|
174 |
+
self.send_stimulation("FAST_STIM", False)
|
175 |
+
else:
|
176 |
+
self.send_stimulation("STIM", True)
|
177 |
+
|
178 |
+
self.last_detected_ts = ts
|
179 |
+
|
180 |
+
|
181 |
+
class IsolatedSpindleRealTimeStimulator(SpindleTrainRealTimeStimulator):
|
182 |
+
def stimulate(self, detection_signal):
|
183 |
+
for sig in detection_signal:
|
184 |
+
# We detect a stimulation
|
185 |
+
if sig:
|
186 |
+
# Record time of stimulation
|
187 |
+
ts = time.time()
|
188 |
+
|
189 |
+
# Check if time since last stimulation is long enough
|
190 |
+
elapsed = ts - self.last_detected_ts
|
191 |
+
if self.max_spindle_train_t < elapsed:
|
192 |
+
if self.delayer is not None:
|
193 |
+
# If we have a delayer, notify it
|
194 |
+
self.delayer.detected()
|
195 |
+
# Send the LSL marer for the fast stimulation
|
196 |
+
self.send_stimulation("FAST_STIM", False)
|
197 |
+
else:
|
198 |
+
self.send_stimulation("STIM", True)
|
199 |
+
|
200 |
+
self.last_detected_ts = ts
|
201 |
+
|
202 |
+
|
203 |
# Class that delays stimulation to always stimulate peak or through
|
204 |
class UpStateDelayer:
|
205 |
def __init__(self, sample_freq, peak, time_to_buffer, stimulate=None):
|
portiloop/src/utils.py
CHANGED
@@ -118,9 +118,12 @@ class FileReader:
|
|
118 |
"""
|
119 |
Returns the next point in the file
|
120 |
"""
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
|
|
|
|
|
|
|
118 |
"""
|
119 |
Returns the next point in the file
|
120 |
"""
|
121 |
+
try:
|
122 |
+
point = next(self.csv_reader)
|
123 |
+
self.index += 1
|
124 |
+
while time.time() - self.last_time < self.wait_time:
|
125 |
+
continue
|
126 |
+
self.last_time = time.time()
|
127 |
+
return self.index, float(point[0]), float(point[1]), point[2] == '1', point[3] == '1'
|
128 |
+
except StopIteration:
|
129 |
+
return None
|
setup.py
CHANGED
@@ -1,27 +1,39 @@
|
|
1 |
from setuptools import setup, find_packages
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
setup(
|
4 |
name='portiloop',
|
5 |
version='0.0.1',
|
6 |
packages=[package for package in find_packages()],
|
7 |
description='Portiloop software library',
|
8 |
-
install_requires=
|
9 |
-
'EDFlib-Python',
|
10 |
-
'numpy',
|
11 |
-
'portilooplot',
|
12 |
-
'ipywidgets',
|
13 |
-
'python-periphery',
|
14 |
-
'scipy',
|
15 |
-
'matplotlib',
|
16 |
-
],
|
17 |
-
extras_require={
|
18 |
-
'Portiloop': ['pycoral',
|
19 |
-
'spidev',
|
20 |
-
'pylsl-coral',
|
21 |
-
'pyalsaaudio'],
|
22 |
-
'PC': ['gradio',
|
23 |
-
'tensorflow',
|
24 |
-
'pyxdf',
|
25 |
-
'wonambi']
|
26 |
-
},
|
27 |
)
|
|
|
1 |
from setuptools import setup, find_packages
|
2 |
+
import io
|
3 |
+
|
4 |
+
|
5 |
+
def is_coral():
|
6 |
+
try:
|
7 |
+
with io.open('/sys/firmware/devicetree/base/model', 'r') as m:
|
8 |
+
if 'phanbell' in m.read().lower(): return True
|
9 |
+
except Exception: pass
|
10 |
+
return False
|
11 |
+
|
12 |
+
requirements_list = ['wheel',
|
13 |
+
'EDFlib-Python',
|
14 |
+
'numpy',
|
15 |
+
'portilooplot',
|
16 |
+
'ipywidgets',
|
17 |
+
'python-periphery',
|
18 |
+
'scipy',
|
19 |
+
'matplotlib']
|
20 |
+
|
21 |
+
if is_coral():
|
22 |
+
requirements_list += ['pycoral',
|
23 |
+
'spidev',
|
24 |
+
'pylsl-coral',
|
25 |
+
'pyalsaaudio']
|
26 |
+
else:
|
27 |
+
requirements_list += ['gradio',
|
28 |
+
'tensorflow',
|
29 |
+
'pyxdf',
|
30 |
+
'wonambi']
|
31 |
+
|
32 |
|
33 |
setup(
|
34 |
name='portiloop',
|
35 |
version='0.0.1',
|
36 |
packages=[package for package in find_packages()],
|
37 |
description='Portiloop software library',
|
38 |
+
install_requires=requirements_list,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
)
|