├── EOG-in Python ├── bin │ ├── liblsl32.dll │ ├── liblsl32.dylib │ ├── liblsl64.dll │ ├── liblsl64.dylib │ └── liblsl64.so ├── lib │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ ├── filters.cpython-36.pyc │ │ ├── gui.cpython-36.pyc │ │ ├── main.cpython-36.pyc │ │ ├── open_bci_v3.cpython-36.pyc │ │ ├── plot.cpython-36.pyc │ │ ├── streamerlsl.cpython-36.pyc │ │ └── template_match.cpython-36.pyc │ ├── filters.py │ ├── gui.py │ ├── live_graph.py │ ├── main.py │ ├── open_bci_v3.py │ ├── streamerlsl.py │ └── template_match.py └── openbci_lsl.py ├── Intern-FInal-Report └── Report _Bharath_Kumar.doc ├── README.md ├── StreamRawData └── StreamRawData │ └── StreamRawData.ino ├── __pycache__ ├── main.cpython-36.pyc └── template_match.cpython-36.pyc ├── data ├── openBCI_raw_2018-07-02_15-46-30.txt └── openBCI_raw_2018-07-02_15-49-13.txt ├── data_stream.py ├── graph.py ├── images ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── ADS1299.png ├── GUI.jpg ├── bottom.jpg ├── electrode.jpg ├── factor.jpg ├── left.png ├── maze.jpg ├── me.jpeg ├── right.jpg └── top.jpg ├── main (copy).py ├── main.py ├── matlab_gui ├── center.png ├── convert.py ├── eog_gui.m ├── left.png ├── right.png └── white.png ├── maze.py ├── maze2.txt ├── plot.py ├── sampleText.txt ├── template_match.py └── values.txt /EOG-in Python/bin/liblsl32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/bin/liblsl32.dll -------------------------------------------------------------------------------- /EOG-in Python/bin/liblsl32.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/bin/liblsl32.dylib -------------------------------------------------------------------------------- /EOG-in Python/bin/liblsl64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/bin/liblsl64.dll -------------------------------------------------------------------------------- /EOG-in Python/bin/liblsl64.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/bin/liblsl64.dylib -------------------------------------------------------------------------------- /EOG-in Python/bin/liblsl64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/bin/liblsl64.so -------------------------------------------------------------------------------- /EOG-in Python/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/lib/__init__.py -------------------------------------------------------------------------------- /EOG-in Python/lib/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/lib/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /EOG-in Python/lib/__pycache__/filters.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/lib/__pycache__/filters.cpython-36.pyc -------------------------------------------------------------------------------- /EOG-in Python/lib/__pycache__/gui.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/lib/__pycache__/gui.cpython-36.pyc -------------------------------------------------------------------------------- /EOG-in Python/lib/__pycache__/main.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/lib/__pycache__/main.cpython-36.pyc -------------------------------------------------------------------------------- /EOG-in Python/lib/__pycache__/open_bci_v3.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/lib/__pycache__/open_bci_v3.cpython-36.pyc -------------------------------------------------------------------------------- /EOG-in Python/lib/__pycache__/plot.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/lib/__pycache__/plot.cpython-36.pyc -------------------------------------------------------------------------------- /EOG-in Python/lib/__pycache__/streamerlsl.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/lib/__pycache__/streamerlsl.cpython-36.pyc -------------------------------------------------------------------------------- /EOG-in Python/lib/__pycache__/template_match.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/EOG-in Python/lib/__pycache__/template_match.cpython-36.pyc -------------------------------------------------------------------------------- /EOG-in Python/lib/filters.py: -------------------------------------------------------------------------------- 1 | from scipy import signal 2 | import numpy as np 3 | 4 | class Filters: 5 | def __init__(self,window_size, low, high): 6 | fs_Hz = 250; 7 | fn = fs_Hz/2 8 | self.filtered_data = np.array((window_size,1)) 9 | 10 | 11 | ####################################### 12 | # Filter Creation 13 | # ------------------------------------- 14 | # 15 | # Create a filter using the scipy module, 16 | # based on specifications suggested by 17 | # Pan-Tompkins (bandpass from 5-15Hz) 18 | # 19 | # 20 | # 1) Establish constants: 21 | # a) filter_order = 2 22 | # b) high pass cutoff = 15Hz 23 | # c) low pass cutoff = 5Hz 24 | # 2) Calculate the coefficients, store in variables 25 | 26 | filter_order = 2 27 | f_high = high 28 | f_low = low 29 | self.high_pass_coefficients = signal.butter(filter_order,f_low/fn, 'high') 30 | self.low_pass_coefficients = signal.butter(filter_order,f_high/fn, 'low') 31 | 32 | 33 | ####################################### 34 | # Bandpass filter 35 | # ------------------------------------- 36 | # Filter the data, using a bandpass of 37 | # 5-15Hz. 38 | # 39 | # Input: 40 | # the data buffer from Data_Buffer class 41 | # Output: 42 | # filtered data as a numpy array 43 | 44 | def bandpass(self,data_buffer): 45 | high_passed = self.high_pass(data_buffer) 46 | low_passed = self.low_pass(high_passed) 47 | filtered_data = np.array(low_passed) 48 | return filtered_data 49 | 50 | def high_pass(self,data_buffer): 51 | [b1, a1] = [self.high_pass_coefficients[0],self.high_pass_coefficients[1]] 52 | high_passed = signal.filtfilt(b1,a1,data_buffer) 53 | return high_passed 54 | 55 | def low_pass(self,data_buffer): 56 | [b, a] = [self.low_pass_coefficients[0],self.low_pass_coefficients[1]] 57 | low_passed = signal.filtfilt(b,a,data_buffer) 58 | return low_passed 59 | -------------------------------------------------------------------------------- /EOG-in Python/lib/gui.py: -------------------------------------------------------------------------------- 1 | ''' 2 | gui.py 3 | ------ 4 | 5 | This module creates and controls GUI function, using the PyQt4 framework. Using the GUI, 6 | the user can manipulate LSL parameters and board configuration, as well as control the 7 | creation, start, and stop of the streams. 8 | 9 | 10 | ''' 11 | 12 | from collections import OrderedDict,deque 13 | import signal 14 | import lib.streamerlsl as streamerlsl 15 | import lib.open_bci_v3 as bci 16 | import pyqtgraph as pg 17 | import numpy as np 18 | import time 19 | import sys 20 | import lib.filters as filters 21 | np.set_printoptions(threshold=np.inf) 22 | 23 | 24 | try: 25 | from PyQt4 import QtGui,QtCore 26 | except ImportError: 27 | print("GUI unavailable: PyQt4 not installed. \n" + \ 28 | "Use command line interface: \n" + \ 29 | " python lsl_openbci.py [port] --stream") 30 | sys.exit(0) 31 | 32 | 33 | class GUI(QtGui.QWidget): 34 | def __init__(self): 35 | super(GUI, self).__init__() 36 | self.gui_setup() 37 | self.lsl = streamerlsl.StreamerLSL(GUI=True) 38 | signal.signal(signal.SIGINT, signal.SIG_DFL) 39 | 40 | 41 | def gui_setup(self): 42 | self.setWindowTitle("OpenBCI - Lab Streaming Layer") 43 | self.setFixedSize(515,460) 44 | self.find_defaults() 45 | self.set_layout() 46 | self.show() 47 | 48 | 49 | def set_layout(self,monitor=False): 50 | #Layout 51 | self.layout = QtGui.QGridLayout() 52 | 53 | #title font 54 | header_font = QtGui.QFont('default',weight=QtGui.QFont.Bold) 55 | header_font.setPointSize(16) 56 | title_font = QtGui.QFont('default',weight=QtGui.QFont.Bold) 57 | title_font.setUnderline(True) 58 | 59 | 60 | title = QtGui.QLabel("OpenBCI - Lab Streaming Layer") 61 | title.setFont(header_font) 62 | 63 | 64 | #Board Configuration 65 | board_configuration = QtGui.QPushButton("Board Config") 66 | board_configuration.setFixedWidth(120) 67 | board_configuration.clicked.connect(self.board_config) 68 | 69 | # Display Monitor 70 | self.display_monitor = QtGui.QPushButton("Show Monitor >") 71 | self.display_monitor.setFixedWidth(150) 72 | self.display_monitor.clicked.connect(self.show_monitor) 73 | 74 | 75 | #PORT 76 | port_label = QtGui.QLabel("Port") 77 | self.port_entry = QtGui.QLineEdit() 78 | self.port_entry.setFixedWidth(150) 79 | self.port_entry.setText(self.port) 80 | 81 | #DAISY 82 | daisy_label = QtGui.QLabel("Daisy (16 chan)") 83 | self.daisy_entry = QtGui.QComboBox() 84 | self.daisy_entry.addItem("Enabled") 85 | self.daisy_entry.addItem("Disabled") 86 | self.daisy_entry.setFixedWidth(100) 87 | if self.daisy: 88 | self.daisy_entry.setCurrentIndex(0) 89 | else: 90 | self.daisy_entry.setCurrentIndex(1) 91 | 92 | #lines and separators 93 | verticalLine0 = QtGui.QFrame() 94 | verticalLine0.setFrameShape(QtGui.QFrame().HLine) 95 | verticalLine0.setFrameShadow(QtGui.QFrame().Sunken) 96 | verticalLine1 = QtGui.QFrame() 97 | verticalLine1.setFrameShape(QtGui.QFrame().HLine) 98 | verticalLine1.setFrameShadow(QtGui.QFrame().Sunken) 99 | verticalLine2 = QtGui.QFrame() 100 | verticalLine2.setFrameShape(QtGui.QFrame().HLine) 101 | verticalLine2.setFrameShadow(QtGui.QFrame().Sunken) 102 | horizontalLine = QtGui.QFrame() 103 | horizontalLine.setFrameShape(QtGui.QFrame().VLine) 104 | horizontalLine.setFrameShadow(QtGui.QFrame().Sunken) 105 | 106 | 107 | #STREAM CONFIG 108 | stream_layout = QtGui.QGridLayout() 109 | title_font.setUnderline(True) 110 | #stream 1 111 | stream1_label = QtGui.QLabel("Stream 1") 112 | stream1_label.setFont(title_font) 113 | stream1_name_label = QtGui.QLabel("Name") 114 | # stream1_name_label.setFixedWidth(120) 115 | self.stream1_name_entry = QtGui.QLineEdit() 116 | self.stream1_name_entry.setText("OpenBCI_EEG") 117 | self.stream1_name_entry.setFixedWidth(125) 118 | 119 | 120 | stream1_type_label = QtGui.QLabel("Type") 121 | self.stream1_type_entry = QtGui.QLineEdit() 122 | self.stream1_type_entry.setText("EEG") 123 | self.stream1_type_entry.setFixedWidth(125) 124 | stream1_channels_label = QtGui.QLabel("# of Channels") 125 | self.stream1_channels_entry = QtGui.QLineEdit() 126 | self.stream1_channels_entry.setText(str(self.data_channels)) 127 | self.stream1_channels_entry.setFixedWidth(125) 128 | stream1_hz_label = QtGui.QLabel("Sample Rate") 129 | self.stream1_hz_entry = QtGui.QLineEdit() 130 | self.stream1_hz_entry.setText(str(self.sample_rate)) 131 | self.stream1_hz_entry.setFixedWidth(125) 132 | stream1_datatype_label = QtGui.QLabel("Data Type") 133 | self.stream1_datatype_entry = QtGui.QLineEdit() 134 | self.stream1_datatype_entry.setText("float32") 135 | self.stream1_datatype_entry.setFixedWidth(125) 136 | stream1_streamid_label = QtGui.QLabel("Stream ID") 137 | self.stream1_streamid_entry = QtGui.QLineEdit() 138 | self.stream1_streamid_entry.setText("openbci_eeg_id1") 139 | self.stream1_streamid_entry.setFixedWidth(125) 140 | #stream2 141 | stream2_label = QtGui.QLabel("Stream 2") 142 | stream2_label.setFont(title_font) 143 | stream2_name_label = QtGui.QLabel("Name") 144 | # stream2_name_label.setFixedWidth(120) 145 | 146 | self.stream2_name_entry = QtGui.QLineEdit() 147 | self.stream2_name_entry.setText("OpenBCI_AUX") 148 | self.stream2_name_entry.setFixedWidth(125) 149 | stream2_type_label = QtGui.QLabel("Type") 150 | self.stream2_type_entry = QtGui.QLineEdit() 151 | self.stream2_type_entry.setText("AUX") 152 | self.stream2_type_entry.setFixedWidth(125) 153 | stream2_channels_label = QtGui.QLabel("# of Channels") 154 | self.stream2_channels_entry = QtGui.QLineEdit() 155 | self.stream2_channels_entry.setText(str(self.aux_channels)) 156 | self.stream2_channels_entry.setFixedWidth(125) 157 | stream2_hz_label = QtGui.QLabel("Sample Rate") 158 | self.stream2_hz_entry = QtGui.QLineEdit() 159 | self.stream2_hz_entry.setText(str(self.sample_rate)) 160 | self.stream2_hz_entry.setFixedWidth(125) 161 | stream2_datatype_label = QtGui.QLabel("Data Type") 162 | self.stream2_datatype_entry = QtGui.QLineEdit() 163 | self.stream2_datatype_entry.setText("float32") 164 | self.stream2_datatype_entry.setFixedWidth(125) 165 | stream2_streamid_label = QtGui.QLabel("Stream ID") 166 | self.stream2_streamid_entry = QtGui.QLineEdit() 167 | self.stream2_streamid_entry.setText("openbci_aux_id1") 168 | self.stream2_streamid_entry.setFixedWidth(125) 169 | 170 | #Connect Board 171 | self.connect_button = QtGui.QPushButton("Connect Board") 172 | self.connect_button.clicked.connect(self.connect_board) 173 | self.connect_button.setFixedWidth(150) 174 | #Start Streaming 175 | self.start_button = QtGui.QPushButton("Start Streaming") 176 | self.start_button.clicked.connect(self.init_streaming) 177 | self.start_button.setEnabled(False) 178 | self.start_button.setFixedWidth(150) 179 | 180 | #Console 181 | self.console = QtGui.QLineEdit() 182 | self.console.setStyleSheet("font-family:Times;font-size:17px;color: rgb(255, 255, 255);background: rgb(0, 0, 0)") 183 | self.console.setReadOnly(True) 184 | self.consoleSizePolicy = QtGui.QSizePolicy() 185 | self.consoleSizePolicy.setVerticalPolicy(QtGui.QSizePolicy.Expanding) 186 | self.console.setSizePolicy(self.consoleSizePolicy) 187 | self.console.setFixedWidth(300) 188 | self.console.setText(" --") 189 | # self.console.setAlignment(QtCore.Qt.AlignRight) 190 | 191 | spacer = QtGui.QSpacerItem(20,40,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding) 192 | #set layout 193 | self.layout.addWidget(title,0,0,1,3) 194 | self.layout.addWidget(verticalLine0,1,0,1,4) 195 | self.layout.addWidget(port_label,2,0) 196 | self.layout.addWidget(self.port_entry,2,1,1,2) 197 | self.layout.addWidget(daisy_label,3,0) 198 | self.layout.addWidget(self.daisy_entry,3,1) 199 | self.layout.addWidget(board_configuration,4,0) 200 | self.layout.addWidget(self.display_monitor,4,2,1,1) 201 | 202 | #stream config area 203 | stream_widget = QtGui.QWidget() 204 | stream_layout.addWidget(horizontalLine,1,2,7,1) 205 | stream_layout.addWidget(stream1_label,0,0,1,2) 206 | stream_layout.addWidget(stream1_name_label,1,0) 207 | stream_layout.addWidget(self.stream1_name_entry,1,1) 208 | stream_layout.addWidget(stream1_type_label,2,0) 209 | stream_layout.addWidget(self.stream1_type_entry,2,1) 210 | stream_layout.addWidget(stream1_channels_label,3,0) 211 | stream_layout.addWidget(self.stream1_channels_entry,3,1) 212 | stream_layout.addWidget(stream1_hz_label,4,0) 213 | stream_layout.addWidget(self.stream1_hz_entry,4,1) 214 | stream_layout.addWidget(stream1_datatype_label,5,0) 215 | stream_layout.addWidget(self.stream1_datatype_entry,5,1) 216 | stream_layout.addWidget(stream1_streamid_label,6,0) 217 | stream_layout.addWidget(self.stream1_streamid_entry,6,1) 218 | stream_layout.addWidget(stream2_label,0,2,1,3) 219 | stream_layout.addWidget(stream2_name_label,1,3) 220 | stream_layout.addWidget(self.stream2_name_entry,1,4) 221 | stream_layout.addWidget(stream2_type_label,2,3) 222 | stream_layout.addWidget(self.stream2_type_entry,2,4) 223 | stream_layout.addWidget(stream2_channels_label,3,3) 224 | stream_layout.addWidget(self.stream2_channels_entry,3,4) 225 | stream_layout.addWidget(stream2_hz_label,4,3) 226 | stream_layout.addWidget(self.stream2_hz_entry,4,4) 227 | stream_layout.addWidget(stream2_datatype_label,5,3) 228 | stream_layout.addWidget(self.stream2_datatype_entry,5,4) 229 | stream_layout.addWidget(stream2_streamid_label,6,3) 230 | stream_layout.addWidget(self.stream2_streamid_entry,6,4) 231 | stream_widget.setLayout(stream_layout) 232 | stream_widget.setFixedWidth(500) 233 | self.layout.addWidget(verticalLine1,5,0,1,4) 234 | self.layout.addWidget(stream_widget,6,0,1,4) 235 | self.layout.addWidget(verticalLine2,7,0,1,4) 236 | self.layout.addWidget(self.connect_button,8,0,1,1) 237 | self.layout.addWidget(self.start_button,9,0,1,1) 238 | self.layout.addWidget(self.console,8,1,2,1) 239 | 240 | self.setLayout(self.layout) 241 | 242 | def show_monitor(self): 243 | self.smw = Stream_Monitor_Widget(parent=self) 244 | self.smw.setFixedWidth(985) 245 | self.layout.addWidget(self.smw,0,3,10,1000) 246 | self.setFixedSize(1500,460) 247 | self.display_monitor.setText("Hide Monitor <") 248 | self.display_monitor.clicked.disconnect(self.show_monitor) 249 | self.display_monitor.clicked.connect(self.hide_monitor) 250 | 251 | def hide_monitor(self): 252 | self.layout.removeWidget(self.smw) 253 | self.lsl.new_data.disconnect(self.smw.update_plot) 254 | self.setFixedSize(510,460) 255 | self.display_monitor.setText("Show Monitor >") 256 | self.display_monitor.clicked.disconnect(self.hide_monitor) 257 | self.display_monitor.clicked.connect(self.show_monitor) 258 | 259 | 260 | def find_defaults(self): 261 | try: 262 | board = bci.OpenBCIBoard(print_enable=False) 263 | self.port = board.port 264 | self.daisy = board.daisy 265 | self.data_channels = board.getNbEEGChannels() 266 | self.aux_channels = board.getNbAUXChannels() 267 | self.sample_rate = board.getSampleRate() 268 | board.disconnect() 269 | except Exception as e: 270 | print(str(e)) 271 | print("Using default configurations") 272 | self.port = "--" 273 | self.daisy = None 274 | self.data_channels = 8 275 | self.aux_channels = 3 276 | self.sample_rate = 250 277 | 278 | def connect_board(self): 279 | self.console.setText(" Searching for board...") 280 | 281 | # Get port 282 | port = self.port_entry.text() 283 | 284 | #Throw error if port is at not specified ('--') 285 | if port == "--": 286 | self.console.setText(" Error: No Port Specified") 287 | print("Error: No Port Specified") 288 | return 289 | #Get daisy setting 290 | if self.daisy_entry.currentIndex: 291 | daisy=False 292 | else: 293 | daisy=True 294 | try: 295 | self.lsl.initialize_board(port=port,daisy=daisy) 296 | except: 297 | self.lsl.board.disconnect() #close the serial 298 | self.console.setText(" Error connecting to the board") 299 | print("Error connecting to the board") 300 | return 301 | 302 | self.lsl.set_board_settings() 303 | self.start_button.setEnabled(True) 304 | self.console.setText(" Board connected. Ready to stream") 305 | self.connect_button.setText("Disconnect") 306 | self.connect_button.clicked.disconnect(self.connect_board) 307 | self.connect_button.clicked.connect(self.disconnect_board) 308 | 309 | def disconnect_board(self): 310 | self.lsl.board.disconnect() 311 | try: 312 | self.lsl.outlet_eeg.close_stream() 313 | self.lsl.outlet_eeg.close_stream() 314 | except: 315 | pass 316 | self.connect_button.setText("Connect") 317 | self.console.setText(" Board disconnected") 318 | self.connect_button.clicked.disconnect(self.disconnect_board) 319 | self.connect_button.clicked.connect(self.connect_board) 320 | if self.start_button.text() == "Stop Streaming": 321 | self.start_button.clicked.disconnect(self.stop_streaming) 322 | self.start_button.clicked.connect(self.start_streaming) 323 | elif self.start_button.text() == "Resume Streaming": 324 | self.start_button.clicked.disconnect(self.start_streaming) 325 | self.start_button.clicked.connect(self.init_streaming) 326 | # elif self.start_button.text() == "Start Streaming": 327 | # self.start_button.clicked.disconnect(self.init_streaming) 328 | 329 | self.start_button.setEnabled(False) 330 | self.start_button.setText("Start Streaming") 331 | 332 | 333 | def init_streaming(self): 334 | #create LSL stream 335 | try: 336 | stream1 = { 337 | 'name' : self.stream1_name_entry.text(), 338 | 'type' : self.stream1_type_entry.text(), 339 | 'channels' : int(self.stream1_channels_entry.text()), 340 | 'sample_rate' : float(self.stream1_hz_entry.text()), 341 | 'datatype' : self.stream1_datatype_entry.text(), 342 | 'id' : self.stream1_streamid_entry.text() 343 | } 344 | stream2 = { 345 | 'name' : self.stream2_name_entry.text(), 346 | 'type' : self.stream2_type_entry.text(), 347 | 'channels' : int(self.stream2_channels_entry.text()), 348 | 'sample_rate' : float(self.stream2_hz_entry.text()), 349 | 'datatype' : self.stream2_datatype_entry.text(), 350 | 'id' : self.stream2_streamid_entry.text() 351 | } 352 | except: 353 | self.console.setText(" LSL Error: check your inputs") 354 | print("LSL Error: check your inputs") 355 | return 356 | try: 357 | self.lsl.create_lsl(default=False,stream1=stream1,stream2=stream2) 358 | except: 359 | self.console.setText(" LSL Error: check your inputs") 360 | try: 361 | self.lsl.start_streaming() 362 | except: 363 | self.console.setText(" Streaming could not start. Check your board/dongle.") 364 | return 365 | else: 366 | self.console.setText(" Streaming data") 367 | self.start_button.setText("Stop Streaming") 368 | self.start_button.clicked.disconnect(self.init_streaming) 369 | self.start_button.clicked.connect(self.stop_streaming) 370 | 371 | def start_streaming(self): 372 | self.lsl.start_streaming() 373 | self.console.setText(" Streaming data.") 374 | self.start_button.setText("Stop Streaming") 375 | self.start_button.clicked.disconnect(self.start_streaming) 376 | self.start_button.clicked.connect(self.stop_streaming) 377 | 378 | def stop_streaming(self): 379 | self.console.setText(" Streaming paused.") 380 | self.lsl.stop_streaming() 381 | self.start_button.setText("Resume Streaming") 382 | self.start_button.clicked.disconnect(self.stop_streaming) 383 | self.start_button.clicked.connect(self.start_streaming) 384 | 385 | def board_config(self): 386 | self.config_widget = Board_Config_Widget(parent=self) 387 | self.config_widget.show() 388 | 389 | class Stream_Monitor_Widget(QtGui.QWidget): 390 | 391 | def __init__(self,parent=None): 392 | QtGui.QWidget.__init__(self) 393 | self.parent = parent 394 | self.curves = OrderedDict() 395 | self.data_buffer = OrderedDict() 396 | self.filtered_data = OrderedDict() 397 | self.create_plot() 398 | self.filters = filters.Filters(self.buffer_size,1,50) 399 | 400 | self.parent.lsl.new_data.connect(self.update_plot) 401 | 402 | 403 | 404 | def create_plot(self): 405 | 406 | self.stream_scroll = pg.PlotWidget(title='Stream Monitor') 407 | 408 | if not self.parent.daisy_entry.currentIndex(): 409 | self.channel_count = 16 410 | self.buffer_size = 1000 411 | samples = 125 412 | self.stream_scroll.setYRange(-.5,16,padding=.01) 413 | else: 414 | self.channel_count = 8 415 | samples = 250 416 | self.buffer_size = 2000 417 | self.stream_scroll.setYRange(-.5,8,padding=.01) 418 | 419 | self.stream_scroll_time_axis = np.linspace(-5,0,samples) 420 | self.stream_scroll.setXRange(-5,0, padding=.01) 421 | self.stream_scroll.setLabel('bottom','Time','Seconds') 422 | self.stream_scroll.setLabel('left','Channel') 423 | for i in range(self.channel_count-1,-1,-1): 424 | self.data_buffer['buffer_channel{}'.format(i+1)] = deque([0]*self.buffer_size) 425 | self.filtered_data['filtered_channel{}'.format(i+1)] = deque([0]*samples) 426 | self.curves['curve_channel{}'.format(i+1)] = self.stream_scroll.plot() 427 | self.curves['curve_channel{}'.format(i+1)].setData(x=self.stream_scroll_time_axis,y=([point+i+1 for point in self.filtered_data['filtered_channel{}'.format(i+1)]])) 428 | self.set_layout() 429 | 430 | def set_layout(self): 431 | self.layout = QtGui.QGridLayout() 432 | self.layout.addWidget(self.stream_scroll,0,0) 433 | self.setLayout(self.layout) 434 | 435 | @QtCore.pyqtSlot('PyQt_PyObject') 436 | def update_plot(self,data): 437 | for i in range(self.channel_count): 438 | self.data_buffer['buffer_channel{}'.format(i+1)].popleft() 439 | self.data_buffer['buffer_channel{}'.format(i+1)].append(data.channel_data[i]) 440 | 441 | current = self.data_buffer['buffer_channel{}'.format(i+1)] 442 | current = self.filters.high_pass(current) 443 | current = [((point/100 + (i+1))) for point in current] 444 | 445 | self.filtered_data['filtered_channel{}'.format(i+1)].popleft() 446 | self.filtered_data['filtered_channel{}'.format(i+1)].append(current[-1]) 447 | filtered = self.filtered_data['filtered_channel{}'.format(i+1)] 448 | self.curves['curve_channel{}'.format(i+1)].setData(x=self.stream_scroll_time_axis,y=([point for point in filtered])) 449 | 450 | class Board_Config_Widget(QtGui.QWidget): 451 | def __init__(self,parent=None): 452 | QtGui.QWidget.__init__(self) 453 | self.parent = parent 454 | self.lsl = parent.lsl 455 | self.setFixedSize(700,715) 456 | self.setWindowTitle("Board Configuration Window") 457 | self.set_layout() 458 | 459 | def set_layout(self): 460 | self.layout = QtGui.QGridLayout() 461 | self.channel_options_layout = QtGui.QGridLayout() 462 | 463 | verticalLine0 = QtGui.QFrame() 464 | verticalLine0.setFrameShape(QtGui.QFrame().HLine) 465 | verticalLine0.setFrameShadow(QtGui.QFrame().Sunken) 466 | verticalLine1 = QtGui.QFrame() 467 | verticalLine1.setFrameShape(QtGui.QFrame().HLine) 468 | verticalLine1.setFrameShadow(QtGui.QFrame().Sunken) 469 | verticalLine2 = QtGui.QFrame() 470 | verticalLine2.setFrameShape(QtGui.QFrame().HLine) 471 | verticalLine2.setFrameShadow(QtGui.QFrame().Sunken) 472 | #Title 473 | title = QtGui.QLabel("Board Settings") 474 | title_font = QtGui.QFont('default',weight=QtGui.QFont.Bold) 475 | title_font.setPointSize(12) 476 | title.setFont(title_font) 477 | 478 | 479 | number_of_chans = int(self.parent.stream1_channels_entry.text()) 480 | 481 | if self.parent.daisy_entry.currentIndex() == 0: 482 | daisy = True 483 | else: 484 | daisy = False 485 | #Channel Number 486 | if daisy: 487 | channel_number = QtGui.QLabel("Number of Channels: {} [Daisy {}]".format("16", "Enabled")) 488 | else: 489 | channel_number = QtGui.QLabel("Number of Channels: {} [Daisy {}]".format("8", "Disabled")) 490 | 491 | 492 | #SD Card Options 493 | sd_label = QtGui.QLabel("SD Card Record:") 494 | self.sd_entry = QtGui.QComboBox() 495 | self.sd_entry.addItem("None") 496 | self.sd_entry.addItem("5 MIN") 497 | self.sd_entry.addItem("15 MIN") 498 | self.sd_entry.addItem("30 MIN") 499 | self.sd_entry.addItem("1 HR") 500 | self.sd_entry.addItem("2 HR") 501 | self.sd_entry.addItem("4 HR") 502 | self.sd_entry.addItem("12 HR") 503 | self.sd_entry.addItem("24 HR") 504 | self.sd_entry.addItem("14 SEC") 505 | 506 | #Save Button 507 | save_button = QtGui.QPushButton("Save Settings") 508 | save_button.clicked.connect(self.save_settings) 509 | 510 | #Set rest of the layout 511 | self.layout.addWidget(title,0,0) 512 | self.layout.addWidget(verticalLine0,1,0,1,4) 513 | self.layout.addWidget(channel_number,2,0) 514 | self.layout.addWidget(verticalLine2,3,0,1,4) 515 | self.set_channel_options_layout() 516 | self.layout.addLayout(self.channel_options_layout,4,0,1,4) 517 | self.layout.addWidget(sd_label,5,0) 518 | self.layout.addWidget(self.sd_entry,5,1) 519 | self.layout.addWidget(verticalLine1,6,0,1,4) 520 | self.layout.addWidget(save_button,7,0,) 521 | self.setLayout(self.layout) 522 | 523 | def set_channel_options_layout(self): 524 | option_headers = [] 525 | #Channel options 526 | self.channels = OrderedDict() 527 | self.channel_attributes = ['channel_label{}','power_entry{}','gain{}','input{}','bias{}','srb2{}','srb1{}'] 528 | self.NUM_ATTRIBUTES = len(self.channel_attributes) 529 | 530 | #header font 531 | header_font = QtGui.QFont('default',weight=QtGui.QFont.Bold) 532 | header_font.setPointSize(8) 533 | 534 | #Option Headers 535 | channel_number = QtGui.QLabel("CHANNEL") 536 | option_headers.append(channel_number) 537 | channel_power = QtGui.QLabel("POWER") 538 | option_headers.append(channel_power) 539 | channel_gain = QtGui.QLabel("GAIN") 540 | option_headers.append(channel_gain) 541 | channel_input = QtGui.QLabel("INPUT") 542 | option_headers.append(channel_input) 543 | channel_bias = QtGui.QLabel("BIAS") 544 | option_headers.append(channel_bias) 545 | channel_srb2 = QtGui.QLabel("SRB2") 546 | option_headers.append(channel_srb2) 547 | channel_srb1 = QtGui.QLabel("SRB1") 548 | option_headers.append(channel_srb1) 549 | 550 | 551 | # Iteratively add options for all channels. 552 | # There is a dictionary to hold all of the channels, 553 | # and then each channel has its own subdictionary to 554 | # hold all of the attributes. This method fills all of those 555 | # things out. 556 | for i in range(16): 557 | current = "channel{}".format(i+1) 558 | self.channels[current] = OrderedDict() 559 | for j,attribute in enumerate(self.channel_attributes): 560 | self.channels[current][attribute.format(i+1)] = '' 561 | current_attribute = attribute.format(i+1) 562 | if j == 0: 563 | self.channels[current][current_attribute] = QtGui.QLabel("Channel {}".format(i+1)) 564 | elif j == 1: 565 | self.channels[current][current_attribute] = QtGui.QComboBox() 566 | self.channels[current][current_attribute].addItem("On") 567 | self.channels[current][current_attribute].addItem("Off") 568 | index = int((self.lsl.current_settings[current][j+1]).decode()) 569 | self.channels[current][current_attribute].setCurrentIndex(index) 570 | elif j == 2: 571 | self.channels[current][current_attribute] = QtGui.QComboBox() 572 | self.channels[current][current_attribute].addItem("0") 573 | self.channels[current][current_attribute].addItem("2") 574 | self.channels[current][current_attribute].addItem("4") 575 | self.channels[current][current_attribute].addItem("6") 576 | self.channels[current][current_attribute].addItem("8") 577 | self.channels[current][current_attribute].addItem("12") 578 | self.channels[current][current_attribute].addItem("24") 579 | index = int((self.lsl.current_settings[current][j+1]).decode()) 580 | self.channels[current][current_attribute].setCurrentIndex(index) 581 | elif j == 3: 582 | self.channels[current][current_attribute] = QtGui.QComboBox() 583 | self.channels[current][current_attribute].addItem("Normal") 584 | self.channels[current][current_attribute].addItem("Shorted") 585 | self.channels[current][current_attribute].addItem("Bias Meas") 586 | self.channels[current][current_attribute].addItem("MVDD") 587 | self.channels[current][current_attribute].addItem("Temp") 588 | self.channels[current][current_attribute].addItem("Test Sig") 589 | self.channels[current][current_attribute].addItem("Bias DRP") 590 | self.channels[current][current_attribute].addItem("Bias DRN") 591 | index = int((self.lsl.current_settings[current][j+1]).decode()) 592 | self.channels[current][current_attribute].setCurrentIndex(index) 593 | elif j == 4: 594 | self.channels[current][current_attribute] = QtGui.QComboBox() 595 | self.channels[current][current_attribute].addItem("Include") 596 | self.channels[current][current_attribute].addItem("Don't Include") 597 | index = 1 - int((self.lsl.current_settings[current][j+1]).decode()) 598 | self.channels[current][current_attribute].setCurrentIndex(index) 599 | elif j == 5: 600 | self.channels[current][current_attribute] = QtGui.QComboBox() 601 | self.channels[current][current_attribute].addItem("Connect") 602 | self.channels[current][current_attribute].addItem("Disconnect") 603 | index = 1 - int((self.lsl.current_settings[current][j+1]).decode()) 604 | self.channels[current][current_attribute].setCurrentIndex(index) 605 | elif j == 6: 606 | self.channels[current][current_attribute] = QtGui.QComboBox() 607 | self.channels[current][current_attribute].addItem("Disconnect") 608 | self.channels[current][current_attribute].addItem("Connect") 609 | index = int((self.lsl.current_settings[current][j+1]).decode()) 610 | self.channels[current][current_attribute].setCurrentIndex(index) 611 | 612 | 613 | # Set channel options layout 614 | # Set the headers 615 | for header in option_headers: 616 | header.setFont(header_font) 617 | for i,header in enumerate(option_headers): 618 | self.channel_options_layout.addWidget(header,2,i) 619 | # Set the options 620 | for i,ch in enumerate(self.channels): 621 | for j,attribute in enumerate(self.channels[ch]): 622 | if self.parent.daisy_entry.currentIndex() == 0: 623 | current = self.channels[ch][attribute] 624 | self.channel_options_layout.addWidget(current,i+3,j) 625 | self.setFixedSize(700,715) 626 | 627 | else: 628 | if i < 8: 629 | current = self.channels[ch][attribute] 630 | self.channel_options_layout.addWidget(current,i+3,j) 631 | else: 632 | current = self.channels[ch][attribute] 633 | current.hide() 634 | self.channel_options_layout.addWidget(current,i+3,j) 635 | self.setFixedSize(700,450) 636 | 637 | 638 | def channel_number_select(self): 639 | print(self.parent.daisy_entry.currentIndex()) 640 | 641 | def save_settings(self): 642 | 643 | self.settings = OrderedDict() 644 | sd_commands = [b" ", b'A', b'S', b'F',b'G',b'H',b'J',b'K',b'L'] 645 | 646 | #Channel number 647 | if self.parent.daisy_entry.currentIndex() == 1: 648 | self.settings['Number_Channels'] = [b'c'] 649 | channel_number = 8 650 | elif self.parent.daisy_entry.currentIndex() == 0: 651 | self.settings['Number_Channels'] = [b'C'] 652 | channel_number = 16 653 | 654 | 655 | # for i in range(16): 656 | # current = "channel{}".format(i+1) 657 | # self.settings[current] = [] 658 | # self.settings[current].append(b'x') 659 | # self.settings[current].append(str.encode(str(i+1))) 660 | # for j,attribute in enumerate(self.channel_attributes): 661 | # temp = self.channels[current][attribute.format(i+1)] 662 | # if j == 1: 663 | # self.settings[current].append(str(temp.currentIndex()).encode()) 664 | # elif j == 2: 665 | # self.settings[current].append(str(temp.currentIndex()).encode()) 666 | # elif j == 3: 667 | # self.settings[current].append(str(temp.currentIndex()).encode()) 668 | # elif j == 4: 669 | # self.settings[current].append(str(1-temp.currentIndex()).encode()) 670 | # elif j == 5: 671 | # self.settings[current].append(str(1-temp.currentIndex()).encode()) 672 | # elif j == 6: 673 | # self.settings[current].append(str(temp.currentIndex()).encode()) 674 | # self.settings[current].append(b'X') 675 | board = bci.OpenBCIBoard() 676 | for i in range(8): 677 | current = "channel{}".format(i+1) 678 | self.settings[current] = [] 679 | self.settings[current].append(b'x') 680 | self.settings[current].append(str.encode(str(i+1))) 681 | 682 | for j,attribute in enumerate(self.channel_attributes): 683 | temp = self.channels[current][attribute.format(i+1)] 684 | if j == 1: 685 | self.settings[current].append(str(temp.currentIndex()).encode()) 686 | elif j == 2: 687 | self.settings[current].append(str(temp.currentIndex()).encode()) 688 | elif j == 3: 689 | self.settings[current].append(str(temp.currentIndex()).encode()) 690 | elif j == 4: 691 | self.settings[current].append(str(1-temp.currentIndex()).encode()) 692 | elif j == 5: 693 | self.settings[current].append(str(1-temp.currentIndex()).encode()) 694 | elif j == 6: 695 | self.settings[current].append(str(temp.currentIndex()).encode()) 696 | self.settings[current].append(b'X') 697 | 698 | print(int(self.settings[current][2])) 699 | if int(self.settings[current][2]) == 1: 700 | #print("off1") 701 | board.set_channel((i+1),0) 702 | elif int(self.settings[current][2]) == 0: 703 | #print("on1") 704 | board.set_channel((i+1),1) 705 | 706 | sd_index = self.sd_entry.currentIndex() 707 | self.settings["SD_Card"] = [sd_commands[sd_index]] 708 | #print(self.settings["channel1"][2]) 709 | 710 | for item in self.settings: 711 | if self.settings[item] != self.parent.lsl.current_settings[item]: 712 | self.parent.lsl.current_settings[item] = self.settings[item] 713 | self.close() 714 | -------------------------------------------------------------------------------- /EOG-in Python/lib/live_graph.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import matplotlib.animation as animation 3 | import time 4 | 5 | import serial 6 | import keyboard 7 | 8 | class plot: 9 | 10 | def __init__(): 11 | fig = plt.figure() 12 | ax1 = self.fig.add_subplot(1,1,1) 13 | x = '0' 14 | y = '0' 15 | iterator = 0 16 | xar = [] 17 | yar = [] 18 | 19 | def animate(i, x_value, y_value): 20 | 21 | global xar, yar, iterator, x, y 22 | 23 | if(iterator>500): 24 | xar.pop(0) 25 | yar.pop(0) 26 | 27 | filedata = open("values.txt","a+") 28 | x = str(round(float(x) + 1,3)) 29 | y = str(round(float(value),3)) 30 | filedata.write(x+","+y+"\n") 31 | filedata.close() 32 | 33 | xar.append(int(iterator)) 34 | yar.append(float(value)) 35 | 36 | ax1.clear() 37 | ax1.plot(xar,yar) 38 | iterator = iterator + 1 39 | 40 | def start(x_value, y_value): 41 | ani = animation.FuncAnimation(fig, animate(x_value, y_value), interval=1) 42 | plt.show() 43 | -------------------------------------------------------------------------------- /EOG-in Python/lib/main.py: -------------------------------------------------------------------------------- 1 | import lib.template_match as tm 2 | import scipy 3 | 4 | prev_amplitude = 0 5 | 6 | class classify: 7 | 8 | def __init__(self): 9 | self.vertical_prev_amplitude = 0 10 | self.horizontal_prev_amplitude = 0 11 | self.left_flag = 0 12 | self.right_flag = 0 13 | self.up_flag = 0 14 | self.down_flag = 0 15 | self.status = "centre" 16 | self.iterator = 0 17 | self.vertical_signal_stack = [] 18 | self.horizontal_signal_stack = [] 19 | self.cross_corr_tresh = 0.9 20 | 21 | # ASSIGN TEMPLATES 22 | 23 | self.vertical_signal_stack = [] 24 | self.horizontal_signal_stack = [] 25 | self.left_template = 0 26 | self.right_template = 0 27 | self.blink_template = 0 28 | self.up_template = 0 29 | self.down_template = 0 30 | 31 | def treshold(self, vertical_amplitude, horizontal_amplitude): 32 | self.upper_thresh_vertical = 50 33 | self.lower_thresh_vertical = -50 34 | self.upper_thresh_horizontal = 50 35 | self.lower_thresh_horizontal = -50 36 | self.difference_tresh = 30 37 | 38 | self.vertical_amplitude = float(vertical_amplitude) 39 | self.horizontal_amplitude = float(horizontal_amplitude) 40 | self.vertical_prev_amplitude = self.vertical_prev_amplitude 41 | self.horizontal_prev_amplitude = self.horizontal_prev_amplitude 42 | #print(self.horizontal_prev_amplitude) 43 | self.vertical_difference = self.vertical_amplitude - self.vertical_prev_amplitude 44 | self.horizontal_difference = self.horizontal_amplitude - self.horizontal_prev_amplitude 45 | self.horizontal_prev_amplitude = self.horizontal_amplitude 46 | self.vertical_prev_amplitude = self.vertical_amplitude 47 | #print(self.horizontal_difference, self.horizontal_amplitude, self.horizontal_prev_amplitude) 48 | 49 | if( self.vertical_amplitude > self.upper_thresh_vertical and self.up_flag is 0 ): 50 | self.up_flag = 1 51 | 52 | if( self.vertical_amplitude < self.lower_thresh_vertical and self.down_flag is 0 ): 53 | self.down_flag = 1 54 | 55 | if( self.horizontal_amplitude > self.upper_thresh_horizontal ): 56 | self.right_flag = 1 57 | 58 | elif( self.horizontal_amplitude < self.lower_thresh_horizontal ): 59 | self.left_flag = 1 60 | 61 | else: 62 | self.left_flag = 0 63 | self.right_flag = 0 64 | 65 | #if( self.horizontal_amplitude > self.upper_thresh_horizontal and self.left_flag is 1): 66 | # self.left_flag = 0 67 | 68 | #if( self.horizontal_amplitude < self.lower_thresh_horizontal and self.right_flag is 1): 69 | # self.right_flag = 0 70 | 71 | #if(self.vertical_difference > abs(self.difference_tresh)): 72 | # self.up_flag = 0 73 | # self.down_flag = 0 74 | # self.right_flag = 0 75 | # self.left_flag = 0 76 | # self.status = "center" 77 | 78 | if self.left_flag is 1: 79 | self.status = "left" 80 | elif self.right_flag is 1: 81 | self.status = "right" 82 | elif self.up_flag is 1: 83 | self.status = "up" 84 | elif self.down_flag is 1: 85 | self.status = "down" 86 | else: 87 | self.status = "center" 88 | 89 | return self.status 90 | 91 | def template_match_image(self, frame): 92 | result = tm.match(frame) 93 | return result 94 | 95 | def template_match(self, vertical_amplitude, horizontal_amplitude, template_size): 96 | self.vertical_amplitude = vertical_amplitude 97 | self.horizontal_amplitude = horizontal_amplitude 98 | self.vertical_signal_stack = self.vertical_signal_stack.append(vertical_amplitude) 99 | self.horizontal_signal_stack = self.horizontal_signal_stack.append(horizontal_amplitude) 100 | 101 | if self.iterator > template_size: 102 | self.vertical_signal_stack.pop(0) 103 | self.horizontal_signal_stack.pop(0) 104 | 105 | self.left_corr = scipy.stats.pearsonr(self.horizontal_signal_stack, self.left_template) 106 | self.right_corr = scipy.stats.pearsonr(self.horizontal_signal_stack, self.right_template) 107 | self.up_corr = scipy.stats.pearsonr(self.vertical_signal_stack, self.up_template) 108 | self.down_corr = scipy.stats.pearsonr(self.vertical_signal_stack, self.down_template) 109 | self.blink_corr = scipy.stats.pearsonr(self.vertical_signal_stack, self.blink_template) 110 | 111 | if self.left_corr[0] > 0.9: 112 | self.status = "left" 113 | if self.right_corr[0] > 0.9: 114 | self.status = "right" 115 | if self.up_corr[0] > 0.9: 116 | self.status = "up" 117 | if self.down_corr[0] > 0.9: 118 | self.status = "down" 119 | if self.blink_corr[0] > 0.9: 120 | self.status = "blink" 121 | 122 | return self.status 123 | 124 | if __name__ == "__main__": 125 | classify.treshold(50) 126 | 127 | -------------------------------------------------------------------------------- /EOG-in Python/lib/open_bci_v3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core OpenBCI object for handling connections and samples from the board. 3 | 4 | EXAMPLE USE: 5 | 6 | def handle_sample(sample): 7 | print(sample.channels) 8 | 9 | board = OpenBCIBoard() 10 | board.print_register_settings() 11 | board.start(handle_sample) 12 | 13 | NOTE: If daisy modules is enabled, the callback will occur every two samples, hence "packet_id" will only contain even numbers. As a side effect, the sampling rate will be divided by 2. 14 | 15 | FIXME: at the moment we can just force daisy mode, do not check that the module is detected. 16 | 17 | 18 | """ 19 | import serial 20 | import struct 21 | import numpy as np 22 | import time 23 | import timeit 24 | import atexit 25 | import logging 26 | import threading 27 | import sys 28 | import glob 29 | import serial.tools.list_ports 30 | import lib.main as main 31 | 32 | 33 | SAMPLE_RATE = 250.0 # Hz 34 | START_BYTE = 0xA0 # start of data packet 35 | END_BYTE = 0xC0 # end of data packet 36 | ADS1299_Vref = 4.5 #reference voltage for ADC in ADS1299. set by its hardware 37 | ADS1299_gain = 24.0 #assumed gain setting for ADS1299. set by its Arduino code 38 | scale_fac_uVolts_per_count = ADS1299_Vref/float((pow(2,23)-1))/ADS1299_gain*1000000. 39 | scale_fac_accel_G_per_count = 0.002 /(pow(2,4)) #assume set to +/4G, so 2 mG 40 | signal_data_buffer = [] 41 | ''' 42 | #Commands for in SDK http://docs.openbci.com/software/01-Open BCI_SDK: 43 | 44 | command_stop = "s"; 45 | command_startText = "x"; 46 | command_startBinary = "b"; 47 | command_startBinary_wAux = "n"; 48 | command_startBinary_4chan = "v"; 49 | command_activateFilters = "F"; 50 | command_deactivateFilters = "g"; 51 | command_deactivate_channel = {"1", "2", "3", "4", "5", "6", "7", "8"}; 52 | command_activate_channel = {"q", "w", "e", "r", "t", "y", "u", "i"}; 53 | command_activate_leadoffP_channel = {"!", "@", "#", "$", "%", "^", "&", "*"}; //shift + 1-8 54 | command_deactivate_leadoffP_channel = {"Q", "W", "E", "R", "T", "Y", "U", "I"}; //letters (plus shift) right below 1-8 55 | command_activate_leadoffN_channel = {"A", "S", "D", "F", "G", "H", "J", "K"}; //letters (plus shift) below the letters below 1-8 56 | command_deactivate_leadoffN_channel = {"Z", "X", "C", "V", "B", "N", "M", "<"}; //letters (plus shift) below the letters below the letters below 1-8 57 | command_biasAuto = "`"; 58 | command_biasFixed = "~"; 59 | ''' 60 | 61 | class OpenBCIBoard(object): 62 | """ 63 | 64 | Handle a connection to an OpenBCI board. 65 | 66 | Args: 67 | port: The port to connect to. 68 | baud: The baud of the serial connection. 69 | daisy: Enable or disable daisy module and 16 chans readings 70 | """ 71 | 72 | def __init__(self, port=None, baud=115200, filter_data=True, 73 | scaled_output=True, daisy=None, log=True, timeout=None, print_enable=True): 74 | self.log = log # print_incoming_text needs log 75 | self.streaming = False 76 | self.baudrate = baud 77 | self.timeout = timeout 78 | self.daisy = daisy 79 | self.iterator = 0 80 | self.classify = main.classify() 81 | if not port: 82 | port = self.find_port() 83 | self.port = port 84 | if print_enable: 85 | print("Connecting to V3 at port %s" %(port)) 86 | self.ser = serial.Serial(port= port, baudrate = baud, timeout=timeout) 87 | if print_enable: 88 | print("Serial established...") 89 | else: 90 | print("Detecting board settings...") 91 | time.sleep(2) 92 | #Initialize 32-bit board, doesn't affect 8bit board 93 | 94 | self.ser.write(b'v'); 95 | #wait for device to be ready 96 | time.sleep(1) 97 | 98 | self.print_incoming_text(print_enable) #print board identification text. Daisy detection occurs in this method. 99 | self.streaming = False 100 | self.filtering_data = filter_data 101 | self.scaling_output = scaled_output 102 | self.eeg_channels_per_sample = 8 # number of EEG channels per sample *from the board* 103 | #self.aux_channels_per_sample = 3 # number of AUX channels per sample *from the board* 104 | self.aux_channels_per_sample = 0 # number of AUX channels per sample *from the board* 105 | self.read_state = 0 106 | if self.daisy is None: 107 | self.daisy = False 108 | 109 | self.last_odd_sample = OpenBCISample(-1, [], []) # used for daisy 110 | 111 | self.log_packet_count = 0 112 | self.attempt_reconnect = False 113 | self.last_reconnect = 0 114 | self.reconnect_freq = 5 115 | self.packets_dropped = 0 116 | 117 | #Disconnects from board when terminated 118 | atexit.register(self.disconnect) 119 | 120 | def getSampleRate(self): 121 | if self.daisy: 122 | return SAMPLE_RATE/2 123 | else: 124 | return SAMPLE_RATE 125 | 126 | def getNbEEGChannels(self): 127 | if self.daisy: 128 | return self.eeg_channels_per_sample*2 129 | else: 130 | return self.eeg_channels_per_sample 131 | 132 | def getNbAUXChannels(self): 133 | return self.aux_channels_per_sample 134 | 135 | def run(self): 136 | self.start_streaming() 137 | 138 | def start_streaming(self,callback,lapse=-1): 139 | """ 140 | Start handling streaming data from the board. Call a provided callback 141 | for every single sample that is processed (every two samples with daisy module). 142 | 143 | Args: 144 | callback: A callback function -- or a list of functions -- that will receive a single argument of the 145 | OpenBCISample object captured. 146 | """ 147 | #print("DEACTIVATED") 148 | 149 | #self.ser.write(b'1') 150 | #self.ser.write(b'2') 151 | #self.ser.write(b'3') 152 | #self.ser.write(b'4') 153 | #self.ser.write(b'5') 154 | #self.ser.write(b'6') 155 | #self.ser.write(b'7') 156 | #self.ser.write(b'8') 157 | 158 | if not self.streaming: 159 | self.ser.write(b'b') 160 | self.streaming = True 161 | 162 | start_time = timeit.default_timer() 163 | 164 | if not isinstance(callback, list): 165 | callback = [callback] 166 | 167 | #Initialize check connection 168 | self.check_connection() 169 | 170 | while self.streaming: 171 | 172 | # read current sample 173 | sample = self._read_serial_binary() 174 | #print(sample) 175 | # if a daisy module is attached, wait to concatenate two samples (main board + daisy) before passing it to callback 176 | if self.daisy: 177 | # odd sample: daisy sample, save for later 178 | if ~sample.id % 2: 179 | self.last_odd_sample = sample 180 | # even sample: concatenate and send if last sample was the fist part, otherwise drop the packet 181 | elif sample.id - 1 == self.last_odd_sample.id: 182 | # the aux data will be the average between the two samples, as the channel samples themselves have been averaged by the board 183 | #avg_aux_data = list((np.array(sample.aux_data) + np.array(self.last_odd_sample.aux_data))/2) 184 | avg_aux_data = [] 185 | #whole_sample = OpenBCISample(sample.id, sample.channel_data + self.last_odd_sample.channel_data, avg_aux_data) 186 | whole_sample = OpenBCISample(sample.id, sample.channel_data + self.last_odd_sample.channel_data, avg_aux_data) 187 | for call in callback: 188 | call(whole_sample) 189 | else: 190 | for call in callback: 191 | call(sample) 192 | 193 | 194 | if(lapse > 0 and timeit.default_timer() - start_time > lapse): 195 | self.stop(); 196 | if self.log: 197 | self.log_packet_count = self.log_packet_count + 1; 198 | 199 | 200 | """ 201 | PARSER: 202 | Parses incoming data packet into OpenBCISample. 203 | Incoming Packet Structure: 204 | Start Byte(1)|Sample ID(1)|Channel Data(24)|Aux Data(6)|End Byte(1) 205 | 0xA0|0-255|8, 3-byte signed ints|3 2-byte signed ints|0xC0 206 | 207 | """ 208 | def _read_serial_binary(self, max_bytes_to_skip=3000): 209 | 210 | global signal_data_buffer 211 | 212 | #def interpretToInt32(array): 213 | # return (((255 & array[3]) << 24)|((255 & array[2]) << 16)|((255 & array[1]) << 8)|((255 & array[0]) << 0)) 214 | 215 | def interpretToInt32(array): 216 | if array[3] > 127: 217 | #real_value = ~((((255 & array[3]) << 24)|((255 & array[2]) << 16)|((255 & array[1]) << 8)|((255 & array[0]) << 0))-1) 218 | real_value = -bswitch((((255 & array[2]) << 16)|((255 & array[1]) << 8)|((255 & array[0]) << 0))-1) 219 | else: 220 | real_value = (((255 & array[3]) << 24)|((255 & array[2]) << 16)|((255 & array[1]) << 8)|((255 & array[0]) << 0)) 221 | return real_value 222 | 223 | def bswitch(num): 224 | c = 1 225 | 226 | while num*2 > c: 227 | num = num ^ c 228 | c = c << 1 229 | 230 | return num 231 | 232 | def read(n): 233 | b = self.ser.read(n) 234 | #print("in") 235 | if not b: 236 | self.warn('Device appears to be stalled. Quitting...') 237 | sys.exit() 238 | raise Exception('Device Stalled') 239 | sys.exit() 240 | return '\xFF' 241 | else: 242 | return b 243 | # 244 | # FOR 3 BYTE PER CHANNEL DATA TRANSMISSION 245 | # 246 | #print("in the loop") 247 | # for rep in range(max_bytes_to_skip): 248 | 249 | # #---------Start Byte & ID--------- 250 | # if self.read_state == 0: 251 | 252 | # b = read(1) 253 | # if struct.unpack('B', b)[0] == START_BYTE: 254 | # if(rep != 0): 255 | # # self.warn('Skipped %d bytes before start found' %(rep)) 256 | # rep = 0; 257 | # packet_id = struct.unpack('B', read(1))[0] #packet id goes from 0-255 258 | # log_bytes_in = str(packet_id); 259 | 260 | # self.read_state = 1 261 | 262 | # #---------Channel Data--------- 263 | # elif self.read_state == 1: 264 | # channel_data = [] 265 | # for c in range(self.eeg_channels_per_sample): 266 | 267 | # #3 byte ints 268 | # literal_read = read(3) 269 | 270 | # unpacked = struct.unpack('3B', literal_read) 271 | # log_bytes_in = log_bytes_in + '|' + str(literal_read); 272 | 273 | # #3byte int in 2s compliment 274 | # if (unpacked[0] >= 127): 275 | # pre_fix = bytes(bytearray.fromhex('FF')) 276 | # else: 277 | # pre_fix = bytes(bytearray.fromhex('00')) 278 | 279 | 280 | # literal_read = pre_fix + literal_read; 281 | 282 | # #unpack little endian(>) signed integer(i) (makes unpacking platform independent) 283 | # myInt = struct.unpack('>i', literal_read)[0] 284 | 285 | # if self.scaling_output: 286 | # channel_data.append(myInt*scale_fac_uVolts_per_count) 287 | # else: 288 | # channel_data.append(myInt) 289 | 290 | # self.read_state = 2; 291 | 292 | # #---------Accelerometer Data--------- 293 | # elif self.read_state == 2: 294 | # aux_data = [] 295 | # for a in range(self.aux_channels_per_sample): 296 | 297 | # #short = h 298 | # acc = struct.unpack('>h', read(2))[0] 299 | # log_bytes_in = log_bytes_in + '|' + str(acc); 300 | 301 | # if self.scaling_output: 302 | # aux_data.append(acc) 303 | # else: 304 | # aux_data.append(acc) 305 | 306 | # self.read_state = 3; 307 | # #---------End Byte--------- 308 | # elif self.read_state == 3: 309 | # val = struct.unpack('B', read(1))[0] 310 | # log_bytes_in = log_bytes_in + '|' + str(val); 311 | # self.read_state = 0 #read next packet 312 | # if (val == END_BYTE): 313 | # sample = OpenBCISample(packet_id, channel_data, aux_data) 314 | # self.packets_dropped = 0 315 | # return sample 316 | # else: 317 | # self.warn("ID:<%d> instead of <%s>" 318 | # %(packet_id, val, END_BYTE)) 319 | # logging.debug(log_bytes_in); 320 | # self.packets_dropped = self.packets_dropped + 1 321 | #print("loop enter") 322 | 323 | # FOR 4 BYTE PER CHANNEL DATA TRANSMISSION 324 | 325 | for rep in range(max_bytes_to_skip): 326 | #print(str(read(1))+" byte") 327 | #---------Start Byte & ID--------- 328 | if self.read_state == 0: 329 | 330 | b = read(1) 331 | #print(b) 332 | if struct.unpack('B', b)[0] == START_BYTE: 333 | if(rep != 0): 334 | # self.warn('Skipped %d bytes before start found' %(rep)) 335 | rep = 0; 336 | packet_size = struct.unpack('B', read(1))[0] #packet id goes from 0-255 337 | log_bytes_in = str(packet_size); 338 | 339 | packet_id = struct.unpack('4B', read(4))[0] #packet id goes from 0-255 340 | log_bytes_in = str(packet_id); 341 | 342 | self.read_state = 1 343 | 344 | #---------Channel Data--------- 345 | elif self.read_state == 1: 346 | channel_data = [] 347 | for c in range(self.eeg_channels_per_sample): 348 | 349 | #3 byte ints 350 | literal_read = read(4) 351 | 352 | unpacked = struct.unpack('4B', literal_read) 353 | log_bytes_in = log_bytes_in + '|' + str(literal_read); 354 | signal_data_buffer.append(unpacked) 355 | 356 | #3byte int in 2s compliment 357 | #if (unpacked[0] >= 127): 358 | # pre_fix = bytes(bytearray.fromhex('FF')) 359 | #else: 360 | # pre_fix = bytes(bytearray.fromhex('00')) 361 | 362 | 363 | #literal_read = pre_fix + literal_read; 364 | 365 | #unpack little endian(>) signed integer(i) (makes unpacking platform independent) 366 | #myInt = struct.unpack('>i', literal_read)[0] 367 | 368 | #if self.scaling_output: 369 | # channel_data.append(myInt*scale_fac_uVolts_per_count) 370 | #else: 371 | # channel_data.append(myInt) 372 | 373 | self.read_state = 2; 374 | 375 | #---------Accelerometer Data--------- 376 | elif self.read_state == 2: 377 | aux_data = [] 378 | for a in range(self.aux_channels_per_sample): 379 | 380 | #short = h 381 | acc = struct.unpack('>h', read(2))[0] 382 | log_bytes_in = log_bytes_in + '|' + str(acc); 383 | 384 | if self.scaling_output: 385 | aux_data.append(acc) 386 | else: 387 | aux_data.append(acc) 388 | 389 | self.read_state = 3; 390 | #---------End Byte--------- 391 | elif self.read_state == 3: 392 | val = struct.unpack('B', read(1))[0] 393 | #val = read(1) 394 | #scaling_factor = (float(4.5) / float(24) / pow(2, 24)) * float(1000000) * float(2) 395 | scaling_factor = (float(4.5) / float(24) / pow(2, 24)) * float(1000000) 396 | 397 | final_value = [(float(interpretToInt32(element))*scaling_factor) for element in signal_data_buffer] 398 | 399 | #final_value = [hex(interpretToInt32(element)) for element in signal_data_buffer] 400 | for channel in range(len(final_value)): 401 | #if channel > 3 or channel < 3 : 402 | if channel > 7 : 403 | final_value[channel] = 0 404 | channel_data = final_value 405 | 406 | 407 | signal_data_buffer = [] 408 | #print("success") 409 | log_bytes_in = log_bytes_in + '|' + str(val); 410 | #print(log_bytes_in) 411 | self.read_state = 0 #read next packet 412 | if (val == END_BYTE): 413 | #print(channel_data) 414 | print(self.classify.treshold(0,channel_data[0])) 415 | sample = OpenBCISample(packet_id, channel_data, aux_data) 416 | self.packets_dropped = 0 417 | return sample 418 | else: 419 | self.warn("ID:<%d> instead of <%s>" 420 | %(packet_id, val, END_BYTE)) 421 | logging.debug(log_bytes_in); 422 | self.packets_dropped = self.packets_dropped + 1 423 | 424 | """ 425 | 426 | Clean Up (atexit) 427 | 428 | """ 429 | def stop(self): 430 | self.streaming = False 431 | self.ser.write(b's') 432 | 433 | def disconnect(self): 434 | if(self.streaming == True): 435 | self.stop() 436 | if (self.ser.isOpen()): 437 | self.ser.close() 438 | 439 | 440 | """ 441 | 442 | SETTINGS AND HELPERS 443 | 444 | """ 445 | def warn(self, text): 446 | if self.log: 447 | #log how many packets where sent succesfully in between warnings 448 | if self.log_packet_count: 449 | logging.info('Data packets received:'+str(self.log_packet_count)) 450 | self.log_packet_count = 0; 451 | logging.warning(text) 452 | print("Warning: %s" % text) 453 | 454 | 455 | def print_incoming_text(self,print_enable): 456 | """ 457 | 458 | When starting the connection, print all the debug data until 459 | we get to a line with the end sequence '$$$'. 460 | 461 | """ 462 | line = '' 463 | #Wait for device to send data 464 | time.sleep(1) 465 | #print(self.ser.inWaiting()) 466 | if self.ser.inWaiting() is not 0: 467 | line = '' 468 | c = '' 469 | #Look for end sequence $$$ 470 | while '...' not in line: 471 | c = self.ser.read().decode('utf-8', errors='replace') 472 | line += c 473 | if "On Daisy" in line: 474 | self.daisy = True 475 | if print_enable: 476 | print(line); 477 | #print("----------------------------------------------------------------------") 478 | # else: 479 | # self.warn("No Message") 480 | 481 | def openbci_id(self, serial): 482 | """ 483 | 484 | When automatically detecting port, parse the serial return for the "OpenBCI" ID. 485 | Also auto-detects the daisy. 486 | 487 | """ 488 | board = False 489 | line = '' 490 | #Wait for device to send data 491 | time.sleep(2) 492 | 493 | if serial.inWaiting(): 494 | line = '' 495 | c = '' 496 | #Look for end sequence $$$ 497 | while '...' not in line: 498 | c = serial.read().decode('utf-8', errors='replace') 499 | line += c 500 | if "OpenBCI" in line: 501 | return True 502 | return False 503 | 504 | def print_register_settings(self): 505 | self.ser.write(b'?') 506 | time.sleep(0.5) 507 | self.print_incoming_text(True); 508 | #DEBBUGING: Prints individual incoming bytes 509 | def print_bytes_in(self): 510 | if not self.streaming: 511 | self.ser.write(b'b') 512 | self.streaming = True 513 | while self.streaming: 514 | print(struct.unpack('B',self.ser.read())[0]); 515 | 516 | '''Incoming Packet Structure: 517 | Start Byte(1)|Sample ID(1)|Channel Data(24)|Aux Data(6)|End Byte(1) 518 | 0xA0|0-255|8, 3-byte signed ints|3 2-byte signed ints|0xC0''' 519 | 520 | def print_packets_in(self): 521 | while self.streaming: 522 | b = struct.unpack('B', self.ser.read())[0]; 523 | 524 | if b == START_BYTE: 525 | self.attempt_reconnect = False 526 | if skipped_str: 527 | logging.debug('SKIPPED\n' + skipped_str + '\nSKIPPED') 528 | skipped_str = '' 529 | 530 | packet_str = "%03d"%(b) + '|'; 531 | b = struct.unpack('B', self.ser.read())[0]; 532 | packet_str = packet_str + "%03d"%(b) + '|'; 533 | 534 | #data channels 535 | for i in range(24-1): 536 | b = struct.unpack('B', self.ser.read())[0]; 537 | packet_str = packet_str + '.' + "%03d"%(b); 538 | 539 | b = struct.unpack('B', self.ser.read())[0]; 540 | packet_str = packet_str + '.' + "%03d"%(b) + '|'; 541 | 542 | #aux channels 543 | #for i in range(6-1): 544 | # b = struct.unpack('B', self.ser.read())[0]; 545 | # packet_str = packet_str + '.' + "%03d"%(b); 546 | 547 | 548 | #b = struct.unpack('B', self.ser.read())[0]; 549 | #packet_str = packet_str + '.' + "%03d"%(b) + '|'; 550 | 551 | #end byte 552 | b = struct.unpack('B', self.ser.read())[0]; 553 | 554 | #Valid Packet 555 | if b == END_BYTE: 556 | packet_str = packet_str + '.' + "%03d"%(b) + '|VAL'; 557 | print(packet_str) 558 | #logging.debug(packet_str) 559 | 560 | #Invalid Packet 561 | else: 562 | packet_str = packet_str + '.' + "%03d"%(b) + '|INV'; 563 | #Reset 564 | self.attempt_reconnect = True 565 | 566 | 567 | else: 568 | print(b) 569 | if b == END_BYTE: 570 | skipped_str = skipped_str + '|END|' 571 | else: 572 | skipped_str = skipped_str + "%03d"%(b) + '.' 573 | 574 | if self.attempt_reconnect and (timeit.default_timer()-self.last_reconnect) > self.reconnect_freq: 575 | self.last_reconnect = timeit.default_timer() 576 | self.warn('Reconnecting') 577 | self.reconnect() 578 | 579 | 580 | 581 | def check_connection(self, interval = 2, max_packets_to_skip=10): 582 | # check number of dropped packages and establish connection problem if too large 583 | if self.packets_dropped > max_packets_to_skip: 584 | #if error, attempt to reconect 585 | self.reconnect() 586 | # check again again in 2 seconds 587 | threading.Timer(interval, self.check_connection).start() 588 | pass 589 | 590 | def reconnect(self): 591 | self.packets_dropped = 0 592 | self.warn('Reconnecting') 593 | self.stop() 594 | time.sleep(0.5) 595 | self.ser.write(b'v') 596 | time.sleep(0.5) 597 | self.ser.write(b'b') 598 | time.sleep(0.5) 599 | self.streaming = True 600 | #self.attempt_reconnect = False 601 | 602 | 603 | #Adds a filter at 60hz to cancel out ambient electrical noise 604 | def enable_filters(self): 605 | self.ser.write(b'f') 606 | self.filtering_data = True; 607 | 608 | def disable_filters(self): 609 | self.ser.write(b'g') 610 | self.filtering_data = False; 611 | 612 | def test_signal(self, signal): 613 | if signal == 0: 614 | self.ser.write(b'0') 615 | self.warn("Connecting all pins to ground") 616 | elif signal == 1: 617 | self.ser.write(b'p') 618 | self.warn("Connecting all pins to Vcc") 619 | elif signal == 2: 620 | self.ser.write(b'-') 621 | self.warn("Connecting pins to low frequency 1x amp signal") 622 | elif signal == 3: 623 | self.ser.write(b'=') 624 | self.warn("Connecting pins to high frequency 1x amp signal") 625 | elif signal == 4: 626 | self.ser.write(b'[') 627 | self.warn("Connecting pins to low frequency 2x amp signal") 628 | elif signal == 5: 629 | self.ser.write(b']') 630 | self.warn("Connecting pins to high frequency 2x amp signal") 631 | else: 632 | self.warn("%s is not a known test signal. Valid signals go from 0-5" %(signal)) 633 | 634 | def set_channel(self, channel, toggle_position): 635 | #Commands to set toggle to on position 636 | # if toggle_position == 1: 637 | # if channel is 1: 638 | # self.ser.write(b'!') 639 | # if channel is 2: 640 | # self.ser.write(b'@') 641 | # if channel is 3: 642 | # self.ser.write(b'#') 643 | # if channel is 4: 644 | # self.ser.write(b'$') 645 | # if channel is 5: 646 | # self.ser.write(b'%') 647 | # if channel is 6: 648 | # self.ser.write(b'^') 649 | # if channel is 7: 650 | # self.ser.write(b'&') 651 | # if channel is 8: 652 | # self.ser.write(b'*') 653 | # if channel is 9 and self.daisy: 654 | # self.ser.write(b'Q') 655 | # if channel is 10 and self.daisy: 656 | # self.ser.write(b'W') 657 | # if channel is 11 and self.daisy: 658 | # self.ser.write(b'E') 659 | # if channel is 12 and self.daisy: 660 | # self.ser.write(b'R') 661 | # if channel is 13 and self.daisy: 662 | # self.ser.write(b'T') 663 | # if channel is 14 and self.daisy: 664 | # self.ser.write(b'Y') 665 | # if channel is 15 and self.daisy: 666 | # self.ser.write(b'U') 667 | # if channel is 16 and self.daisy: 668 | # self.ser.write(b'I') 669 | # #Commands to set toggle to off position 670 | # elif toggle_position == 0: 671 | # if channel is 1: 672 | # self.ser.write(b'1') 673 | # if channel is 2: 674 | # self.ser.write(b'2') 675 | # if channel is 3: 676 | # self.ser.write(b'3') 677 | # if channel is 4: 678 | # self.ser.write(b'4') 679 | # if channel is 5: 680 | # self.ser.write(b'5') 681 | # if channel is 6: 682 | # self.ser.write(b'6') 683 | # if channel is 7: 684 | # self.ser.write(b'7') 685 | # if channel is 8: 686 | # self.ser.write(b'8') 687 | # if channel is 9 and self.daisy: 688 | # self.ser.write(b'q') 689 | # if channel is 10 and self.daisy: 690 | # self.ser.write(b'w') 691 | # if channel is 11 and self.daisy: 692 | # self.ser.write(b'e') 693 | # if channel is 12 and self.daisy: 694 | # self.ser.write(b'r') 695 | # if channel is 13 and self.daisy: 696 | # self.ser.write(b't') 697 | # if channel is 14 and self.daisy: 698 | # self.ser.write(b'y') 699 | # if channel is 15 and self.daisy: 700 | # self.ser.write(b'u') 701 | # if channel is 16 and self.daisy: 702 | # self.ser.write(b'i') 703 | 704 | if toggle_position == 1: 705 | if channel is 1: 706 | print("on") 707 | self.ser.write(b'q') 708 | if channel is 2: 709 | self.ser.write(b'w') 710 | if channel is 3: 711 | self.ser.write(b'e') 712 | if channel is 4: 713 | self.ser.write(b'r') 714 | if channel is 5: 715 | self.ser.write(b't') 716 | if channel is 6: 717 | self.ser.write(b'y') 718 | if channel is 7: 719 | self.ser.write(b'u') 720 | if channel is 8: 721 | self.ser.write(b'i') 722 | 723 | #Commands to set toggle to off position 724 | elif toggle_position == 0: 725 | if channel is 1: 726 | print("off") 727 | self.ser.write(b'1') 728 | if channel is 2: 729 | self.ser.write(b'2') 730 | if channel is 3: 731 | self.ser.write(b'3') 732 | if channel is 4: 733 | self.ser.write(b'4') 734 | if channel is 5: 735 | self.ser.write(b'5') 736 | if channel is 6: 737 | self.ser.write(b'6') 738 | if channel is 7: 739 | self.ser.write(b'7') 740 | if channel is 8: 741 | self.ser.write(b'8') 742 | 743 | 744 | def find_port(self): 745 | try: 746 | temp_port_list = serial.tools.list_ports.comports() 747 | except OSError: 748 | raise OSError('Serial port not found! Try entering your port manually.') 749 | ports = [i[0] for i in temp_port_list][::-1] 750 | 751 | openbci_port = '' 752 | for port in ports: 753 | try: 754 | s = serial.Serial(port= port, baudrate = self.baudrate, timeout=self.timeout) 755 | s.write(b'v') 756 | openbci_serial = self.openbci_id(s) 757 | s.close() 758 | if openbci_serial: 759 | openbci_port = port; 760 | except (OSError, serial.SerialException): 761 | pass 762 | if openbci_port == '': 763 | raise OSError('Cannot find OpenBCI port') 764 | else: 765 | return openbci_port 766 | 767 | class OpenBCISample(object): 768 | """Object encapulsating a single sample from the OpenBCI board.""" 769 | def __init__(self, packet_id, channel_data, aux_data): 770 | self.id = packet_id; 771 | self.channel_data = channel_data; 772 | self.aux_data = aux_data; 773 | 774 | 775 | -------------------------------------------------------------------------------- /EOG-in Python/lib/streamerlsl.py: -------------------------------------------------------------------------------- 1 | ''' 2 | streamerlsl.py 3 | --------------- 4 | 5 | This is the module that handles the creation and function of LSL using OpenBCI data. 6 | 7 | If the GUI application is used, the GUI controls the parameters of the stream, and calls 8 | the functions of this class to create, run, and stop each stream instance. 9 | 10 | If the command line application is used, this module creates the LSL instances 11 | using default parameters, and then allows the user interaction with the stream via the CLI. 12 | 13 | 14 | ''' 15 | 16 | import threading 17 | import signal 18 | from collections import OrderedDict 19 | import time 20 | import lib.open_bci_v3 as bci 21 | from pylsl import StreamInfo, StreamOutlet 22 | import sys 23 | import random 24 | 25 | GUI = True 26 | try: 27 | from PyQt4.QtCore import pyqtSignal,pyqtSlot,QThread 28 | except: 29 | GUI = False 30 | 31 | 32 | class StreamerLSL(QThread if GUI == True else object): 33 | try: 34 | from PyQt4.QtCore import pyqtSignal,pyqtSlot,QThread 35 | new_data = pyqtSignal(object) 36 | except: 37 | pass 38 | 39 | def __init__(self,port=None,GUI=False): 40 | self.default_settings = OrderedDict() 41 | self.current_settings = OrderedDict() 42 | #print(port) 43 | self.GUI = GUI 44 | if not self.GUI: 45 | if port is None: 46 | self.initialize_board(autodetect=True) 47 | else: 48 | self.initialize_board(port=port) 49 | else: 50 | QThread.__init__(self) 51 | self.count=0 52 | 53 | self.init_board_settings() 54 | 55 | 56 | 57 | def initialize_board(self,autodetect=False,port=None,daisy=None): 58 | print ("\n-------INSTANTIATING BOARD-------") 59 | 60 | if autodetect: 61 | self.board = bci.OpenBCIBoard() 62 | else: 63 | self.board = bci.OpenBCIBoard(port=port) 64 | self.eeg_channels = self.board.getNbEEGChannels() 65 | self.aux_channels = self.board.getNbAUXChannels() 66 | self.sample_rate = self.board.getSampleRate() 67 | 68 | def init_board_settings(self): 69 | #set default board configuration 70 | 71 | #default to 16 channels initially 72 | #self.default_settings["Number_Channels"] = [b'C'] 73 | self.default_settings["Number_Channels"] = [b'8'] 74 | #for i in range(16): 75 | for i in range(16): 76 | current = "channel{}".format(i+1) 77 | self.default_settings[current] = [] 78 | self.default_settings[current].append(b'x') 79 | self.default_settings[current].append(str(i+1).encode()) 80 | self.default_settings[current].append(b'0') 81 | self.default_settings[current].append(b'6') 82 | self.default_settings[current].append(b'0') 83 | self.default_settings[current].append(b'1') 84 | self.default_settings[current].append(b'1') 85 | self.default_settings[current].append(b'0') 86 | self.default_settings[current].append(b'X') 87 | self.default_settings["SD_Card"] = b" " 88 | self.current_settings = self.default_settings.copy() 89 | 90 | def set_board_settings(self): 91 | for item in self.current_settings: 92 | if self.current_settings[item] != self.default_settings[item]: 93 | for byte in self.current_settings[item]: 94 | self.board.ser.write(byte) 95 | time.sleep(.2) 96 | 97 | def send(self,sample): 98 | 99 | if self.GUI: 100 | self.count+=1 101 | if self.count % 5 == 0: 102 | self.new_data.emit(sample) 103 | #try: 104 | # self.outlet_eeg.push_sample(sample.channel_data) 105 | # self.outlet_aux.push_sample(sample.aux_data) 106 | #except: 107 | # print("Error! Check LSL settings") 108 | 109 | def create_lsl(self,default=True,stream1=None,stream2=None): 110 | if default: 111 | random_id = random.randint(0,255) 112 | # default parameters 113 | eeg_name = 'openbci_eeg' 114 | eeg_type = 'EEG' 115 | eeg_chan = self.eeg_channels 116 | eeg_hz = self.sample_rate 117 | eeg_data = 'float32' 118 | eeg_id = 'openbci_eeg_id' + str(random_id) 119 | aux_name = 'openbci_aux' 120 | aux_type = 'AUX' 121 | aux_chan = self.aux_channels 122 | aux_hz = self.sample_rate 123 | aux_data = 'float32' 124 | aux_id = 'openbci_aux_id' + str(random_id) 125 | #create StreamInfo 126 | self.info_eeg = StreamInfo(eeg_name,eeg_type,eeg_chan,eeg_hz,eeg_data,eeg_id) 127 | self.info_aux = StreamInfo(aux_name,aux_type,aux_chan,aux_hz,aux_data,aux_id) 128 | else: 129 | #user input parameters 130 | eeg_name = stream1['name'] 131 | eeg_type = stream1['type'] 132 | eeg_chan = stream1['channels'] 133 | eeg_hz = stream1['sample_rate'] 134 | eeg_data = stream1['datatype'] 135 | eeg_id = stream1['id'] 136 | aux_name = stream2['name'] 137 | aux_type = stream2['type'] 138 | aux_chan = stream2['channels'] 139 | aux_hz = stream2['sample_rate'] 140 | aux_data = stream2['datatype'] 141 | aux_id = stream2['id'] 142 | #create StreamInfo 143 | self.info_eeg = StreamInfo(eeg_name,eeg_type,eeg_chan,eeg_hz,eeg_data,eeg_id) 144 | self.info_aux = StreamInfo(aux_name,aux_type,aux_chan,aux_hz,aux_data,aux_id) 145 | 146 | 147 | #channel locations 148 | chns = self.info_eeg.desc().append_child('channels') 149 | if self.eeg_channels == 16: 150 | labels = ['Fp1','Fp2', 'C3','C4','T5','T6','O1','O2','F7','F8','F3','F4','T3','T4','P3','P4'] 151 | else: 152 | labels = ['Fp1','Fp2', 'C3','C4','T5','T6','O1','O2'] 153 | for label in labels: 154 | ch = chns.append_child("channel") 155 | ch.append_child_value('label', label) 156 | ch.append_child_value('unit','microvolts') 157 | ch.append_child_value('type','EEG') 158 | 159 | #additional Meta Data 160 | self.info_eeg.desc().append_child_value('manufacturer','OpenBCI Inc.') 161 | self.info_aux.desc().append_child_value('manufacturer','OpenBCI Inc.') 162 | 163 | #create StreamOutlet 164 | self.outlet_eeg = StreamOutlet(self.info_eeg) 165 | self.outlet_aux = StreamOutlet(self.info_aux) 166 | 167 | print ("--------------------------------------\n"+ \ 168 | "LSL Configuration: \n" + \ 169 | " Stream 1: \n" + \ 170 | " Name: " + eeg_name + " \n" + \ 171 | " Type: " + eeg_type + " \n" + \ 172 | " Channel Count: " + str(eeg_chan) + "\n" + \ 173 | " Sampling Rate: " + str(eeg_hz) + "\n" + \ 174 | " Channel Format: "+ eeg_data + " \n" + \ 175 | " Source Id: " + eeg_id + " \n" + \ 176 | " Stream 2: \n" + \ 177 | " Name: " + aux_name + " \n" + \ 178 | " Type: "+ aux_type + " \n" + \ 179 | " Channel Count: " + str(aux_chan) + "\n" + \ 180 | " Sampling Rate: " + str(aux_hz) + "\n" + \ 181 | " Channel Format: " + aux_data +" \n" + \ 182 | " Source Id: " + aux_id + " \n\n" + \ 183 | "Electrode Location Montage:\n" + \ 184 | str(labels) + "\n" + \ 185 | "---------------------------------------\n") 186 | 187 | def cleanUp(): 188 | board.disconnect() 189 | print ("Disconnecting...") 190 | atexit.register(cleanUp) 191 | 192 | def start_streaming(self): 193 | boardThread = threading.Thread(target=self.board.start_streaming,args=(self.send,-1)) 194 | boardThread.daemon = True # will stop on exit 195 | boardThread.start() 196 | print("Current streaming: {} EEG channels and {} AUX channels at {} Hz\n".format(self.eeg_channels, self.aux_channels,self.sample_rate)) 197 | def stop_streaming(self): 198 | self.board.stop() 199 | 200 | #clean up any leftover bytes from serial port 201 | # self.board.ser.reset_input_buffer() 202 | time.sleep(.1) 203 | line = '' 204 | while self.board.ser.inWaiting(): 205 | #print("doing this thing") 206 | c = self.board.ser.read().decode('utf-8', errors='replace') 207 | line += c 208 | time.sleep(0.001) 209 | if (c == '\n'): 210 | line = '' 211 | print("Streaming paused.\n") 212 | 213 | def begin(self): 214 | print ("--------------INFO---------------") 215 | print ( 216 | "Commands: \n" + \ 217 | " Type \"/start\" to stream to LSL \n" + \ 218 | " Type \"/stop\" to stop stream.\n" + \ 219 | " Type \"/exit\" to disconnect the board. \n" + \ 220 | "Advanced command map available at http://docs.openbci.com") 221 | 222 | print("\n-------------BEGIN---------------") 223 | # Init board state 224 | # s: stop board streaming; v: soft reset of the 32-bit board (no effect with 8bit board) 225 | s = 'sv' 226 | # Tell the board to enable or not daisy module 227 | if self.board.daisy: 228 | s = s + 'C' 229 | else: 230 | s = s + 'c' 231 | # d: Channels settings back to default 232 | s = s + 'd' 233 | 234 | while(s != "/exit"): 235 | #print("in loop") 236 | # Send char and wait for registers to set 237 | 238 | if sys.hexversion > 0x03000000: 239 | s = input('--> ') 240 | else: 241 | s = raw_input('--> ') 242 | 243 | if (not s): 244 | pass 245 | elif("help" in s): 246 | print ("View command map at:" + \ 247 | "http://docs.openbci.com/software/01-OpenBCI_SDK.\n" +\ 248 | "For user interface: read README or view" + \ 249 | "https://github.com/OpenBCI/OpenBCI_Python") 250 | 251 | elif self.board.streaming and s != "/stop": 252 | print ("Error: the board is currently streaming data, please type '/stop' before issuing new commands.") 253 | else: 254 | # read silently incoming packet if set (used when stream is stopped) 255 | flush = False 256 | #print("in loop") 257 | if('/' == s[0]): 258 | s = s[1:] 259 | rec = False # current command is recognized or fot 260 | 261 | if("T:" in s): 262 | lapse = int(s[string.find(s, "T:")+2:]) 263 | rec = True 264 | elif("t:" in s): 265 | lapse = int(s[string.find(s, "t:")+2:]) 266 | rec = True 267 | else: 268 | lapse = -1 269 | 270 | if("start" in s): 271 | #print("in") 272 | # start streaming in a separate thread so we could always send commands in here 273 | boardThread = threading.Thread(target=self.board.start_streaming,args=(self.send,-1)) 274 | boardThread.daemon = True # will stop on exit 275 | #print("I am in") 276 | try: 277 | boardThread.start() 278 | print("Streaming data...") 279 | except: 280 | print("not streaming data...") 281 | raise 282 | rec = True 283 | elif('test' in s): 284 | test = int(s[s.find("test")+4:]) 285 | self.board.test_signal(test) 286 | rec = True 287 | elif('stop' in s): 288 | self.board.stop() 289 | rec = True 290 | flush = True 291 | elif('loc' in s): 292 | self.change_locations(s[4:]) 293 | rec = True 294 | flush = True 295 | if rec == False: 296 | print("Command not recognized...") 297 | 298 | elif s: 299 | for c in s: 300 | if sys.hexversion > 0x03000000: 301 | self.board.ser.write(bytes(c, 'utf-8')) 302 | else: 303 | self.board.ser.write(bytes(c)) 304 | time.sleep(0.100) 305 | 306 | line = '' 307 | time.sleep(0.1) #Wait to see if the board has anything to report 308 | 309 | # DEACTIVATE CHANNELS 310 | 311 | print("DEACTIVATED") 312 | 313 | #self.board.ser.write(b'1') 314 | #self.board.ser.write(b'2') 315 | #self.board.ser.write(b'3') 316 | #self.ser.write(b'4') 317 | #self.board.ser.write(b'5') 318 | #self.board.ser.write(b'6') 319 | #self.board.ser.write(b'7') 320 | #self.board.ser.write(b'8') 321 | 322 | while self.board.ser.inWaiting(): 323 | pass 324 | #c = str(self.board.ser.read()) 325 | 326 | #c = self.board.ser.read().decode('utf-8', errors='replace') 327 | #line += c 328 | #time.sleep(0.001) 329 | #if (c == '\n') and not flush: 330 | # print('%\t'+line[:-1]) 331 | # line = '' 332 | #print(c) 333 | #print("stream_end") 334 | #if not flush: 335 | # print(line) 336 | #print("-----------------------------------------------------------------------------------") 337 | 338 | # Take user input 339 | #s = input('--> ') 340 | #if sys.hexversion > 0x03000000: 341 | # s = input('--> ') 342 | #else: 343 | # s = raw_input('--> ') 344 | 345 | def change_locations(self,locs): 346 | new_locs = [loc for loc in locs.split(',')] 347 | 348 | chns = self.info_eeg.desc().child('channels') 349 | ch = chns.child("channel") 350 | for label in new_locs: 351 | ch.set_child_value('label', label) 352 | ch = ch.next_sibling() 353 | 354 | 355 | print("New Channel Montage:") 356 | print(str(new_locs)) 357 | 358 | 359 | #create StreamOutlet 360 | self.outlet_eeg = StreamOutlet(self.info_eeg) 361 | self.outlet_aux = StreamOutlet(self.info_aux) 362 | -------------------------------------------------------------------------------- /EOG-in Python/lib/template_match.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | 4 | def match(img_rgb): 5 | 6 | img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY) 7 | 8 | left = cv2.imread('kohli.jpeg', 0) 9 | #template = cv2.resize(template,(70,70)) 10 | w, h = left.shape[::-1] 11 | right = cv2.imread('kohli.jpeg', 0) 12 | up = cv2.imread('kohli.jpeg', 0) 13 | down = cv2.imread('kohli.jpeg', 0) 14 | blink = cv2.imread('kohli.jpeg', 0) 15 | 16 | res_left = cv2.matchTemplate(img_gray,left,cv2.TM_CCOEFF_NORMED) #TM_SQDIFF-Best Match///TM_CCOEFF_NORMED-Threshold Match 17 | res_right = cv2.matchTemplate(img_gray,right,cv2.TM_CCOEFF_NORMED) #TM_SQDIFF-Best Match///TM_CCOEFF_NORMED-Threshold Match 18 | res_up = cv2.matchTemplate(img_gray,up,cv2.TM_CCOEFF_NORMED) #TM_SQDIFF-Best Match///TM_CCOEFF_NORMED-Threshold Match 19 | res_down = cv2.matchTemplate(img_gray,down,cv2.TM_CCOEFF_NORMED) #TM_SQDIFF-Best Match///TM_CCOEFF_NORMED-Threshold Match 20 | res_blink = cv2.matchTemplate(img_gray,blink,cv2.TM_CCOEFF_NORMED) #TM_SQDIFF-Best Match///TM_CCOEFF_NORMED-Threshold Match 21 | 22 | left_threshold = 0.8 23 | right_threshold = 0.8 24 | up_threshold = 0.8 25 | down_threshold = 0.8 26 | blink_threshold = 0.8 27 | 28 | loc_left = np.where( res_left >= left_threshold) 29 | loc_right = np.where( res_right >= right_threshold) 30 | loc_up = np.where( res_up >= up_threshold) 31 | loc_down = np.where( res_down >= down_threshold) 32 | loc_blink = np.where( res_blink >= blink_threshold) 33 | 34 | for pt in zip(*loc[::-1]): 35 | cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 1) 36 | for pt in zip(*loc[::-1]): 37 | cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (255,0,0), 1) 38 | for pt in zip(*loc[::-1]): 39 | cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,255,0), 1) 40 | for pt in zip(*loc[::-1]): 41 | cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,255,255), 1) 42 | for pt in zip(*loc[::-1]): 43 | cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (255,255,0), 1) 44 | 45 | return img_rgb 46 | 47 | if __name__ == "__main__": 48 | match(cv2.imread('crowd.jpg')) 49 | -------------------------------------------------------------------------------- /EOG-in Python/openbci_lsl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | ''' 3 | openbci_lsl.py 4 | --------------- 5 | 6 | This is the main module for establishing an OpenBCI stream through the Lab Streaming Layer (LSL). 7 | 8 | Lab streaming layer is a networking system for real time streaming, recording, and analysis of time-series 9 | data. LSL can be used to connect OpenBCI to applications that can record, analyze, and manipulate the data, 10 | such as Matlab, NeuroPype, BCILAB, and others. 11 | 12 | To run the program as a GUI application, enter `python openbci_lsl.py`. 13 | 14 | To run the program as a command line interface, enter `python openbci_lsl.py [port] --stream`. The port argument 15 | is optional, since the program automatically detects the OpenBCI port. However, if this functionality fails, the 16 | port must be specified as an argument. 17 | 18 | For more information, see the readme on the Github repo: 19 | 20 | https://github.com/gabrielibagon/OpenBCI_LSL 21 | 22 | ''' 23 | 24 | import sys 25 | import lib.streamerlsl as streamerlsl 26 | 27 | def main(argv): 28 | 29 | # if no arguments are provided, default to the GUI application 30 | if not argv: 31 | import lib.gui as gui 32 | from PyQt4 import QtGui 33 | app = QtGui.QApplication(sys.argv) 34 | window = gui.GUI() 35 | sys.exit(app.exec_()) 36 | 37 | # if user specifies CLI using the "--stream" argument, check if a port is also specified 38 | else: 39 | if argv[0] == '--stream': 40 | lsl = streamerlsl.StreamerLSL(GUI=False) 41 | else: 42 | try: 43 | if argv[1] != '--stream': 44 | print ("Command '%s' not recognized" % argv[1]) 45 | return 46 | except IndexError: 47 | print("Command '%s' not recognized" % argv[0]) 48 | return 49 | port = argv[0] 50 | lsl = streamerlsl.StreamerLSL(port=port,GUI=False) 51 | 52 | lsl.create_lsl() 53 | #print("begin") 54 | lsl.begin() 55 | 56 | 57 | if __name__ == '__main__': 58 | main(sys.argv[1:]) 59 | -------------------------------------------------------------------------------- /Intern-FInal-Report/Report _Bharath_Kumar.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/Intern-FInal-Report/Report _Bharath_Kumar.doc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EOG signal acquisition and classification using ADS1299 2 | 3 | 4 |

