add lab 9
Esse commit está contido em:
@@ -0,0 +1,19 @@
|
||||
## code by Alexandre Barachant
|
||||
## adapted by Pierre Karashchuk for compatibility with Python3
|
||||
|
||||
from pylsl import StreamInfo, StreamOutlet
|
||||
import numpy as np
|
||||
try:
|
||||
input = raw_input
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
info = StreamInfo('Ganglion_EEG', 'EEG', 4, 200, 'float32',
|
||||
'Ganglion_123456789')
|
||||
outlet = StreamOutlet(info)
|
||||
while True:
|
||||
strSample = input().split(': ', 1)
|
||||
sample = 1e6*np.array(list(map(float, strSample[1].split(' '))))
|
||||
stamp = float(strSample[0])*1e-3
|
||||
outlet.push_sample(sample, stamp)
|
||||
# print('Pushed Sample At: ' + strSample[0])
|
||||
@@ -0,0 +1,27 @@
|
||||
# Lab 9: Measuring brain response to different smells
|
||||
|
||||
### Introduction
|
||||
In this lab, we will record the different brain responses to various smells.
|
||||
|
||||
### Setup
|
||||
|
||||
First, install the libraries (there are new python dependencies this time!):
|
||||
``` bash
|
||||
npm install
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
(If you don't have `npm`, you can install by running `brew install node`. You can get `brew` from https://brew.sh/)
|
||||
|
||||
### Stimulus Presentation + Recording
|
||||
|
||||
- Attach electrodes to participant's head, 2 on the frontal cortex (on forehead) and 2 on temporal lobe (right above the ears).
|
||||
- Connect to the ganglion and stream data: `node ganglion-lsl.js`
|
||||
- Run lsl-viewer to check connections and stream: `python lsl-viewer.py`
|
||||
- Record data by opening the `record_data.ipynb` notebook
|
||||
|
||||
### Analysis
|
||||
|
||||
- Open `analyze_data.ipynb` notebook
|
||||
- Replace the filenames at the beginning with your filenames
|
||||
- Run it and see the effects!
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
Arquivo executável
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from time import time, strftime, gmtime
|
||||
from pylsl import StreamInlet, resolve_byprop
|
||||
|
||||
def get_channel_names(inlet):
|
||||
"""Get channel names for an LSL inlet"""
|
||||
info = inlet.info()
|
||||
description = info.desc()
|
||||
|
||||
freq = info.nominal_srate()
|
||||
Nchan = info.channel_count()
|
||||
|
||||
ch = description.child('channels').first_child()
|
||||
ch_names = [ch.child_value('label')]
|
||||
for i in range(1, Nchan):
|
||||
ch = ch.next_sibling()
|
||||
ch_names.append(ch.child_value('label'))
|
||||
|
||||
if np.all(np.array(ch_names) == ""):
|
||||
ch_names = ['C{}'.format(x) for x in range(1,len(ch_names)+1)]
|
||||
|
||||
return ch_names
|
||||
|
||||
|
||||
def collect_eeg(inlet, duration=10, tag=None):
|
||||
"""Collect EEG data from stream.
|
||||
Returns a pandas object with the data
|
||||
|
||||
Arguments:
|
||||
duration -- length of recording in seconds.
|
||||
tag -- optionally tag the data
|
||||
"""
|
||||
|
||||
### ignore any data stored
|
||||
data = [0]
|
||||
while len(data) != 0:
|
||||
data, timestamp = inlet.pull_chunk(max_samples=1024)
|
||||
|
||||
res = []
|
||||
timestamps = []
|
||||
|
||||
t_init = time()
|
||||
print('Start recording at time t={:.3f}'.format(t_init))
|
||||
last = t_init
|
||||
while (last - t_init) < duration:
|
||||
try:
|
||||
data, timestamp = inlet.pull_chunk(timeout=1.0,
|
||||
max_samples=24)
|
||||
if timestamp:
|
||||
res.append(data)
|
||||
timestamps.extend(timestamp)
|
||||
last = timestamp[-1]
|
||||
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
t_done = time()
|
||||
print('Finished recording at time {:.3f} ({:.3f} seconds)'.format(t_done, t_done - t_init))
|
||||
|
||||
res = np.concatenate(res, axis=0)
|
||||
timestamps = np.array(timestamps)
|
||||
|
||||
res = np.c_[timestamps, res]
|
||||
ch_names = get_channel_names(inlet)
|
||||
|
||||
columns = ['timestamps'] + ch_names
|
||||
if tag is not None:
|
||||
columns += ['tag']
|
||||
tagc = np.repeat(tag, res.shape[0])[:, np.newaxis]
|
||||
res = np.hstack([res, tagc])
|
||||
|
||||
data = pd.DataFrame(data=res, columns=columns)
|
||||
|
||||
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,29 @@
|
||||
// code by Alexandre Barachant
|
||||
|
||||
const Ganglion = require('openbci-ganglion').Ganglion;
|
||||
const ganglion = new Ganglion();
|
||||
// Construct LSL Handoff Python Shell
|
||||
var PythonShell = require('python-shell');
|
||||
var lsloutlet = new PythonShell('LSLHandoff.py');
|
||||
|
||||
lsloutlet.on('message', function(message){
|
||||
console.log('LslOutlet: ' + message);
|
||||
});
|
||||
console.log('Python Shell Created for LSLHandoff');
|
||||
|
||||
ganglion.once('ganglionFound', (peripheral) => {
|
||||
// Stop searching for BLE devices once a ganglion is found.
|
||||
ganglion.searchStop();
|
||||
ganglion.on('sample', (sample) => {
|
||||
/** Work with sample */
|
||||
st = sample.channelData.join(' ');
|
||||
var s = ''+ sample.timeStamp + ': '+ st
|
||||
lsloutlet.send(s)
|
||||
});
|
||||
ganglion.once('ready', () => {
|
||||
ganglion.streamStart();
|
||||
});
|
||||
ganglion.connect(peripheral);
|
||||
});
|
||||
// Start scanning for BLE devices
|
||||
ganglion.searchStart();
|
||||
Arquivo executável
+191
@@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env python
|
||||
## code by Alexandre Barachant
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from scipy.signal import butter, filtfilt
|
||||
from time import time, sleep
|
||||
from pylsl import StreamInlet, resolve_byprop
|
||||
import seaborn as sns
|
||||
from threading import Thread
|
||||
|
||||
sns.set(style="whitegrid")
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
parser = OptionParser()
|
||||
|
||||
parser.add_option("-w", "--window",
|
||||
dest="window", type='float', default=5.,
|
||||
help="window lenght to display in seconds.")
|
||||
parser.add_option("-s", "--scale",
|
||||
dest="scale", type='float', default=100,
|
||||
help="scale in uV")
|
||||
parser.add_option("-r", "--refresh",
|
||||
dest="refresh", type='float', default=0.2,
|
||||
help="refresh rate in seconds.")
|
||||
parser.add_option("-f", "--figure",
|
||||
dest="figure", type='string', default="15x6",
|
||||
help="window size.")
|
||||
|
||||
filt = True
|
||||
subsample = 2
|
||||
buf = 12
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
window = options.window
|
||||
scale = options.scale
|
||||
figsize = np.int16(options.figure.split('x'))
|
||||
|
||||
print("looking for an EEG stream...")
|
||||
streams = resolve_byprop('type', 'EEG', timeout=2)
|
||||
|
||||
if len(streams) == 0:
|
||||
raise(RuntimeError("Cant find EEG stream"))
|
||||
print("Start aquiring data")
|
||||
|
||||
|
||||
class LSLViewer():
|
||||
|
||||
def __init__(self, stream, fig, axes, window, scale, dejitter=True):
|
||||
"""Init"""
|
||||
self.stream = stream
|
||||
self.window = window
|
||||
self.scale = scale
|
||||
self.dejitter = dejitter
|
||||
self.inlet = StreamInlet(stream, max_chunklen=buf)
|
||||
self.filt = True
|
||||
info = self.inlet.info()
|
||||
description = info.desc()
|
||||
|
||||
self.sfreq = info.nominal_srate()
|
||||
self.n_samples = int(self.sfreq * self.window)
|
||||
self.n_chan = info.channel_count()
|
||||
|
||||
ch = description.child('channels').first_child()
|
||||
ch_names = [ch.child_value('label')]
|
||||
|
||||
for i in range(self.n_chan):
|
||||
ch = ch.next_sibling()
|
||||
ch_names.append(ch.child_value('label'))
|
||||
|
||||
self.ch_names = ch_names
|
||||
|
||||
fig.canvas.mpl_connect('key_press_event', self.OnKeypress)
|
||||
fig.canvas.mpl_connect('button_press_event', self.onclick)
|
||||
|
||||
self.fig = fig
|
||||
self.axes = axes
|
||||
|
||||
sns.despine(left=True)
|
||||
|
||||
self.data = np.zeros((self.n_samples, self.n_chan))
|
||||
self.times = np.arange(-self.window, 0, 1./self.sfreq)
|
||||
impedances = np.std(self.data, axis=0)
|
||||
lines = []
|
||||
|
||||
for ii in range(self.n_chan):
|
||||
line, = axes.plot(self.times[::subsample],
|
||||
self.data[::subsample, ii] - ii, lw=1)
|
||||
lines.append(line)
|
||||
self.lines = lines
|
||||
|
||||
axes.set_ylim(-self.n_chan + 0.5, 0.5)
|
||||
ticks = np.arange(0, -self.n_chan, -1)
|
||||
|
||||
axes.set_xlabel('Time (s)')
|
||||
axes.xaxis.grid(False)
|
||||
axes.set_yticks(ticks)
|
||||
|
||||
ticks_labels = ['%s - %.1f' % (ch_names[ii], impedances[ii])
|
||||
for ii in range(self.n_chan)]
|
||||
axes.set_yticklabels(ticks_labels)
|
||||
|
||||
self.display_every = int(0.2 / (12/self.sfreq))
|
||||
|
||||
self.bf, self.af = butter(4, np.array([1, 40])/(self.sfreq/2.),
|
||||
'bandpass')
|
||||
|
||||
def update_plot(self):
|
||||
k = 0
|
||||
while self.started:
|
||||
samples, timestamps = self.inlet.pull_chunk(timeout=1.0,
|
||||
max_samples=12)
|
||||
if timestamps:
|
||||
self.data = np.vstack([self.data, samples])
|
||||
if self.dejitter:
|
||||
timestamps = np.float64(np.arange(len(timestamps)))
|
||||
timestamps /= self.sfreq
|
||||
timestamps += self.times[-1] + 1./self.sfreq
|
||||
self.times = np.concatenate([self.times, timestamps])
|
||||
|
||||
self.n_samples = int(self.sfreq * self.window)
|
||||
self.data = self.data[-self.n_samples:]
|
||||
self.times = self.times[-self.n_samples:]
|
||||
k += 1
|
||||
if k == self.display_every:
|
||||
if self.filt:
|
||||
data_f = filtfilt(self.bf, self.af, self.data, axis=0)
|
||||
else:
|
||||
data_f = self.data
|
||||
data_f -= data_f.mean(axis=0)
|
||||
|
||||
for ii in range(self.n_chan):
|
||||
self.lines[ii].set_xdata(self.times[::subsample] -
|
||||
self.times[-1])
|
||||
self.lines[ii].set_ydata(data_f[::subsample, ii] /
|
||||
self.scale - ii)
|
||||
|
||||
impedances = np.std(data_f, axis=0)
|
||||
ticks_labels = ['%s - %.2f' %
|
||||
(self.ch_names[ii], impedances[ii])
|
||||
for ii in range(self.n_chan)]
|
||||
self.axes.set_yticklabels(ticks_labels)
|
||||
self.axes.set_xlim(-self.window, 0)
|
||||
self.fig.canvas.draw()
|
||||
k = 0
|
||||
else:
|
||||
sleep(0.2)
|
||||
|
||||
def onclick(self, event):
|
||||
print((event.button, event.x, event.y, event.xdata, event.ydata))
|
||||
|
||||
def OnKeypress(self, event):
|
||||
if event.key == '/':
|
||||
self.scale *= 1.2
|
||||
elif event.key == '*':
|
||||
self.scale /= 1.2
|
||||
elif event.key == '+':
|
||||
self.window += 1
|
||||
elif event.key == '-':
|
||||
if self.window > 1:
|
||||
self.window -= 1
|
||||
elif event.key == 'd':
|
||||
self.filt = not(self.filt)
|
||||
|
||||
def start(self):
|
||||
self.started = True
|
||||
self.thread = Thread(target=self.update_plot)
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
def stop(self):
|
||||
self.started = False
|
||||
|
||||
|
||||
fig, axes = plt.subplots(1, 1, figsize=figsize, sharex=True)
|
||||
lslv = LSLViewer(streams[0], fig, axes, window, scale)
|
||||
|
||||
help_str = """
|
||||
toggle filter : d
|
||||
toogle full screen : f
|
||||
zoom out : /
|
||||
zoom in : *
|
||||
increase time scale : -
|
||||
decrease time scale : +
|
||||
"""
|
||||
print(help_str)
|
||||
lslv.start()
|
||||
|
||||
plt.show()
|
||||
lslv.stop()
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "lab3",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "ganglion-lsl.js",
|
||||
"dependencies": {
|
||||
"openbci-ganglion": "^0.4.3",
|
||||
"python-shell": "^0.4.0"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "GPL-3.0"
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 43,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from scipy import signal, stats\n",
|
||||
"import numpy as np\n",
|
||||
"import pandas as pd\n",
|
||||
"from time import time, strftime, gmtime\n",
|
||||
"from pylsl import StreamInlet, resolve_byprop\n",
|
||||
"from matplotlib.pyplot import *\n",
|
||||
"\n",
|
||||
"from collect_tools import collect_eeg\n",
|
||||
"\n",
|
||||
"%matplotlib inline"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 44,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"looking for an EEG stream...\n",
|
||||
"Found stream!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(\"looking for an EEG stream...\")\n",
|
||||
"streams = resolve_byprop('type', 'EEG', timeout=2)\n",
|
||||
"\n",
|
||||
"if len(streams) == 0:\n",
|
||||
" print('No streams found! Are you streaming data?')\n",
|
||||
"else:\n",
|
||||
" print('Found stream!')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 45,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"inlet = StreamInlet(streams[0], max_chunklen=24)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 46,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"baseline: just stay still and don't smell too much\n",
|
||||
"\n",
|
||||
"Start recording at time t=1491953530.525\n",
|
||||
"Finished recording at time 1491953540.611 (10.086 seconds)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(\"baseline: just stay still and don't smell too much\\n\")\n",
|
||||
"baseline = collect_eeg(inlet,duration=10, tag='baseline')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 47,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"smell: try to smell the object as much as possible\n",
|
||||
"\n",
|
||||
"Start recording at time t=1491953540.627\n",
|
||||
"Finished recording at time 1491953550.697 (10.069 seconds)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(\"smell: try to smell the object as much as possible\\n\")\n",
|
||||
"smell = collect_eeg(inlet,duration=10, tag='smell')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 48,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"data saved in:\n",
|
||||
"data/smell_1491953550.csv\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"data = pd.concat([baseline, smell])\n",
|
||||
"last = np.array(data.timestamps)[-1]\n",
|
||||
"last = int(float(last))\n",
|
||||
"fname = 'data/smell_{}.csv'.format(last)\n",
|
||||
"data.to_csv(fname, index=False)\n",
|
||||
"print('data saved in:\\n{}'.format(fname))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.6.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 1
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
numpy
|
||||
scipy
|
||||
matplotlib
|
||||
pandas
|
||||
pylsl
|
||||
Referência em uma Nova Issue
Bloquear um usuário