- 31 Jul 2024
- PDF
Kernel Tasks SDK
- Updated on 31 Jul 2024
- PDF
The Kernel SDK allows you to emit your own task events to be recorded alongside Flow2 data in the same dataset.
Specification
To send events to our system, send 2 TCP packets for each event to the computer being used to acquire Flow2 data on port 6767. The first packet should contain the byte size of the JSON payload represented as a network-endian/big-endian unsigned 4 byte integer. The second packet should contain the JSON bytes itself.
The fields in the JSON should be:
NAME | TYPE | NOTES |
---|---|---|
id | integer | An incrementing integer signifying a unique event identifier |
timestamp | integer | An epoch timestamp in microseconds |
event | string | The name of the event |
value | string | The value of the event |
If tasks are not running on the computer being used to acquire Flow2 data, ensure that:
- The TCP packets can reach the data acquisition computer over the network
- The two computers' clocks are synchronized via NTP or a similar protocol
Conventions
Context Setting Events
To take advantage of the export and analysis tools available in the Kernel Researcher Portal, structure your custom tasks according to the following conventions:
- The first event must be start_experiment.
- The last event must be end_experiment.
- A task must be structured using the following hierarchy of events: experiment > task > block > trial.
- Task, block, and trial are optional, but the hierarchy must be respected. (e.g. a block cannot be within a trial.)
- The value for these context events MUST be an integer that represents the ordinal index (block 1, block 2, ..., block N)
- Epochs (events with a non-zero duration) must be delineated by 2 events:
- start_{}
- end_{}
- A duration will be calculated from these and will appear in the task data exported in the SNIRF files.
Instantaneous Events
Events (with a zero duration) which you wish to record a timestamp for MUST start with event_.
Metadata Events
All other events (those which are not prefixed with start_, end_, or event_ are considered metadata and their timestamps are not included in the task data in exported SNIRF files.
Additional Notes
The value field of an event can be a dictionary, in which case its keys appear as separate columns (prefixed with the name of the event) in the task data in exported SNIRF files.
Data Example
A simple finger tapping paradigm may create this sequence of events:
{"id": 1, "timestamp": 1709500189972160, "event": "start_experiment", "value": "1"}
{"id": 2, "timestamp": 1709500189972160, "event": "experiment_type", "value": "finger_tapping"}
{"id": 3, "timestamp": 1709500189972160, "event": "start_rest", "value": "1"}
{"id": 4, "timestamp": 1709500189972169, "event": "end_rest", "value": "1"}
{"id": 5, "timestamp": 1709500189972169, "event": "start_block", "value": "1"}
{"id": 6, "timestamp": 1709500189972169, "event": "block_type", "value": "right"}
{"id": 7, "timestamp": 1709500189972175, "event": "end_block", "value": "1"}
{"id": 8, "timestamp": 1709500189972175, "event": "start_rest", "value": "2"}
{"id": 9, "timestamp": 1709500189972180, "event": "end_rest", "value": "2"}
{"id": 10, "timestamp": 1709500189972180, "event": "start_block", "value": "2"}
{"id": 11, "timestamp": 1709500189972180, "event": "block_type", "value": "left"}
{"id": 12, "timestamp": 1709500189972185, "event": "end_block", "value": "2"}
...
{"id": 248, "timestamp": 1709500246184622, "event": "end_experiment", "value": "1"}
This series of events would be transformed by the Kernel Analysis tools to the following flattened version, ready for analysis (and export)
timestamp | event | duration | experiment | experiment_type | rest | block | block_type |
---|---|---|---|---|---|---|---|
0.000287 | start_experiment | 1129.979274 | 1.0 | finger_tapping | NaN | NaN | nan |
0.016940 | start_rest | 23.723486 | 1.0 | finger_tapping | 1 | NaN | nan |
23.740575 | start_block | 5.051849 | 1.0 | finger_tapping | NaN | 1.0 | right |
28.812785 | start_rest | 20.218486 | 1.0 | finger_tapping | 2 | NaN | nan |
49.031372 | start_block | 5.032070 | 1.0 | finger_tapping | NaN | 2.0 | left |
Note that the timestamps are "zeroed" to the beginning of the first timestamp of the fNIRS data.
Task Events SDK Client
Kernel provides a Task SDK Client to make it easier to send events in the format described. See Kernel SDK for available languages and instructions.
Code Examples
import json
import socket
import struct
from time import time
# one time connection
host = 'acquisition.computer.host'
port = 6767
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
# then send events
id=1
timestamp = time()
data_to_send = {
"id": id,
"timestamp": int(timestamp * 1e6),
"event": "start_trial",
"value": "5",
}
event = json.dumps(data_to_send).encode("utf-8")
msg = struct.pack("!I", len(event)) + event
sock.sendall(msg)
using System;
using System.Net;
using System.Net.Sockets;
using Newtonsoft.Json;
using System.Text;
class KernelEvent {
public int id { get; set; }
public long timestamp { get; set; }
public string @event { get; set; }
public string value { get; set; }
}
class KernelEvents {
const int PORT = 6767;
NetworkStream nwStream;
int id = 0;
DateTime epoch = new DateTime(1970, 1, 1);
public KernelEvents(string ip) {
TcpClient client = new TcpClient(ip, PORT);
this.nwStream = client.GetStream();
}
public void send(KernelEvent kernelEvent) {
this.id++;
kernelEvent.id = this.id;
TimeSpan timestamp = DateTime.UtcNow - epoch;
kernelEvent.timestamp = (long)(timestamp.TotalMilliseconds * 1000);
string json = JsonConvert.SerializeObject(kernelEvent);
Console.WriteLine(json);
byte[] bytes = Encoding.UTF8.GetBytes(json);
byte[] size = BitConverter.GetBytes(bytes.Length);
if (BitConverter.IsLittleEndian) Array.Reverse(size);
this.nwStream.Write(size, 0, size.Length);
this.nwStream.Write(bytes, 0, bytes.Length);
}
}
public class Program {
public static void Main(string[] args) {
KernelEvents kernelEvents = new KernelEvents("acquisition.computer.ip");
kernelEvents.send(new KernelEvent {
@event="start_trial",
value="5",
});
}
}
Matlab: https://www.mathworks.com/help/instrument/write-and-read-data-over-the-tcpip-interface.html