5 | 6 |

7 | 8 | ### Objectives of the Project: 9 | 10 | 1. Design a circuit that can process the electrode signals to signal of range 0-5V for supplying it to Arduino ADC 11 | 12 | 2. Transfer the data from Arduino to PC using HC-05 Bluetooth module 13 | 14 | 3. Acquire and transfer the EOG signals to PC using ADS1299 and Arduino 15 | 16 | 4. Build an interface that can make use of the acquired signal in Python 17 | 18 | 5. Write a classification Algorithm to classify the signals 19 | 20 | 6. Implement it in an Application 21 | 22 | ### Tools used for Implementation 23 | 24 | 1. Python 25 | 2. Processing (Java) 26 | 27 | ### Data transfer from Arduino to PC: 28 | 29 | The HC-05 module is powered by 5V from the Arduino. The interfacing of Arduino and HC-05 is as follows. The transfer pin of Arduino is connected to receiver pin of HC-05 and receiver pin of Arduino is connected to the receiver pin of HC-05. The data acquired in the ADC pin is converted to voltage. 30 | 31 | The baud rate is set to 115200 so that the speed of data transfer becomes 115200 bits per second. This is implemented with C language in Arduino IDE and uploaded to Arduino. A Python script is written in PC ensuring the reception of data bytes. This is ensured using the python module PySerial, that can decode the incoming byte and display it as output. 32 | 33 | ### Electrode Placement: 34 | 35 | The placement of electrodes is important in EOG signal acquisition. The point of forehead is taken as reference, where there is very less electrical activity. Two electrodes are placed above and below an eye, and the other two are placed right of the right eye and left of the left eye. The iris has abundant electrons to create electrical activity during eye movements. This is the crux of EOG. 36 | 37 |

