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