- 08 Apr 2022
-
PDF
Kernel tasks SDK
- Updated on 08 Apr 2022
-
PDF
The Kernel SDK allows you to emit your own task events to be recorded alongside Flow data in the same dataset.
Specification
To send events to our system, send a multicast packet containing JSON to 239.128.35.86:7891.
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 nanoseconds |
event | string | The name of the event |
value | string | The value of the event |
If tasks are not running on the provided Kernel Flow PC, ensure that:
- The multicast packets can reach the Kernel Flow PC 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": 1.6416027480329585e+18, "event": "start_experiment", "value": "1"}
{"id": 2, "timestamp": 1.6416027480330596e+18, "event": "experiment_type", "value": "finger_tapping"}
{"id": 3, "timestamp": 1.6416027480496115e+18, "event": "start_rest", "value": "1"}
{"id": 4, "timestamp": 1.641602771773098e+18, "event": "end_rest", "value": "1"}
{"id": 5, "timestamp": 1.6416027717732467e+18, "event": "start_block", "value": "1"}
{"id": 6, "timestamp": 1.6416027717733532e+18, "event": "block_type", "value": "right"}
{"id": 7, "timestamp": 1.6416027768250962e+18, "event": "end_block", "value": "1"}
{"id": 8, "timestamp": 1.6416027768454572e+18, "event": "start_rest", "value": "2"}
{"id": 9, "timestamp": 1.6416027970639432e+18, "event": "end_rest", "value": "2"}
{"id": 10, "timestamp": 1.6416027970640435e+18, "event": "start_block", "value": "2"}
{"id": 11, "timestamp": 1.6416027970641178e+18, "event": "block_type", "value": "left"}
{"id": 12, "timestamp": 1.6416028020961134e+18, "event": "end_block", "value": "2"}
...
{"id": 248, "timestamp": 1.6416038780122324e+18, "event": "end_experiment", "value": "1"}
Which 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.
Code Examples
import socket
from time import time
import json
id=1
timestamp = time()
data_to_send = {
"id": id,
"timestamp": int(timestamp * 1e9),
"event": "start_trial",
"value": "5",
}
event = json.dumps(data_to_send).encode("utf-8")
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(event, ("239.128.35.86", 7891))
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 {
Socket socket;
int id = 0;
DateTime epoch = new DateTime(1970, 1, 1);
public KernelEvents(string ip, int port) {
this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress ipAddress = IPAddress.Parse(ip);
this.socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(ipAddress));
this.socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 2);
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, port);
this.socket.Connect(ipEndPoint);
}
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);
this.socket.Send(bytes, bytes.Length, SocketFlags.None);
}
}
public class Program {
public static void Main(string[] args) {
KernelEvents kernelEvents = new KernelEvents("239.128.35.86", 7891);
kernelEvents.send(new KernelEvent {
@event="start_trial",
value="5",
});
}
}
Matlab: https://www.mathworks.com/help/instrument/send-and-receive-multicast-data-packets-using-udp.html