Skip to content

Commit

Permalink
Version 1.1.0 (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberjunk authored Aug 21, 2024
1 parent 3d53800 commit 5e89ee4
Show file tree
Hide file tree
Showing 15 changed files with 1,209 additions and 258 deletions.
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# UKT - Neurofeedback
# NINFA

Small Matlab Framework for running Custom Neurofeedback Protocols on LSL Streams

Expand All @@ -14,12 +14,20 @@ Small Matlab Framework for running Custom Neurofeedback Protocols on LSL Streams

## Configuration

1. [Optional] Adjust `TYPE` of LSL input stream (default is for NIRS device)
2. Click `OPEN` to connect with LSL input stream
3. Configure `SETTINGS`, `ID` and `EPOCHS`
4. Click `START` to run a session
1. Select your device
2. [Optional] Adjust `TYPE` of LSL input stream
3. Click `OPEN` to connect with LSL input stream
4. Configure `SETTINGS`, `ID` and `EPOCHS`
5. Click `START` to run a session

![ukt-nf-settings](https://github.com/cyberjunk/ukt-nf/assets/780159/0690b24e-7a20-4357-bd0d-9171c880115d)
![ninfa](https://github.com/user-attachments/assets/7f8ba7ad-ea09-4d08-8384-57f182961430)

### DEVICE

* Select your device type and model from the available options.
* Add a device by creating a `.json` file for it in subfolder `devices`
* Take a look at the existing `nirs_nirx_nirsport2.json`
* The most important part is defining the LSL channels sent by the device

### LSL STREAM

Expand All @@ -38,10 +46,10 @@ Small Matlab Framework for running Custom Neurofeedback Protocols on LSL Streams

| Setting | Description |
|----------------------|------------------------------------------------------------------------------------------------|
| `SELECTED CHANNELS` | Comma separated list of LSL input channel numbers to use (others are ignored). |
| `PROTOCOL` | The Matlab file from folder `protocols` with algorithm executed on each window. |
| `CHANNELS` | Select LSL channels to use in the selected protocol |
| `WINDOW SIZE (S)` | Size of the sliding window in seconds. The window always contains last n seconds of samples. |
| `SESSION LENGTH (S)` | The session will automatically stop after this time. |
| `PROTOCOL` | The Matlab file from folder `protocols` with algorithm executed on each window. |

### ID

Expand Down Expand Up @@ -88,8 +96,9 @@ An epoch is a configurable timespan within a session.

* A protocol calculates a feedback value from an input window
* To add a protocol put the Matlab file in subfolder `protocols`
* See `example1.m` (returns a random feedback value)

* The `Gauss.m` example requires a NIRS device that sends at least one `HbO` channel with `μmol/L` unit
* The `RecordOnly.m` works with any device type and model and just records data

## Drift and Execution Times

* `DRIFT` shows current offset in playback schedule (`where we are` vs. `where we should be`)
Expand Down
66 changes: 66 additions & 0 deletions components/devices.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
classdef devices < handle
%Devices
% Detailed explanation goes here

properties (Constant)
types = ["NIRS", "EEG"]
end

properties
nirs struct = [];
eeg struct = [];
selected struct = struct([]);
end

methods
function self = devices()
self.reload();
end

function reload(self)
files = ls("devices/*.json");
for f = 1:size(files, 1)
file = files(f, 1:end);
json = jsondecode(fileread("./devices/" + file));
switch json.type
case "NIRS"
idx = length(self.nirs) + 1;
self.nirs(idx).name = json.name;
self.nirs(idx).type = json.type;
self.nirs(idx).lsl = json.lsl;
case "EEG"
idx = length(self.eeg) + 1;
self.eeg(idx).name = json.name;
self.eeg(idx).type = json.type;
self.eeg(idx).lsl = json.lsl;
otherwise
disp("Ignoring unknown device type");
end
end
end

function r = select(self, type, name)
switch type
case "NIRS"
for d = 1:length(self.nirs)
if self.nirs(d).name == name
self.selected = self.nirs(d);
r = true;
return
end
end
case "EEG"
for d = 1:length(self.eeg)
if self.eeg(d).name == name
self.selected = self.eeg(d);
r = true;
return
end
end
otherwise
warning("Ignoring unknown device type: " + type);
end
r = false;
end
end
end
67 changes: 67 additions & 0 deletions components/protocols.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
classdef protocols < handle
%Protocols
% Detailed explanation goes here

properties (Constant)
end

properties
list struct = []
selected struct = struct([]);
end

methods
function reload(self, device)
self.list = [];
self.selected = struct();
files = ls("protocols/*.m");
for f = 1:size(files, 1)
file = files(f, 1:end);
name = strtrim(erase(file, ".m"));
fh = feval(name);
req = fh.requires();
if ~self.iscompatible(req, device)
continue
end
idx = length(self.list) + 1;
self.list(idx).name = name;
self.list(idx).fh = fh;
self.list(idx).req = req;
end
end

function r = iscompatible(~, req, dev)
% check device type
if req.devicetype ~= "ANY" && req.devicetype ~= dev.type
r = false;
return
end
% check channel requirements
for idx = 1:length(req.channels)
found = 0;
for lslch = 1:length(dev.lsl.channels)
if req.channels(idx).type == dev.lsl.channels(lslch).type && ...
req.channels(idx).unit == dev.lsl.channels(lslch).unit
found = found + 1;
end
end
if found < req.channels(idx).min
r = false;
return
end
end
r = true;
end

function r = select(self, name)
for p = 1:length(self.list)
if self.list(p).name == name
self.selected = self.list(p);
r = true;
return
end
end
r = false;
end
end
end
Loading

0 comments on commit 5e89ee4

Please sign in to comment.