Skip to content

Commit ca3f55a

Browse files
Merge pull request #305 from JohnGriffiths/add_kf_device
Add kf device
2 parents a6240d4 + b9d5c55 commit ca3f55a

File tree

2 files changed

+133
-8
lines changed

2 files changed

+133
-8
lines changed

eegnb/devices/eeg.py

Lines changed: 128 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import time
1010
import logging
1111
from time import sleep
12+
from datetime import datetime
1213
from multiprocessing import Process
1314

1415
import numpy as np
@@ -26,6 +27,9 @@
2627
EEG_CHANNELS,
2728
)
2829

30+
import socket, json, logging, struct
31+
from time import time
32+
2933

3034
logger = logging.getLogger(__name__)
3135

@@ -54,6 +58,7 @@
5458
]
5559

5660

61+
5762
class EEG:
5863
device_name: str
5964
stream_started: bool = False
@@ -66,8 +71,8 @@ def __init__(
6671
mac_addr=None,
6772
other=None,
6873
ip_addr=None,
69-
ch_names=None
70-
):
74+
ch_names=None,
75+
make_logfile=False):
7176
"""The initialization function takes the name of the EEG device and determines whether or not
7277
the device belongs to the Muse or Brainflow families and initializes the appropriate backend.
7378
@@ -84,6 +89,7 @@ def __init__(
8489
self.mac_address = mac_addr
8590
self.ip_addr = ip_addr
8691
self.other = other
92+
self.make_logfile = make_logfile # currently only used for kf
8793
self.backend = self._get_backend(self.device_name)
8894
self.initialize_backend()
8995
self.n_channels = len(EEG_INDICES[self.device_name])
@@ -92,23 +98,30 @@ def __init__(
9298
self.ch_names = ch_names
9399

94100
def initialize_backend(self):
101+
# run this at initialization to get some
102+
# stream metadata into the eeg class
95103
if self.backend == "brainflow":
96104
self._init_brainflow()
97105
self.timestamp_channel = BoardShim.get_timestamp_channel(self.brainflow_id)
98106
elif self.backend == "muselsl":
99107
self._init_muselsl()
100-
self._muse_get_recent() # run this at initialization to get some
101-
# stream metadata into the eeg class
108+
self._muse_get_recent()
109+
elif self.backend == "kernelflow":
110+
self._init_kf()
102111

103112
def _get_backend(self, device_name):
104113
if device_name in brainflow_devices:
105114
return "brainflow"
106115
elif device_name in ["muse2016", "muse2", "museS"]:
107116
return "muselsl"
117+
elif device_name in ["kernelflow"]:
118+
return "kernelflow"
119+
108120

109121
#####################
110122
# MUSE functions #
111123
#####################
124+
112125
def _init_muselsl(self):
113126
# Currently there's nothing we need to do here. However keeping the
114127
# option open to add things with this init method.
@@ -186,9 +199,11 @@ def _muse_get_recent(self, n_samples: int = 256, restart_inlet: bool = False):
186199
df = pd.DataFrame(samples, index=timestamps, columns=ch_names)
187200
return df
188201

202+
189203
##########################
190204
# BrainFlow functions #
191205
##########################
206+
192207
def _init_brainflow(self):
193208
"""This function initializes the brainflow backend based on the input device name. It calls
194209
a utility function to determine the appropriate USB port to use based on the current operating system.
@@ -398,10 +413,111 @@ def _brainflow_get_recent(self, n_samples=256):
398413
# print (df)
399414
return df
400415