38 | 39 | Electrode placement 40 |

41 | 42 | ### ADS1299 and Arduino Interfacing : 43 | 44 | The ADS1299 and Arduino communicate using SPI (Serial Peripheral Interface). In SPI, only one side generates the clock signal (usually called CLK or SCK for Serial Clock). The side that generates the clock is called the “master”, and the other side is called the “slave”. There is always only one master (which is almost always your microcontroller), but there can be multiple slaves (more on this in a bit). 45 | 46 | When data is sent from the master to a slave, it’s sent on a data line called MOSI, for “Master Out / Slave In”. If the slave needs to send a response back to the master, the master will continue to generate a prearranged number of clock cycles, and the slave will put the data onto a third data line called MISO, for “Master In / Slave Out”. 47 | 48 | The SS line is normally held high, which disconnects the slave from the SPI bus. (This type of logic is known as “active low,” and you’ll often see used it for enable and reset lines.) Just beforedata is sent to the slave, the line is brought low, which activates the slave. When you’re done using the slave, the line is made high again. 49 | 50 | The Arduino to ADS1299 connections is as follows: 51 | 52 | 1 . Arduino pin D10 (SS = Slave Select) to ADS1299 pin 39 (CS = Chip Select) 53 | 54 | 2 . Arduino pin D11 (MOSI = Master Out, Slave In) to ADS1299 pin 34 (DIN = Data In) 55 | 56 | 3 . Arduino pin D12 (MISO = Master In, Slave Out) to ADS1299 pin 43 (DOUT = Data Out) 57 | 58 | 4 . Arduino pin D13 (SCK = Serial Clock) to ADS1299 pin 40 (SCLK), and of course 59 | 60 | 5 . Arduino pin GND to ADS1299 pins 33, 49, 51 (DGND = Digital Ground) 61 | 62 | 6 . Arduino pin 5V to ADS1299 5V pin for powering ADS1299 63 | 64 | 65 | ### Electrode connection: 66 | 67 | There are 16 pins in ADS1299 for 8 channels. Each channels will have a respective +ve and 68 | 69 | -ve pin. There are 2 SRB pins, these SRB pins when enabled, gets shorted with the entire row of pins. 70 | 71 | This enables us to use the electrodes with respect to common reference. 72 | 73 | When the SRB pins are disabled, each channel pins can be used as differential input, so that each electrode has separate reference. There are bias pins, where required bias for the output signal can be supplied. For our project, we had disabled both SRB pins, and connected the electrodes in channel 1 to use it in differential mode. We connected ground to the Bias pin. 74 | 75 | Connecting electrodes to ADS1299 76 | 77 |

