Skip to content

Commit f78cefa

Browse files
authored
Merge pull request #962 from OpenBCI/development
GUI 5.0.4
2 parents 4b3dfbd + fd87f89 commit f78cefa

27 files changed

+824
-621
lines changed

.github/workflows/mergenovadev.yml

Lines changed: 0 additions & 20 deletions
This file was deleted.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ libBoardController.so
2525
libDataHandler.so
2626
libGanglionLib.so
2727
libGanglionScan.so
28+
libunicorn.so

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ jobs:
1313
branches:
1414
only:
1515
- master
16-
- novaxr-dev
1716

1817
before_install:
1918
- if [ "$TRAVIS_OS_NAME" = osx ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then openssl aes-256-cbc -K $encrypted_2f5d2771e3cb_key -iv $encrypted_2f5d2771e3cb_iv -in release_script/mac_only/Certificates.p12.enc -out release_script/mac_only/Certificates.p12 -d; fi

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
# v5.0.4
2+
3+
### Improvements
4+
* Add Copy/Paste for all textfields on all OS #940
5+
* Update BrainFlow library to version that includes a marker channel
6+
* Handle paths with spaces on Linux Standalone GUI #916
7+
* Allow Expert Ganglion Users to send square wave commands via keyboard #950
8+
* Show Send Custom Hardware Command UI for Cyton Expert Mode in Hardware Settings
9+
* Improve Hardware Setting UX/UI for ADS1299 boards #954
10+
11+
### Bug Fixes
12+
* Clean up GUI code to fix Processing/JVM memory issue causing crash #955
13+
* Avoid playback history file not found exception #959
14+
* Fix issue with Spectrogram Widget data image default height
15+
* Fix issue with Accelerometer Widget graph default vertical scale
16+
* Fix text drawing in wrong spot in Session Data box in Control Panel
17+
118
# v5.0.3
219

320
### Improvements
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#############################################################################
2+
## BrainFlow + LSL ##
3+
## Use BrainFlow to read data from board send it as an LSL stream ##
4+
#############################################################################
5+
6+
# Install dependencies with:
7+
# pip install --upgrade numpy brainflow pylsl
8+
9+
# Here are example commands using Cyton and get_exg_channels()from BrainFlow. This has only been tested with Cyton + Dongle, for now.
10+
11+
# Mac:
12+
# python3 Networking-Test-Kit/LSL/brainflow_lsl.py --board-id 2 --serial-port /dev/cu.usbserial-DM00D7TW --name test --data-type EXG --channel-names 1,2,3,4,5,6,7,8 --uid brainflow
13+
14+
# Windows:
15+
# python3 Networking-Test-Kit/LSL/brainflow_lsl.py --board-id 2 --serial-port COM3 --name test --data-type EXG --channel-names 1,2,3,4,5,6,7,8 --uid brainflow
16+
17+
import argparse
18+
import time
19+
import numpy as np
20+
21+
from queue import Queue
22+
23+
import brainflow
24+
from brainflow.board_shim import BoardShim, BrainFlowInputParams
25+
from brainflow.data_filter import DataFilter, FilterTypes, AggOperations
26+
27+
from pylsl import StreamInfo, StreamOutlet, local_clock
28+
29+
def channel_select(board, board_id, data_type):
30+
switcher = {
31+
'EXG': board.get_exg_channels(board_id),
32+
# can add more
33+
}
34+
35+
return switcher.get(data_type, "error")
36+
37+
def main():
38+
BoardShim.enable_dev_board_logger()
39+
40+
parser = argparse.ArgumentParser()
41+
42+
# brainflow params - use docs to check which parameters are required for specific board, e.g. for Cyton set serial port
43+
parser.add_argument('--timeout', type=int, help='timeout for device discovery or connection', required=False, default=0)
44+
parser.add_argument('--ip-address', type=str, help='ip address', required=False, default='')
45+
parser.add_argument('--board-id', type=int, help='board id, check docs to get a list of supported boards', required=True)
46+
parser.add_argument('--serial-port', type=str, help='serial port', required=False, default='')
47+
parser.add_argument('--streamer-params', type=str, help='streamer params', required=False, default='')
48+
49+
# LSL params
50+
parser.add_argument('--name', type=str, help='name', required=True)
51+
parser.add_argument('--data-type', type=str, help='data type', required=True)
52+
parser.add_argument('--channel-names', type=str, help='channel names', required=True)
53+
parser.add_argument('--uid', type=str, help='uid', required=True)
54+
55+
args = parser.parse_args()
56+
57+
# brainflow initialization
58+
params = BrainFlowInputParams()
59+
params.serial_port = args.serial_port
60+
params.ip_address = args.ip_address
61+
board = BoardShim(args.board_id, params)
62+
63+
# LSL initialization
64+
channel_names = args.channel_names.split(',')
65+
n_channels = len(channel_names)
66+
srate = board.get_sampling_rate(args.board_id)
67+
info = StreamInfo(args.name, args.data_type, n_channels, srate, 'double64', args.uid)
68+
outlet = StreamOutlet(info)
69+
fw_delay = 0
70+
71+
# prepare session
72+
board.prepare_session()
73+
74+
# send commands to the board for every channel. Cyton has 8 Channels. Here, we turn off every channel except for 1 and 8.
75+
# This is here for testing purposes.
76+
#board.config_board("x1000110X") #Lower the gain to 1x on channel 1
77+
#board.config_board("x1061000X")
78+
#board.config_board("x2161000X")
79+
#board.config_board("x3161000X")
80+
#board.config_board("x4161000X")
81+
#board.config_board("x5161000X")
82+
#board.config_board("x6161000X")
83+
#board.config_board("x7161000X")
84+
#board.config_board("x8060110X")
85+
86+
# start stream
87+
board.start_stream(45000, args.streamer_params)
88+
time.sleep(1)
89+
start_time = local_clock()
90+
sent_samples = 0
91+
queue = Queue(maxsize = 5*srate)
92+
chans = channel_select(board, args.board_id, args.data_type)
93+
94+
# Vars for filters
95+
applyBandStop = True
96+
applyBandPass = True
97+
bandStopFrequency = 60.0
98+
bp_lowerBound = 5.0
99+
bp_upperBound = 50.0
100+
bp_centerFreq = (bp_upperBound + bp_lowerBound) / 2.0;
101+
bp_bandWidth = bp_upperBound - bp_lowerBound
102+
103+
104+
# read data with brainflow and send it via LSL
105+
print("Now sending data...")
106+
while True:
107+
data = board.get_board_data()[chans]
108+
109+
# It's best to apply filters on the receiving end, but this is here just for testing purposes.
110+
"""
111+
for chan in range(len(chans)):
112+
if applyBandStop:
113+
DataFilter.perform_bandstop(data[chan],
114+
BoardShim.get_sampling_rate(args.board_id),
115+
bandStopFrequency,
116+
4.0,
117+
2,
118+
FilterTypes.BUTTERWORTH.value,
119+
0);
120+
if applyBandPass:
121+
DataFilter.perform_bandpass(
122+
data[chan],
123+
BoardShim.get_sampling_rate(args.board_id),
124+
bp_centerFreq,
125+
bp_bandWidth,
126+
2,
127+
FilterTypes.BUTTERWORTH.value,
128+
0);
129+
"""
130+
131+
for i in range(len(data[0])):
132+
queue.put(data[:,i].tolist())
133+
elapsed_time = local_clock() - start_time
134+
required_samples = int(srate * elapsed_time) - sent_samples
135+
if required_samples > 0 and queue.qsize() >= required_samples:
136+
mychunk = []
137+
138+
for i in range(required_samples):
139+
mychunk.append(queue.get())
140+
stamp = local_clock() - fw_delay
141+
outlet.push_chunk(mychunk, stamp)
142+
sent_samples += required_samples
143+
time.sleep(1)
144+
145+
146+
if __name__ == "__main__":
147+
main()

Networking-Test-Kit/LSL/lslStreamTest.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,25 @@
1515

1616
def testLSLSamplingRate():
1717
start = time.time()
18-
numSamples = 0
18+
totalNumSamples = 0
19+
validSamples = 0
1920
numChunks = 0
2021

2122
while time.time() <= start + duration:
2223
# get chunks of samples
2324
samples, timestamp = inlet.pull_chunk()
24-
if timestamp:
25+
if samples:
2526
numChunks += 1
2627
print( len(samples) )
27-
numSamples += len(samples)
28+
totalNumSamples += len(samples)
2829
# print(samples);
30+
for sample in samples:
31+
print(sample)
32+
validSamples += 1
2933

30-
print( "Number of Chunks == {}".format(numChunks) )
31-
print( "Avg Sampling Rate == {}".format(numSamples / duration) )
34+
print( "Number of Chunks and Samples == {} , {}".format(numChunks, totalNumSamples) )
35+
print( "Valid Samples and Duration == {} / {}".format(validSamples, duration) )
36+
print( "Avg Sampling Rate == {}".format(validSamples / duration) )
3237

3338

3439
testLSLSamplingRate()
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Example program to show how to read a multi-channel time series from LSL."""
2+
import time
3+
from pylsl import StreamInlet, resolve_stream
4+
from time import sleep
5+
import numpy as np
6+
import matplotlib.pyplot as plt
7+
from matplotlib import style
8+
from collections import deque
9+
10+
# first resolve an EEG stream on the lab network
11+
print("looking for an EEG stream...")
12+
streams = resolve_stream('type', 'EXG')
13+
14+
# create a new inlet to read from the stream
15+
inlet = StreamInlet(streams[0])
16+
duration = 10
17+
18+
sleep(0)
19+
20+
def testLSLSamplingRate():
21+
start = time.time()
22+
numSamples = 0
23+
numChunks = 0
24+
25+
while time.time() <= start + duration:
26+
# get chunks of samples
27+
chunk, timestamp = inlet.pull_chunk()
28+
if timestamp:
29+
numChunks += 1
30+
for sample in chunk:
31+
numSamples += 1
32+
33+
print( "Number of Chunks == {}".format(numChunks) )
34+
print( "Avg Sampling Rate == {}".format(numSamples / duration) )
35+
36+
37+
# testLSLSamplingRate()
38+
39+
print("gathering data to plot...")
40+
41+
def testLSLPulseData():
42+
start = time.time()
43+
raw_pulse_signal = []
44+
45+
while time.time() <= start + duration:
46+
chunk, timestamp = inlet.pull_chunk()
47+
if timestamp:
48+
for sample in chunk:
49+
print(sample)
50+
raw_pulse_signal.append(sample[0])
51+
52+
# print(raw_pulse_signal)
53+
print( "Avg Sampling Rate == {}".format(len(raw_pulse_signal) / duration) )
54+
plt.plot(raw_pulse_signal)
55+
plt.ylabel('raw analog signal')
56+
plt.show()
57+
58+
testLSLPulseData()

0 commit comments

Comments
 (0)