416+
417+
418+
###########################
419+
# Kernel Flow functions #
420+
###########################
421+
422+
423+
def _init_kf(self):
424+
425+
self._notes = None #muse_recent_inlet = None
426+
427+
# Grab the init time for tracking
428+
dtstr = str(datetime.now()).replace(' ', '_').split('.')[0].replace(':', '-')
429+
430+
# Initiate connection to trigger-recording port
431+
host = 'localhost' # could be another computer on network with a visible IP address?
432+
port = 6767
433+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
434+
sock.connect((host, port))
435+
self.kf_host = host
436+
self.kf_port = port
437+
self.kf_socket = sock
438+
439+
# Triggers history list
440+
self.kf_triggers_history = []
441+
self.kf_triggers_history.append('Initiated connection: %s' %dtstr)
442+
443+
# Optionally maintain a logfile
444+
if self.make_logfile:
445+
self.kf_logfile_fname = 'eegexpy_kf_logfile__%s.txt' % dtstr
446+
self.kf_logfile_handle = open(self.kf_logfile_fname, 'a')
447+
self.kf_logfile_handle.write('KF TRIGGERS LOGFILE %s \n\n' %dtstr)
448+
449+
450+
def _start_kf(self): #:, duration):
451+
452+
kf_start_timestamp = int(time()*1e6)
453+
454+
# Send first data packet
455+
self.kf_evnum = 0
456+
data_to_send = {"id": self.kf_evnum,
457+
"timestamp": kf_start_timestamp,
458+
"event": "start_experiment",
459+
"value": "0"
460+
}
461+
self._kf_sendeventinfo(data_to_send)
462+
463+
# Update logfile text
464+
self.kf_triggers_history.append({'kf_evnum': '%s' %self.kf_evnum,
465+
'kf_start_timestamp': kf_start_timestamp,
466+
'packet_sent': data_to_send})
467+
468+
469+
def _kf_push_sample(self, timestamp, marker, marker_name):
470+
471+
# Send trigger
472+
self.kf_evnum+=1
473+
kf_trigger_timestamp = int(time()*1e6)
474+
data_to_send = {
475+
"id": self.kf_evnum, #event_id,
476+
"timestamp": kf_trigger_timestamp, # timestamp
477+
"event": 'start_trial', #marker_name, #event_name,
478+
"value": str(marker), #str(marker_name),
479+
}
480+
self._kf_sendeventinfo(data_to_send)
481+
482+
# Update logfile text
483+
self.kf_triggers_history.append({'kf_evnum': '%s' %self.kf_evnum,
484+
'kf_trigger_timestamp': kf_trigger_timestamp,
485+
'experiment_timestamp': timestamp,
486+
'packet_sent': data_to_send})
487+
488+
489+
def _stop_kf(self):
490+
491+
self.kf_evnum+=1
492+
kf_stop_timestamp = int(time()*1e6)
493+
data_to_send = {
494+
"id": 3, #self.kf_evnum,
495+
"timestamp": kf_stop_timestamp,
496+
"event": "end_experiment",
497+
"value": "5"
498+
}
499+
self._kf_sendeventinfo(data_to_send)
500+
501+
self.kf_triggers_history.append({'kf_evnum': '%s' % self.kf_evnum,
502+
'kf_stop_timestamp': kf_stop_timestamp,
503+
'packet_sent': data_to_send})
504+
505+
if self.make_logfile:
506+
self.kf_logfile_handle.write(self.kf_triggers_history)
507+
self.kf_logfile_handle.close()
508+
509+
510+
def _kf_sendeventinfo(self, event_info):
511+
512+
event_info_pack = json.dumps(event_info).encode("utf-8")
513+
msg = struct.pack("!I", len(event_info_pack)) + event_info_pack
514+
self.kf_socket.sendall(msg)
515+
516+
401517
#################################
402518
# Highlevel device functions #
403519
#################################
404-
520+
405521
def start(self, fn, duration=None):
406522
"""Starts the EEG device based on the defined backend.
407523
@@ -416,8 +532,10 @@ def start(self, fn, duration=None):
416532
self.markers = []
417533
elif self.backend == "muselsl":
418534
self._start_muse(duration)
535+
elif self.backend == "kernelflow":
536+
self._start_kf()
419537

420-
def push_sample(self, marker, timestamp):
538+
def push_sample(self, marker, timestamp, marker_name=None):
421539
"""
422540
Universal method for pushing a marker and its timestamp to store alongside the EEG data.
423541
@@ -429,12 +547,16 @@ def push_sample(self, marker, timestamp):
429547
self._brainflow_push_sample(marker=marker)
430548
elif self.backend == "muselsl":
431549
self._muse_push_sample(marker=marker, timestamp=timestamp)
550+
elif self.backend == "kernelflow":
551+
self._kf_push_sample(marker=marker,timestamp=timestamp, marker_name=marker_name)
432552

433553
def stop(self):
434554
if self.backend == "brainflow":
435555
self._stop_brainflow()
436556
elif self.backend == "muselsl":
437557
pass
558+
elif self.backend == "kernelflow":
559+
self._stop_kf()
438560

439561
def get_recent(self, n_samples: int = 256):
440562
"""

eegnb/devices/utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"notion2": BoardShim.get_eeg_names(BoardIds.NOTION_2_BOARD.value),
2828
"crown": BoardShim.get_eeg_names(BoardIds.CROWN_BOARD.value),
2929
"freeeeg32": [f"eeg_{i}" for i in range(0, 32)],
30+
"kernelflow": []
3031
}
3132

3233
BRAINFLOW_CHANNELS = {
@@ -56,7 +57,8 @@
5657
"notion2": BoardShim.get_eeg_channels(BoardIds.NOTION_2_BOARD.value),
5758
"crown": BoardShim.get_eeg_channels(BoardIds.CROWN_BOARD.value),
5859
"freeeeg32": BoardShim.get_eeg_channels(BoardIds.FREEEEG32_BOARD.value),
59-
}
60+
"kernelflow": [],
61+
}
6062

6163
SAMPLE_FREQS = {
6264
"muse2016": 256,
@@ -78,7 +80,8 @@
7880
"notion2": BoardShim.get_sampling_rate(BoardIds.NOTION_2_BOARD.value),
7981
"crown": BoardShim.get_sampling_rate(BoardIds.CROWN_BOARD.value),
8082
"freeeeg32": BoardShim.get_sampling_rate(BoardIds.FREEEEG32_BOARD.value),
81-
}
83+
"kernelflow": [],
84+
}
8285

8386

8487
def create_stim_array(timestamps, markers):

0 commit comments

Comments
 (0)