78 | 79 |

80 | 81 | 82 | ### Arduino code: 83 | 84 | The channels are activated in the setup. Each data from the ADS1299 channels is updated to Arduino in the loop during every iteration. Filters are applied to these signals. The filters include two 60 hz notch filter and a DC component filter. These are converted to bytes and sent to PC serially. 85 | 86 | The serial event part will respond to the received serial data. The received data will correspond to certain actions. Some include enabling and disabling channels. 87 | 88 | 89 | ### ADS1299 data format: 90 | 91 | The data from ADS1299 and Arduino is sent to PC through serial communication in a specific format, so that its easier for us to differentiate signals from all channels at different instances. The data format is as follows 92 | 93 | Byte 1 : START BYTE (0xa0). 94 | 95 | Byte 2 : Size of Data Packet excluding START BYTE, STOP BYTE and Byte 2. 96 | 97 | Byte 3 to 6 : ID for each Data Packet, this is incremented after the arrival of every Data Packet. 98 | 99 | Byte 7 to 38 : Data for all 8 channels (4 bytes for each channel) 100 | 101 | Byte 39 : STOP BYTE (0xc0) 102 | 103 | The baud rate is set to 115200 bits per second in both Arduino and Python receiver script. The data is sent in the sequence as mentioned above from the Arduino. On the receiving end, the python script receives the START BYTE and checks for the next. 104 | 105 | After the second byte is received, the channel loop is manipulated with respect to the value of this byte. Then 4 continuous bytes are received, this act as a counter of data packets and keep track of the number of data packets. 106 | 107 | After this, every 4 bytes of data is acquired and saved in a list array with row number as the channel number. This way 36 bytes for 8 channels are received and save in list array of size 8 rows x 4 columns. The bytes are decoded first. Then the 4 bytes from each channel is converted to 32 bit signed integer. This integer is in 2s complement, so that we can store and transfer negative values too. We convert the 2s complement back to integer. This value is further converted to micro volts by multiplying with the scaling factor. The scaling factor is as follows. 108 | 109 |

110 | 111 |

112 | 113 | Finally the script waits for the STOP BYTE, if the STOP BYTE is equal to 0xc0, the Data packet is valid, else its invalid. 114 | 115 | 116 | ### User Interface: 117 | 118 | There are 4 scripts written to acquire and display the signals. These scripts are derived and developed from OpenBCI opensouce scripts. To run the user interface openbci_lsl.py is to be run. Python openbci_lsl.py command should be used in command prompt to open GUI (Graphical User Interface) or Python openbci_lsl.py –stream command should be used to just run the script in command prompt and print the channel outputs. 119 | 120 | Running the script will call streamerlsl.py python script. This script will in turn call open_bci_v3.py , allowing the script to find the port to which Arduino is connected. 121 | 122 | 123 | Then the streamerlsl.py sets the default settings to acquire the signal properly. After that, the script reads the initial data dumped by Arduino in the Flash memory. That will give us the necessary information to proceed with the user interface. The streaming is then started. The user interface can be used alongside the streaming with the help of threading module. The streaming is run in separate thread so that is doesn’t clash with the user interface. 124 | 125 | There is a separate script for graphical user interface gui.py , which is called when openbci_lsl.py is executed. The GUI is done totally using PyQt4 module in python. 126 | 127 | 128 | Disabling channels: 129 | 130 | For disabling channels we send data (1 to 8) serially to Arduino to disable the respective channels. For enabling we send (q to i) serially to Arduino to enable the channels respective channels. The Arduino is coded accordingly to manipulate these settings. 131 | 132 | Output when openbci_lsl.py is executed: 133 | 134 |

135 | 136 | The Graphical User Interface 137 |

138 | 139 | The GUI contains options to change the port, if we are using multiple Arduino. Using the connect button the specific port is connected along with the modified settings. To change the settings we have to disconnect, change the settings and connect again to visualize the changes. Board Config button is used to change the state of the channels, whether to turn it on or off. 140 | Output when openbci_lsl.py –stream is used: 141 | 142 |

143 | 144 | Command prompt user interface 145 |

146 | 147 | 148 | The basic information is printed in the screen. From the output we have a list array with 8 elements. Each element is the output of the respective channels in micro volts. This is printed continuously in the command prompt window. 149 | 150 | 151 | 152 | ### Output: 153 | 154 |

155 | 156 | Eye to right 157 |

158 |

159 | 160 | Eye to left 161 |

162 |

163 | 164 | Eye to bottom 165 |

166 |

167 | 168 | Eye to top 169 |

170 | 171 | 172 | ### Classification Algorithm: 173 | 174 | Three classification algorithms has been implemented for classifying the EOG signal. 175 | 176 | 1 . Thresholding 177 | 178 | 2 . Template Matching 179 | 180 | 3 . RNN 181 | 182 | Thresholding : 183 | 184 | The output signals can be picturized from the above mentioned images. The main problem in just plainly classifying the signal using thresholding is that, there is both positive and negative peak for both left and right movement. This makes it difficult to differentiate between them. To overcome this edge variable is used. 185 | 186 | Edges are detected by subtracting the current voltage output by the previous voltage output. If we detect a positive edge, the edge flag is set to 1. When the output voltage surpasses the threshold level, the status of the movement is set as “right”. Again, until we detect a negative peak, the status is right, else the status goes back to “centre”. Vice versa for left. 187 | 188 | Template matching: 189 | 190 | Templates for left, right, top, bottom are already predefined from previously collected data. The incoming data is stored in a buffer of length same as the template. This buffer is then compared with the template using Pearson’s correlation function. 191 | 192 | The output value of this function will be in range between 0 to 1, with 1 being highly corelated and 0 being no correlation. 193 | 194 | RNN : 195 | 196 | The algorithm is implemented totally using Keras, tensorflow backend. The data should be pre processed accordingly. The model used here is sequential model. The features are the outputs amplitude from the two channels. The output is passes through embedding and convolution layer. 197 | 198 | Then the output is pooled and the output data is normalized. The resultant is sent to LSTM layer and made dense to give an output. 199 | 200 | 201 | ### Application: 202 | 203 | The output of the classifier is used to control the turtle to navigate the maze given below. The objective is to find a way out of the maze using eye movement. This can be developed to eye-movement based keyboard and many other applications. 204 | 205 |

206 | 207 | The maze game 208 |

209 | 210 | ### Circuit design: 211 | 212 | A circuit is built to process the milli volt output from electrodes to the range of 0 to 5 volts, such the it can be given as input to Arduino 213 | 214 | Different stages of the circuit: 215 | 216 | 1 . The signal from channels are given to instrumentation amplifier AD620 to get differential output 217 | 218 | 2 . A passive high pass filter to reduce low frequency noise and remove DC component. 219 | 220 | 3 . An active high pass filter to again reduce the low frequency noise. 221 | 222 | 4 . A passive low pass filter to reduce high frequency noise. 223 | 224 | 5 . A 2nd order Butterworth filter with higher roll frequency. 225 | 226 | 6 . Finally, an instrumentation amplifier to add 2.5V bias. Each stage has their own gain that finally gives us output between 0-5V. 227 | 228 | 229 |

230 | 231 | AD620 Instrumentation Amplifier 232 |

233 | 234 | 235 |

236 | 237 | Rest of the signal processing circuit 238 |

239 | 240 | -------------------------------------------------------------------------------- /StreamRawData/StreamRawData/StreamRawData.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | 7 | 8 | /* 9 | 10 | Developed by Chip Audette (Fall 2013) for use with OpenBCI 11 | Builds upon work by Joel Murphy and Conor Russomanno (Summer 2013) 12 | 13 | Modified January 2014. 14 | 15 | This example uses the ADS1299 Arduino Library, a software bridge between the ADS1299 TI chip and 16 | Arduino. See http://www.ti.com/product/ads1299 for more information about the device and the README 17 | folder in the ADS1299 directory for more information about the library. 18 | 19 | */ 20 | typedef long int int32; 21 | #define N_CHANNELS_PER_OPENBCI (8) //number of channels on a single OpenBCI board 22 | 23 | //for using a single OpenBCI board 24 | //#include //for a single OpenBCI board 25 | ADS1299Manager ADSManager; //Uses SPI bus and pins to say data is ready. Uses Pins 13,12,11,10,9,8,4 26 | #define MAX_N_CHANNELS (N_CHANNELS_PER_OPENBCI) //use this for a single OpenBCI board 27 | int nActiveChannels = 2; //how many active channels would I like? 28 | 29 | //for using two daisy-chained OpenBCI boards 30 | //#include //for daisy chaining 31 | //ADS1299DaisyManager ADSManager; //Uses SPI bus and pins to say data is ready. Uses Pins 13,12,11,10,9,8,4 32 | //#define MAX_N_CHANNELS (2*N_CHANNELS_PER_OPENBCI) //use this for daisy chaining 33 | //int nActiveChannels = 16; //how many active channels would I like? 34 | 35 | //other settings for OpenBCI 36 | //byte gainCode = ADS_GAIN24; //how much gain do I want 37 | byte gainCode = ADS_GAIN24; 38 | byte inputType = ADSINPUT_NORMAL; //here's the normal way to setup the channels 39 | //byte inputType = ADSINPUT_SHORTED; //here's another way to setup the channels 40 | //byte inputType = ADSINPUT_TESTSIG; //here's a third way to setup the channels 41 | 42 | //other variables 43 | long sampleCounter = 0; // used to time the tesing loop 44 | boolean is_running = false; // this flag is set in serialEvent on reciept of prompt 45 | #define PIN_STARTBINARY (7) //pull this pin to ground to start binary transfer 46 | //define PIN_STARTBINARY_OPENEEG (6) 47 | boolean startBecauseOfPin = false; 48 | boolean startBecauseOfSerial = false; 49 | 50 | #define OUTPUT_NOTHING (0) 51 | #define OUTPUT_TEXT (1) 52 | #define OUTPUT_BINARY (2) 53 | #define OUTPUT_BINARY_SYNTHETIC (3) 54 | #define OUTPUT_BINARY_4CHAN (4) 55 | #define OUTPUT_BINARY_OPENEEG (6) 56 | #define OUTPUT_BINARY_OPENEEG_SYNTHETIC (7) 57 | int outputType; 58 | 59 | //Design filters (This BIQUAD class requires ~6K of program space! Ouch.) 60 | //For frequency response of these filters: http://www.earlevel.com/main/2010/12/20/biquad-calculator/ 61 | #include //modified from this source code: http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ 62 | #define SAMPLE_RATE_HZ (250.0) //default setting for OpenBCI 63 | //////#define SAMPLE_RATE_HZ (500.0) //default setting for ECG * 64 | #define FILTER_Q (0.5) //critically damped is 0.707 (Butterworth) 65 | #define FILTER_PEAK_GAIN_DB (0.0) //we don't want any gain in the passband 66 | //#define HP_CUTOFF_HZ (0.5) //set the desired cutoff for the highpass filter 67 | #define HP_CUTOFF_HZ (0.05) //set the desired cutoff for the highpass filter 68 | Biquad_multiChan stopDC_filter(MAX_N_CHANNELS, bq_type_highpass, HP_CUTOFF_HZ / SAMPLE_RATE_HZ, FILTER_Q, FILTER_PEAK_GAIN_DB); //one for each channel because the object maintains the filter states 69 | //Biquad_multiChan stopDC_filter(MAX_N_CHANNELS,bq_type_bandpass,10.0 / SAMPLE_RATE_HZ, 6.0, FILTER_PEAK_GAIN_DB); //one for each channel because the object maintains the filter states 70 | #define NOTCH_FREQ_HZ (50.0) 71 | #define NOTCH_Q (4.0) //pretty sharp notch 72 | #define NOTCH_PEAK_GAIN_DB (0.0) //doesn't matter for this filter type 73 | Biquad_multiChan notch_filter1(MAX_N_CHANNELS, bq_type_notch, NOTCH_FREQ_HZ / SAMPLE_RATE_HZ, NOTCH_Q, NOTCH_PEAK_GAIN_DB); //one for each channel because the object maintains the filter states 74 | Biquad_multiChan notch_filter2(MAX_N_CHANNELS, bq_type_notch, NOTCH_FREQ_HZ / SAMPLE_RATE_HZ, NOTCH_Q, NOTCH_PEAK_GAIN_DB); //one for each channel because the object maintains the filter states 75 | boolean useFilters = true; //enable or disable as you'd like...turn off if you're daisy chaining! 76 | 77 | 78 | void setup() { 79 | //detect which version of OpenBCI we're using (is Pin2 jumped to Pin3?) 80 | int OpenBCI_version = OPENBCI_V2; //assume V2 81 | pinMode(2, INPUT); digitalWrite(2, HIGH); //activate pullup...for detecting which version of OpenBCI PCB 82 | pinMode(3, OUTPUT); digitalWrite(3, LOW); //act as a ground pin...for detecting which version of OpenBCI PCB 83 | if (digitalRead(2) == LOW) OpenBCI_version = OPENBCI_V1; //check pins to see if there is a jumper. if so, it is the older board 84 | ADSManager.initialize(OpenBCI_version); //must do this VERY early in the setup...preferably first 85 | 86 | // setup the serial link to the PC 87 | Serial.begin(115200); //Need 115200 for 16-channels, only need 115200 for 8-channels but let's do 115200*2 for consistency 88 | Serial.println(F("ADS1299-Arduino UNO - Stream Raw Data")); //read the string from Flash to save RAM 89 | Serial.print(F("Configured as OpenBCI_Version code = ")); Serial.println(OpenBCI_version); 90 | Serial.flush(); 91 | 92 | // setup the channels as desired on the ADS1299..set gain, input type, referece (SRB1), and patient bias signal 93 | for (int chan = 1; chan <= nActiveChannels; chan++) { 94 | ADSManager.activateChannel(chan, gainCode, inputType); 95 | } 96 | 97 | //print state of all registers 98 | //ADSManager.printAllRegisters();Serial.flush(); 99 | 100 | // setup hardware to allow a jumper or button to start the digitaltransfer 101 | pinMode(PIN_STARTBINARY, INPUT); digitalWrite(PIN_STARTBINARY, HIGH); //activate pullup 102 | //pinMode(PIN_STARTBINARY_OPENEEG,INPUT); digitalWrite(PIN_STARTBINARY_OPENEEG,HIGH); //activate pullup 103 | 104 | //look out for daisy chaining and disable filtering because it'll likely take too much computation 105 | if (nActiveChannels > 8) useFilters = false; 106 | if (useFilters) Serial.print(F("Configured to do some filtering here on the Arduino.")); 107 | 108 | 109 | // tell the controlling program that we're ready to start! 110 | Serial.println(F("Press '?' to query and print ADS1299 register settings again")); //read it straight from flash 111 | Serial.println(F("Press 1-8 to disable EEG Channels, q-i to enable (all enabled by default)")); 112 | Serial.println(F("Press 'F' to enable filters. 'f' to disable filters (disabled by default)")); 113 | Serial.println(F("Press 'x' (text) or 'b' (binary) to begin streaming data...")); 114 | 115 | } // end of setup 116 | 117 | boolean firstReport = true; 118 | unsigned long totalMicrosBusy = 0; //use this to count time 119 | void loop() { 120 | 121 | if (digitalRead(PIN_STARTBINARY) == LOW) { 122 | //button is pressed (or pin is jumpered to ground) 123 | startBecauseOfPin = true; 124 | //startRunning(OUTPUT_BINARY_OPENEEG_SYNTHETIC); 125 | //startRunning(OUTPUT_BINARY_OPENEEG); 126 | startRunning(OUTPUT_BINARY); 127 | //if (firstReport) { Serial.println(F("Starting Binary_OpenEEG Based on Pin")); firstReport=false;} 128 | } else { 129 | if (startBecauseOfPin) { 130 | startBecauseOfPin = false; 131 | stopRunning(); 132 | //if (firstReport == false) { Serial.println(F("Stopping Binary Based on Pin")); firstReport=true;} 133 | } 134 | } 135 | 136 | if (is_running) { 137 | 138 | //is data ready? 139 | while (!(ADSManager.isDataAvailable())) { // watch the DRDY pin 140 | delayMicroseconds(100); 141 | } 142 | unsigned long start_micros = micros(); 143 | 144 | //get the data 145 | ADSManager.updateChannelData(); // update the channelData array 146 | sampleCounter++; // increment my sample counter 147 | 148 | //Apply filers to the data 149 | if (useFilters) applyFilters(); 150 | 151 | //print the data 152 | //if ((sampleCounter % 1) == 0) { 153 | switch (outputType) { 154 | case OUTPUT_NOTHING: 155 | //don't output anything...the Arduino is still collecting data from the OpenBCI board...just nothing is being done with it 156 | //if ((sampleCounter % 250) == 1) { Serial.print(F("Free RAM = ")); Serial.println(freeRam()); }; //print memory status 157 | break; 158 | case OUTPUT_BINARY: 159 | ADSManager.writeChannelDataAsBinary(MAX_N_CHANNELS, sampleCounter); //print all channels, whether active or not 160 | break; 161 | case OUTPUT_BINARY_SYNTHETIC: 162 | ADSManager.writeChannelDataAsBinary(MAX_N_CHANNELS, sampleCounter, true); //print all channels, whether active or not 163 | break; 164 | case OUTPUT_BINARY_4CHAN: 165 | ADSManager.writeChannelDataAsBinary(4, sampleCounter); //print 4 channels, whether active or not 166 | break; 167 | case OUTPUT_BINARY_OPENEEG: 168 | ADSManager.writeChannelDataAsOpenEEG_P2(sampleCounter); //this format accepts 6 channels, so that's what it does 169 | break; 170 | case OUTPUT_BINARY_OPENEEG_SYNTHETIC: 171 | ADSManager.writeChannelDataAsOpenEEG_P2(sampleCounter, true); //his format accepts 6 channels, so that's what it does 172 | break; 173 | default: 174 | ADSManager.printChannelDataAsText(MAX_N_CHANNELS, sampleCounter); //print all channels, whether active or not 175 | } 176 | //} 177 | 178 | // totalMicrosBusy += (micros()-start_micros); //accumulate 179 | // if (sampleCounter==250) totalMicrosBusy = 0; //start from 250th sample 180 | // if (sampleCounter==500) { 181 | // stopRunning(); 182 | // Serial.println(); 183 | // Serial.print(F("Was busy for ")); 184 | // Serial.print(totalMicrosBusy); 185 | // Serial.println(F(" microseconds across 250 samples")); 186 | // Serial.print(F("Assuming a 250Hz Sample Rate, it was busy for ")); 187 | // unsigned long micros_per_250samples = 1000000UL; 188 | // Serial.print(((float)totalMicrosBusy/(float)micros_per_250samples)*100.0); 189 | // Serial.println(F("% of the available time")); 190 | // } 191 | 192 | } 193 | 194 | } // end of loop 195 | 196 | 197 | #define ACTIVATE_SHORTED (2) 198 | #define ACTIVATE (1) 199 | #define DEACTIVATE (0) 200 | void serialEvent() { // send an 'x' on the serial line to trigger ADStest() 201 | while (Serial.available()) { 202 | char inChar = (char)Serial.read(); 203 | switch (inChar) 204 | { 205 | case '1': 206 | changeChannelState_maintainRunningState(1, DEACTIVATE); break; 207 | case '2': 208 | changeChannelState_maintainRunningState(2, DEACTIVATE); break; 209 | case '3': 210 | changeChannelState_maintainRunningState(3, DEACTIVATE); break; 211 | case '4': 212 | changeChannelState_maintainRunningState(4, DEACTIVATE); break; 213 | case '5': 214 | changeChannelState_maintainRunningState(5, DEACTIVATE); break; 215 | case '6': 216 | changeChannelState_maintainRunningState(6, DEACTIVATE); break; 217 | case '7': 218 | changeChannelState_maintainRunningState(7, DEACTIVATE); break; 219 | case '8': 220 | changeChannelState_maintainRunningState(8, DEACTIVATE); break; 221 | case 'q': 222 | changeChannelState_maintainRunningState(1, ACTIVATE); break; 223 | case 'w': 224 | changeChannelState_maintainRunningState(2, ACTIVATE); break; 225 | case 'e': 226 | changeChannelState_maintainRunningState(3, ACTIVATE); break; 227 | case 'r': 228 | changeChannelState_maintainRunningState(4, ACTIVATE); break; 229 | case 't': 230 | changeChannelState_maintainRunningState(5, ACTIVATE); break; 231 | case 'y': 232 | changeChannelState_maintainRunningState(6, ACTIVATE); break; 233 | case 'u': 234 | changeChannelState_maintainRunningState(7, ACTIVATE); break; 235 | case 'i': 236 | changeChannelState_maintainRunningState(8, ACTIVATE); break; 237 | case '0': 238 | activateAllChannelsToTestCondition(ADSINPUT_SHORTED, ADSTESTSIG_NOCHANGE, ADSTESTSIG_NOCHANGE); break; 239 | case '-': 240 | activateAllChannelsToTestCondition(ADSINPUT_TESTSIG, ADSTESTSIG_AMP_1X, ADSTESTSIG_PULSE_SLOW); break; 241 | case '+': 242 | activateAllChannelsToTestCondition(ADSINPUT_TESTSIG, ADSTESTSIG_AMP_1X, ADSTESTSIG_PULSE_FAST); break; 243 | case '=': 244 | //repeat the line above...just for human convenience 245 | activateAllChannelsToTestCondition(ADSINPUT_TESTSIG, ADSTESTSIG_AMP_1X, ADSTESTSIG_PULSE_FAST); break; 246 | case 'p': 247 | activateAllChannelsToTestCondition(ADSINPUT_TESTSIG, ADSTESTSIG_AMP_2X, ADSTESTSIG_DCSIG); break; 248 | case '[': 249 | activateAllChannelsToTestCondition(ADSINPUT_TESTSIG, ADSTESTSIG_AMP_2X, ADSTESTSIG_PULSE_SLOW); break; 250 | case ']': 251 | activateAllChannelsToTestCondition(ADSINPUT_TESTSIG, ADSTESTSIG_AMP_2X, ADSTESTSIG_PULSE_FAST); break; 252 | case 'n': 253 | toggleRunState(OUTPUT_NOTHING); 254 | startBecauseOfSerial = is_running; 255 | if (is_running) Serial.println(F("Arduino: Starting, but not outputing to PC...")); 256 | break; 257 | case 'b': 258 | toggleRunState(OUTPUT_BINARY); 259 | //toggleRunState(OUTPUT_BINARY_SYNTHETIC); 260 | startBecauseOfSerial = is_running; 261 | if (is_running) Serial.println(F("Arduino: Starting binary...")); 262 | break; 263 | case 'v': 264 | toggleRunState(OUTPUT_BINARY_4CHAN); 265 | startBecauseOfSerial = is_running; 266 | if (is_running) Serial.println(F("Arduino: Starting binary 4-chan...")); 267 | break; 268 | case 's': 269 | stopRunning(); 270 | startBecauseOfSerial = is_running; 271 | break; 272 | case 'x': 273 | toggleRunState(OUTPUT_TEXT); 274 | startBecauseOfSerial = is_running; 275 | if (is_running) Serial.println(F("Arduino: Starting text...")); 276 | break; 277 | case 'f': 278 | useFilters = false; 279 | Serial.println(F("Arduino: disabling filters")); 280 | break; 281 | case 'F': 282 | useFilters = true; 283 | Serial.println(F("Arduino: enabaling filters")); 284 | break; 285 | case '?': 286 | //print state of all registers 287 | ADSManager.printAllRegisters(); 288 | break; 289 | default: 290 | break; 291 | } 292 | } 293 | } 294 | 295 | boolean toggleRunState(int OUT_TYPE) 296 | { 297 | if (is_running) { 298 | return stopRunning(); 299 | } else { 300 | return startRunning(OUT_TYPE); 301 | } 302 | } 303 | 304 | boolean stopRunning(void) { 305 | ADSManager.stop(); // stop the data acquisition 306 | is_running = false; 307 | return is_running; 308 | } 309 | 310 | boolean startRunning(int OUT_TYPE) { 311 | outputType = OUT_TYPE; 312 | ADSManager.start(); //start the data acquisition 313 | is_running = true; 314 | return is_running; 315 | } 316 | 317 | int changeChannelState_maintainRunningState(int chan, int start) 318 | { 319 | boolean is_running_when_called = is_running; 320 | int cur_outputType = outputType; 321 | 322 | //must stop running to change channel settings 323 | stopRunning(); 324 | if (start == true) { 325 | Serial.print(F("Activating channel ")); 326 | Serial.println(chan); 327 | ADSManager.activateChannel(chan, gainCode, inputType); 328 | } else { 329 | Serial.print(F("Deactivating channel ")); 330 | Serial.println(chan); 331 | ADSManager.deactivateChannel(chan); 332 | } 333 | 334 | //restart, if it was running before 335 | if (is_running_when_called == true) { 336 | startRunning(cur_outputType); 337 | } 338 | } 339 | 340 | int activateAllChannelsToTestCondition(int testInputCode, byte amplitudeCode, byte freqCode) 341 | { 342 | boolean is_running_when_called = is_running; 343 | int cur_outputType = outputType; 344 | 345 | //set the test signal to the desired state 346 | ADSManager.configureInternalTestSignal(amplitudeCode, freqCode); 347 | 348 | //must stop running to change channel settings 349 | stopRunning(); 350 | 351 | //loop over all channels to change their state 352 | for (int Ichan = 1; Ichan <= 8; Ichan++) { 353 | ADSManager.activateChannel(Ichan, gainCode, testInputCode); //Ichan must be [1 8]...it does not start counting from zero 354 | } 355 | 356 | //restart, if it was running before 357 | if (is_running_when_called == true) { 358 | startRunning(cur_outputType); 359 | } 360 | } 361 | 362 | long int runningAve[MAX_N_CHANNELS]; 363 | int applyFilters(void) { 364 | //scale factor for these coefficients was 32768 = 2^15 365 | const static long int a0 = 32360L; //16 bit shift? 366 | const static long int a1 = -2L * a0; 367 | const static long int a2 = a0; 368 | const static long int b1 = -64718L; //this is a shift of 17 bits! 369 | const static long int b2 = 31955L; 370 | static long int z1[MAX_N_CHANNELS], z2[MAX_N_CHANNELS]; 371 | long int val_int, val_in_down9, val_out, val_out_down9; 372 | float val; 373 | for (int Ichan = 0; Ichan < MAX_N_CHANNELS; Ichan++) { 374 | switch (1) { 375 | case 1: 376 | //use BiQuad 377 | val = (float) ADSManager.channelData[Ichan]; //get the stored value for this sample 378 | val = stopDC_filter.process(val, Ichan); //apply DC-blocking filter 379 | break; 380 | case 2: 381 | //do fixed point, 1st order running ave 382 | val_int = ADSManager.channelData[Ichan]; //get the stored value for this sample 383 | //runningAve[Ichan]=( ((512-1)*(runningAve[Ichan]>>2)) + (val_int>>2) )>>7; // fs/0.5Hz = ~512 points..9 bits 384 | //runningAve[Ichan]=( ((256-1)*(runningAve[Ichan]>>2)) + (val_int>>2) )>>6; // fs/1.0Hz = ~256 points...8 bits 385 | runningAve[Ichan] = ( ((128 - 1) * (runningAve[Ichan] >> 1)) + (val_int >> 1) ) >> 6; // fs/2.0Hz = ~128 points...7 bits 386 | val = (float)(val_int - runningAve[Ichan]); //remove the DC 387 | break; 388 | // case 3: 389 | // val_in_down9 = ADSManager.channelData[Ichan] >> 9; //get the stored value for this sample...bring 24-bit value down to 16-bit 390 | // val_out = (val_in_down9 * a0 + (z1[Ichan]>>9)) >> (16-9); //8bits were already removed...results in 24-bit value 391 | // val_out_down9 = val_out >> 9; //remove eight bits to go from 24-bit down to 16 bit 392 | // z1[Ichan] = (val_in_down9 * a1 + (z2[Ichan] >> 9) - b1 * val_out_down9 ) >> (16-9); //8-bits were pre-removed..end in 24 bit number 393 | // z2[Ichan] = (val_in_down9 * a2 - b2 * val_out_down9) >> (16-9); //8-bits were pre-removed...end in 24-bit number 394 | // val = (float)val_out; 395 | // break; 396 | } 397 | val = notch_filter1.process(val, Ichan); //apply 60Hz notch filter 398 | val = notch_filter2.process(val, Ichan); //apply it again 399 | ADSManager.channelData[Ichan] = (long) val; //save the value back into the main data-holding object 400 | } 401 | return 0; 402 | } 403 | 404 | int freeRam() 405 | { 406 | extern int __heap_start, *__brkval; 407 | int v; 408 | return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 409 | } 410 | 411 | 412 | -------------------------------------------------------------------------------- /__pycache__/main.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/__pycache__/main.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/template_match.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/__pycache__/template_match.cpython-36.pyc -------------------------------------------------------------------------------- /data_stream.py: -------------------------------------------------------------------------------- 1 | ### ONLY ALGO IMPLEMENTATION ### 2 | 3 | databyte = 0 4 | readstate = 0 5 | numchannels = 8 6 | errorcounter = 0 7 | bytecounter = 0 8 | bytebuffer = [] 9 | 10 | if readstate is 0: 11 | if databyte is bytes('0Xa0', 'utf-8'): 12 | readstate = readstate + 1 13 | elif readstate is 1: 14 | channels_in_packet = int(databyte)/4 - 1 15 | if channels_in_packet is not numchannels: 16 | errorcounter = errorcounter + 1 17 | readstate = 0 18 | else: 19 | bytecounter = 0 20 | readstate = readstate + 1 21 | elif readstate is 2: 22 | bytebuffer[bytecounter] = databyte 23 | bytecounter = bytecounter + 1 24 | if bytecounter is 4: 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /graph.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import matplotlib.animation as animation 3 | import time 4 | import numpy 5 | 6 | class graph: 7 | 8 | def __init__(self): 9 | self.fig = plt.figure() 10 | self.ax1 = self.fig.add_subplot(1,1,1) 11 | self.x = '3' 12 | self.y = '0' 13 | 14 | def animate(self, i): 15 | print(self.x,self.y) 16 | #deletes first row 17 | self.f = open("sampleText.txt","r+") 18 | self.d = self.f.readlines() 19 | self.f.seek(0) 20 | for i in self.d: 21 | if i != self.d[0]: 22 | self.f.write(i) 23 | self.f.truncate() 24 | self.f.close() 25 | 26 | #writes new data for plotting 27 | self.filedata = open("sampleText.txt","a+") 28 | self.x = str(int(self.x) + 1) 29 | self.y = str(int(self.y) + 2) 30 | self.filedata.write(self.x+","+self.y+"\n") 31 | self.filedata.close() 32 | 33 | #save data 34 | self.filedata = open("values.txt","a+") 35 | self.x = str(int(self.x) + 1) 36 | self.y = str(int(self.y) + 2) 37 | self.filedata.write(self.x+","+self.y+"\n") 38 | self.filedata.close() 39 | 40 | #plot 41 | self.pullData = open("sampleText.txt","r").read() 42 | self.dataArray = self.pullData.split('\n') 43 | self.xar = [] 44 | self.yar = [] 45 | for eachLine in self.dataArray: 46 | if len(eachLine)>1: 47 | self.x,self.y = eachLine.split(',') 48 | self.xar.append(int(self.x)) 49 | self.yar.append(int(self.y)) 50 | self.ax1.clear() 51 | self.ax1.plot(self.xar,self.yar) 52 | self.graph_image = np.array(self.fig.canvas.renderer._renderer) 53 | return self.graph_image 54 | 55 | def start(self): 56 | self.ani = animation.FuncAnimation(self.fig, self.animate, interval=1000) 57 | self.show = plt.show() 58 | 59 | if __name__ == "__main__": 60 | graph1 = graph() 61 | graph1.start() -------------------------------------------------------------------------------- /images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/1.jpg -------------------------------------------------------------------------------- /images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/2.jpg -------------------------------------------------------------------------------- /images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/3.jpg -------------------------------------------------------------------------------- /images/ADS1299.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/ADS1299.png -------------------------------------------------------------------------------- /images/GUI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/GUI.jpg -------------------------------------------------------------------------------- /images/bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/bottom.jpg -------------------------------------------------------------------------------- /images/electrode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/electrode.jpg -------------------------------------------------------------------------------- /images/factor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/factor.jpg -------------------------------------------------------------------------------- /images/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/left.png -------------------------------------------------------------------------------- /images/maze.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/maze.jpg -------------------------------------------------------------------------------- /images/me.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/me.jpeg -------------------------------------------------------------------------------- /images/right.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/right.jpg -------------------------------------------------------------------------------- /images/top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/images/top.jpg -------------------------------------------------------------------------------- /main (copy).py: -------------------------------------------------------------------------------- 1 | import template_match as tm 2 | import scipy 3 | 4 | prev_amplitude = 0 5 | 6 | class classify: 7 | 8 | def __init__(self): 9 | self.vertical_prev_amplitude = 0 10 | self.horizontal_prev_amplitude = 0 11 | self.left_flag = 0 12 | self.right_flag = 0 13 | self.up_flag = 0 14 | self.down_flag = 0 15 | self.status = "centre" 16 | self.iterator = 0 17 | self.vertical_signal_stack = [] 18 | self.horizontal_signal_stack = [] 19 | self.cross_corr_tresh = 0.9 20 | 21 | # ASSIGN TEMPLATES 22 | 23 | self.vertical_signal_stack = [] 24 | self.horizontal_signal_stack = [] 25 | self.left_template = 0 26 | self.right_template = 0 27 | self.blink_template = 0 28 | self.up_template = 0 29 | self.down_template = 0 30 | 31 | def treshold(self, vertical_amplitude, horizontal_amplitude): 32 | self.upper_thresh_vertical = 100 33 | self.lower_thresh_vertical = 100 34 | self.upper_thresh_horizontal = 3 35 | self.lower_thresh_horizontal = 2 36 | self.difference_tresh = 0.5 37 | 38 | self.vertical_amplitude = float(vertical_amplitude) 39 | self.horizontal_amplitude = float(horizontal_amplitude) 40 | #self.vertical_prev_amplitude = self.vertical_prev_amplitude 41 | #self.horizontal_prev_amplitude = self.horizontal_prev_amplitude 42 | #print(self.horizontal_prev_amplitude) 43 | self.vertical_difference = self.vertical_amplitude - self.vertical_prev_amplitude 44 | self.horizontal_difference = self.horizontal_amplitude - self.horizontal_prev_amplitude 45 | self.horizontal_prev_amplitude = self.horizontal_amplitude 46 | self.vertical_prev_amplitude = self.vertical_amplitude 47 | #print(self.horizontal_difference, self.horizontal_amplitude, self.horizontal_prev_amplitude) 48 | 49 | if(self.vertical_amplitude > self.upper_thresh_vertical and self.vertical_difference > self.difference_tresh): 50 | self.up_flag = 1 51 | self.status = "up" 52 | if(self.vertical_amplitude < self.lower_thresh_vertical and self.vertical_difference < -(self.difference_tresh)): 53 | self.down_flag = 1 54 | self.status = "down" 55 | if(self.horizontal_amplitude > self.upper_thresh_horizontal and self.horizontal_difference > self.difference_tresh): 56 | self.right_flag = 1 57 | self.status = "right" 58 | if(self.horizontal_amplitude < self.lower_thresh_horizontal and self.horizontal_difference < -(self.difference_tresh)): 59 | self.left_flag = 1 60 | self.status = "left" 61 | if(self.up_flag == 1 and self.vertical_difference < -(self.difference_tresh)): 62 | self.up_flag = 0 63 | self.status = "center" 64 | if(self.down_flag == 1 and self.vertical_difference > self.difference_tresh): 65 | self.down_flag = 0 66 | self.status = "center" 67 | if(self.right_flag == 1 and self.horizontal_difference < -(self.difference_tresh)): 68 | self.right_flag = 0 69 | self.status = "center" 70 | if(self.left_flag == 1 and self.horizontal_difference > self.difference_tresh): 71 | self.left_flag = 0 72 | self.status = "center" 73 | return self.status 74 | 75 | def template_match_image(self, frame): 76 | result = tm.match(frame) 77 | return result 78 | 79 | def template_match(self, vertical_amplitude, horizontal_amplitude, template_size): 80 | self.vertical_amplitude = vertical_amplitude 81 | self.horizontal_amplitude = horizontal_amplitude 82 | self.vertical_signal_stack = self.vertical_signal_stack.append(vertical_amplitude) 83 | self.horizontal_signal_stack = self.horizontal_signal_stack.append(horizontal_amplitude) 84 | 85 | if self.iterator > template_size: 86 | self.vertical_signal_stack.pop(0) 87 | self.horizontal_signal_stack.pop(0) 88 | 89 | self.left_corr = scipy.stats.pearsonr(self.horizontal_signal_stack, self.left_template) 90 | self.right_corr = scipy.stats.pearsonr(self.horizontal_signal_stack, self.right_template) 91 | self.up_corr = scipy.stats.pearsonr(self.vertical_signal_stack, self.up_template) 92 | self.down_corr = scipy.stats.pearsonr(self.vertical_signal_stack, self.down_template) 93 | self.blink_corr = scipy.stats.pearsonr(self.vertical_signal_stack, self.blink_template) 94 | 95 | if self.left_corr[0] > 0.9: 96 | self.status = "left" 97 | if self.right_corr[0] > 0.9: 98 | self.status = "right" 99 | if self.up_corr[0] > 0.9: 100 | self.status = "up" 101 | if self.down_corr[0] > 0.9: 102 | self.status = "down" 103 | if self.blink_corr[0] > 0.9: 104 | self.status = "blink" 105 | 106 | return self.status 107 | 108 | if __name__ == "__main__": 109 | classify.treshold(50) 110 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import template_match as tm 2 | import scipy 3 | 4 | prev_amplitude = 0 5 | 6 | class classify: 7 | 8 | def __init__(self): 9 | self.vertical_prev_amplitude = 0 10 | self.horizontal_prev_amplitude = 0 11 | self.left_flag = 0 12 | self.right_flag = 0 13 | self.up_flag = 0 14 | self.down_flag = 0 15 | self.status = "centre" 16 | self.iterator = 0 17 | self.vertical_signal_stack = [] 18 | self.horizontal_signal_stack = [] 19 | self.cross_corr_tresh = 0.9 20 | 21 | # ASSIGN TEMPLATES 22 | 23 | self.vertical_signal_stack = [] 24 | self.horizontal_signal_stack = [] 25 | self.left_template = 0 26 | self.right_template = 0 27 | self.blink_template = 0 28 | self.up_template = 0 29 | self.down_template = 0 30 | 31 | def treshold(self, vertical_amplitude, horizontal_amplitude): 32 | self.upper_thresh_vertical = 100 33 | self.lower_thresh_vertical = 0 34 | self.upper_thresh_horizontal = 3.5 35 | self.lower_thresh_horizontal = 2 36 | self.difference_tresh = 1.5 37 | 38 | self.vertical_amplitude = float(vertical_amplitude) 39 | self.horizontal_amplitude = float(horizontal_amplitude) 40 | #self.vertical_prev_amplitude = self.vertical_prev_amplitude 41 | #self.horizontal_prev_amplitude = self.horizontal_prev_amplitude 42 | #print(self.horizontal_prev_amplitude) 43 | self.vertical_difference = self.vertical_amplitude - self.vertical_prev_amplitude 44 | self.horizontal_difference = self.horizontal_amplitude - self.horizontal_prev_amplitude 45 | self.horizontal_prev_amplitude = self.horizontal_amplitude 46 | self.vertical_prev_amplitude = self.vertical_amplitude 47 | #print(self.horizontal_difference, self.horizontal_amplitude, self.horizontal_prev_amplitude) 48 | 49 | if( self.vertical_amplitude > self.upper_thresh_vertical and self.up_flag is 0 ): 50 | self.up_flag = 1 51 | 52 | if( self.vertical_amplitude < self.lower_thresh_vertical and self.down_flag is 0 ): 53 | self.down_flag = 1 54 | 55 | if( self.horizontal_amplitude > self.upper_thresh_horizontal ): 56 | self.right_flag = 1 57 | 58 | elif( self.horizontal_amplitude < self.lower_thresh_horizontal ): 59 | self.left_flag = 1 60 | 61 | else: 62 | self.left_flag = 0 63 | self.right_flag = 0 64 | 65 | #if( self.horizontal_amplitude > self.upper_thresh_horizontal and self.left_flag is 1): 66 | # self.left_flag = 0 67 | 68 | #if( self.horizontal_amplitude < self.lower_thresh_horizontal and self.right_flag is 1): 69 | # self.right_flag = 0 70 | 71 | #if(self.vertical_difference > abs(self.difference_tresh)): 72 | # self.up_flag = 0 73 | # self.down_flag = 0 74 | # self.right_flag = 0 75 | # self.left_flag = 0 76 | # self.status = "center" 77 | 78 | if self.left_flag is 1: 79 | self.status = "left" 80 | elif self.right_flag is 1: 81 | self.status = "right" 82 | elif self.up_flag is 1: 83 | self.status = "up" 84 | elif self.down_flag is 1: 85 | self.status = "down" 86 | else: 87 | self.status = "center" 88 | 89 | return self.status 90 | 91 | def template_match_image(self, frame): 92 | result = tm.match(frame) 93 | return result 94 | 95 | def template_match(self, vertical_amplitude, horizontal_amplitude, template_size): 96 | self.vertical_amplitude = vertical_amplitude 97 | self.horizontal_amplitude = horizontal_amplitude 98 | self.vertical_signal_stack = self.vertical_signal_stack.append(vertical_amplitude) 99 | self.horizontal_signal_stack = self.horizontal_signal_stack.append(horizontal_amplitude) 100 | 101 | if self.iterator > template_size: 102 | self.vertical_signal_stack.pop(0) 103 | self.horizontal_signal_stack.pop(0) 104 | 105 | self.left_corr = scipy.stats.pearsonr(self.horizontal_signal_stack, self.left_template) 106 | self.right_corr = scipy.stats.pearsonr(self.horizontal_signal_stack, self.right_template) 107 | self.up_corr = scipy.stats.pearsonr(self.vertical_signal_stack, self.up_template) 108 | self.down_corr = scipy.stats.pearsonr(self.vertical_signal_stack, self.down_template) 109 | self.blink_corr = scipy.stats.pearsonr(self.vertical_signal_stack, self.blink_template) 110 | 111 | if self.left_corr[0] > 0.9: 112 | self.status = "left" 113 | if self.right_corr[0] > 0.9: 114 | self.status = "right" 115 | if self.up_corr[0] > 0.9: 116 | self.status = "up" 117 | if self.down_corr[0] > 0.9: 118 | self.status = "down" 119 | if self.blink_corr[0] > 0.9: 120 | self.status = "blink" 121 | 122 | return self.status 123 | 124 | if __name__ == "__main__": 125 | classify.treshold(50) 126 | -------------------------------------------------------------------------------- /matlab_gui/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/matlab_gui/center.png -------------------------------------------------------------------------------- /matlab_gui/convert.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/matlab_gui/convert.py -------------------------------------------------------------------------------- /matlab_gui/eog_gui.m: -------------------------------------------------------------------------------- 1 | % 2 | % GUI for EOG Signal classification 3 | % 4 | 5 | raw_left = imread('left.png'); 6 | raw_right = imread('right.png'); 7 | raw_center = imread('center.png'); 8 | background = imread('white.png'); 9 | 10 | left = background; 11 | right = background; 12 | center = background; 13 | 14 | size(raw_left) 15 | size(raw_right) 16 | size(raw_center) 17 | size(background) 18 | size(left(20:99,17:266,:)) 19 | 20 | left(20:99,17:266,:) = raw_left; 21 | right(20:99,17:266,:) = raw_right; 22 | center(20:99,17:266,:) = raw_center; 23 | 24 | status = "left" ; 25 | 26 | position = [100 120]; 27 | text = ['left']; 28 | 29 | while 1 30 | if status == "left" 31 | final = insertText(left,[100 120],['left'],'FontSize',32,'BoxOpacity',0); 32 | imshow(final); 33 | end 34 | if status == "right" 35 | final = insertText(right,[90 120],['right'],'FontSize',32,'BoxOpacity',0); 36 | imshow(final); 37 | end 38 | if status == "center" 39 | final = insertText(center,[80 120],['center'],'FontSize',32,'BoxOpacity',0); 40 | imshow(final); 41 | end 42 | pause(0.05); 43 | end -------------------------------------------------------------------------------- /matlab_gui/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/matlab_gui/left.png -------------------------------------------------------------------------------- /matlab_gui/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/matlab_gui/right.png -------------------------------------------------------------------------------- /matlab_gui/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/matlab_gui/white.png -------------------------------------------------------------------------------- /maze.py: -------------------------------------------------------------------------------- 1 | import turtle 2 | 3 | PART_OF_PATH = 'O' 4 | TRIED = '.' 5 | OBSTACLE = '+' 6 | DEAD_END = '-' 7 | 8 | class Maze: 9 | def __init__(self,mazeFileName): 10 | rowsInMaze = 0 11 | columnsInMaze = 0 12 | self.mazelist = [] 13 | mazeFile = open(mazeFileName,'r') 14 | rowsInMaze = 0 15 | for line in mazeFile: 16 | rowList = [] 17 | col = 0 18 | for ch in line[:-1]: 19 | rowList.append(ch) 20 | if ch == 'S': 21 | self.startRow = rowsInMaze 22 | self.startCol = col 23 | col = col + 1 24 | rowsInMaze = rowsInMaze + 1 25 | self.mazelist.append(rowList) 26 | columnsInMaze = len(rowList) 27 | 28 | self.rowsInMaze = rowsInMaze 29 | self.columnsInMaze = columnsInMaze 30 | self.xTranslate = -columnsInMaze/2 31 | self.yTranslate = rowsInMaze/2 32 | self.t = turtle.Turtle() 33 | self.t.shape('turtle') 34 | self.wn = turtle.Screen() 35 | self.wn.setworldcoordinates(-(columnsInMaze-1)/2-.5,-(rowsInMaze-1)/2-.5,(columnsInMaze-1)/2+.5,(rowsInMaze-1)/2+.5) 36 | 37 | def drawMaze(self): 38 | self.t.speed(10) 39 | self.wn.tracer(0) 40 | for y in range(self.rowsInMaze): 41 | for x in range(self.columnsInMaze): 42 | if self.mazelist[y][x] == OBSTACLE: 43 | self.drawCenteredBox(x+self.xTranslate,-y+self.yTranslate,'orange') 44 | self.t.color('black') 45 | self.t.fillcolor('blue') 46 | self.wn.update() 47 | self.wn.tracer(1) 48 | 49 | def drawCenteredBox(self,x,y,color): 50 | self.t.up() 51 | self.t.goto(x-.5,y-.5) 52 | self.t.color(color) 53 | self.t.fillcolor(color) 54 | self.t.setheading(90) 55 | self.t.down() 56 | self.t.begin_fill() 57 | for i in range(4): 58 | self.t.forward(1) 59 | self.t.right(90) 60 | self.t.end_fill() 61 | 62 | def moveTurtle(self,x,y): 63 | self.t.up() 64 | self.t.setheading(self.t.towards(x+self.xTranslate,-y+self.yTranslate)) 65 | self.t.goto(x+self.xTranslate,-y+self.yTranslate) 66 | 67 | def dropBreadcrumb(self,color): 68 | self.t.dot(10,color) 69 | 70 | def updatePosition(self,row,col,val=None): 71 | if val: 72 | self.mazelist[row][col] = val 73 | self.moveTurtle(col,row) 74 | 75 | if val == PART_OF_PATH: 76 | color = 'green' 77 | elif val == OBSTACLE: 78 | color = 'red' 79 | elif val == TRIED: 80 | color = 'black' 81 | elif val == DEAD_END: 82 | color = 'red' 83 | else: 84 | color = None 85 | 86 | if color: 87 | self.dropBreadcrumb(color) 88 | 89 | def isExit(self,row,col): 90 | return (row == 0 or 91 | row == self.rowsInMaze-1 or 92 | col == 0 or 93 | col == self.columnsInMaze-1 ) 94 | 95 | def __getitem__(self,idx): 96 | return self.mazelist[idx] 97 | 98 | 99 | def searchFrom(maze, startRow, startColumn): 100 | # try each of four directions from this point until we find a way out. 101 | # base Case return values: 102 | # 1. We have run into an obstacle, return false 103 | maze.updatePosition(startRow, startColumn) 104 | if maze[startRow][startColumn] == OBSTACLE : 105 | return False 106 | # 2. We have found a square that has already been explored 107 | if maze[startRow][startColumn] == TRIED or maze[startRow][startColumn] == DEAD_END: 108 | return False 109 | # 3. We have found an outside edge not occupied by an obstacle 110 | if maze.isExit(startRow,startColumn): 111 | maze.updatePosition(startRow, startColumn, PART_OF_PATH) 112 | return True 113 | maze.updatePosition(startRow, startColumn, TRIED) 114 | # Otherwise, use logical short circuiting to try each direction 115 | # in turn (if needed) 116 | found = searchFrom(maze, startRow-1, startColumn) or \ 117 | searchFrom(maze, startRow+1, startColumn) or \ 118 | searchFrom(maze, startRow, startColumn-1) or \ 119 | searchFrom(maze, startRow, startColumn+1) 120 | if found: 121 | maze.updatePosition(startRow, startColumn, PART_OF_PATH) 122 | else: 123 | maze.updatePosition(startRow, startColumn, DEAD_END) 124 | return found 125 | 126 | 127 | myMaze = Maze('maze2.txt') 128 | myMaze.drawMaze() 129 | myMaze.updatePosition(myMaze.startRow,myMaze.startCol) 130 | 131 | searchFrom(myMaze, myMaze.startRow, myMaze.startCol) 132 | -------------------------------------------------------------------------------- /maze2.txt: -------------------------------------------------------------------------------- 1 | 2 | ++++++++++++++++++++++ 3 | + + ++ ++ + 4 | + ++++++++++ 5 | + + ++ ++++ +++ ++ 6 | + + + + ++ +++ + 7 | + ++ ++ + + 8 | +++++ + + ++ + + 9 | +++++ +++ + + ++ + 10 | + + + S+ + + 11 | +++++ + + + + + + 12 | ++++++++++++++++++++++ 13 | -------------------------------------------------------------------------------- /plot.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import matplotlib.animation as animation 3 | import time 4 | 5 | class graph: 6 | 7 | def __init__(self): 8 | self.fig = plt.figure() 9 | self.ax1 = self.fig.add_subplot(1,1,1) 10 | self.x = '0' 11 | self.y = '0' 12 | 13 | def animate(self,i): 14 | #global self.x, self.y 15 | self.filedata = open("sampleText.txt","a+") 16 | self.x = str(int(self.x) + 1) 17 | self.y = str(int(self.y) + 2) 18 | self.filedata.write(self.x+","+self.y+"\n") 19 | self.filedata.close() 20 | self.pullData = open("sampleText.txt","r").read() 21 | self.dataArray = pullData.split('\n') 22 | self.xar = [] 23 | self.yar = [] 24 | for eachLine in self.dataArray: 25 | if len(eachLine)>1: 26 | self.x,self.y = eachLine.split(',') 27 | self.xar.append(int(self.x)) 28 | self.yar.append(int(self.y)) 29 | self.ax1.clear() 30 | self.ax1.plot(self.xar,self.yar) 31 | 32 | def start(self): 33 | self.ani = animation.FuncAnimation(self.fig, self.animate, interval=1000) 34 | plt.show() -------------------------------------------------------------------------------- /sampleText.txt: -------------------------------------------------------------------------------- 1 | 8,10 2 | 9,12 3 | 10,14 4 | 11,16 5 | 12,18 6 | -------------------------------------------------------------------------------- /template_match.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | 4 | def match(img_rgb): 5 | 6 | img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY) 7 | 8 | left = cv2.imread('kohli.jpeg', 0) 9 | #template = cv2.resize(template,(70,70)) 10 | w, h = left.shape[::-1] 11 | right = cv2.imread('kohli.jpeg', 0) 12 | up = cv2.imread('kohli.jpeg', 0) 13 | down = cv2.imread('kohli.jpeg', 0) 14 | blink = cv2.imread('kohli.jpeg', 0) 15 | 16 | res_left = cv2.matchTemplate(img_gray,left,cv2.TM_CCOEFF_NORMED) #TM_SQDIFF-Best Match///TM_CCOEFF_NORMED-Threshold Match 17 | res_right = cv2.matchTemplate(img_gray,right,cv2.TM_CCOEFF_NORMED) #TM_SQDIFF-Best Match///TM_CCOEFF_NORMED-Threshold Match 18 | res_up = cv2.matchTemplate(img_gray,up,cv2.TM_CCOEFF_NORMED) #TM_SQDIFF-Best Match///TM_CCOEFF_NORMED-Threshold Match 19 | res_down = cv2.matchTemplate(img_gray,down,cv2.TM_CCOEFF_NORMED) #TM_SQDIFF-Best Match///TM_CCOEFF_NORMED-Threshold Match 20 | res_blink = cv2.matchTemplate(img_gray,blink,cv2.TM_CCOEFF_NORMED) #TM_SQDIFF-Best Match///TM_CCOEFF_NORMED-Threshold Match 21 | 22 | left_threshold = 0.8 23 | right_threshold = 0.8 24 | up_threshold = 0.8 25 | down_threshold = 0.8 26 | blink_threshold = 0.8 27 | 28 | loc_left = np.where( res_left >= left_threshold) 29 | loc_right = np.where( res_right >= right_threshold) 30 | loc_up = np.where( res_up >= up_threshold) 31 | loc_down = np.where( res_down >= down_threshold) 32 | loc_blink = np.where( res_blink >= blink_threshold) 33 | 34 | for pt in zip(*loc[::-1]): 35 | cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 1) 36 | for pt in zip(*loc[::-1]): 37 | cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (255,0,0), 1) 38 | for pt in zip(*loc[::-1]): 39 | cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,255,0), 1) 40 | for pt in zip(*loc[::-1]): 41 | cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,255,255), 1) 42 | for pt in zip(*loc[::-1]): 43 | cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (255,255,0), 1) 44 | 45 | return img_rgb 46 | 47 | if __name__ == "__main__": 48 | match(cv2.imread('crowd.jpg')) 49 | -------------------------------------------------------------------------------- /values.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdbharath/oculo-control/c956f0bf97d4d4de137ed7e682f73ce797375ff7/values.txt --------------------------------------------------------------------------------