Skip to content

Commit

Permalink
Merge pull request #1 from haoershi/main
Browse files Browse the repository at this point in the history
update unit tests
  • Loading branch information
haoershi authored Dec 5, 2023
2 parents 9a02436 + 9de8a67 commit 300872f
Show file tree
Hide file tree
Showing 86 changed files with 29,577 additions and 29,842 deletions.
28 changes: 22 additions & 6 deletions .github/workflows/matlab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ name: MATLAB Unit Tests

on:
push:
branches:
- main
branches: [ "main" ]
paths:
- './matlab/**'
- 'matlab/**'
pull_request:
branches: [ "main" ]
paths:
- 'matlab/**'
workflow_dispatch:

jobs:
Expand All @@ -20,10 +23,23 @@ jobs:
uses: matlab-actions/setup-matlab@v1
with:
matlab-version: R2021b # Adjust to your MATLAB version
license-server: your-license-server-address # Optional if using a network license

- name: Run MATLAB Unit Tests
env:
IEEG_USERNAME: ${{ secrets.IEEG_USERNAME }}
IEEG_PASSWORD: ${{ secrets.IEEG_PASSWORD }}
run: |
matlab -batch "runtests('test','IncludeSubfolders',true)"
working-directory: ./matlab
matlab -batch "import matlab.unittest.TestRunner; import matlab.unittest.TestSuite; import matlab.unittest.plugins.XMLPlugin; suite = testsuite('matlab/test','IncludeSubfolders',true); runner = TestRunner.withNoPlugins; xmlFile = 'matlab/test-results.xml'; p = XMLPlugin.producingJUnitFormat(xmlFile); runner.addPlugin(p); result = runner.run(suite); if any([result.Failed]) exit(1); end"
- name: Upload Test Results
uses: actions/upload-artifact@v2
with:
name: test-results
path: matlab/test-results.xml

- name: Display Test Results
uses: mikepenz/action-junit-report@v4
if: success() || failure() # always run even if the previous step fails
with:
report_paths: 'matlab/test-results.xml'

13 changes: 8 additions & 5 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ on:
push:
branches: [ "main" ]
paths:
- './python/**'
- 'python/**'
pull_request:
branches: [ "main" ]
paths:
- './python/**'
# workflow_dispatch:
- 'python/**'
workflow_dispatch:

jobs:
build:
Expand All @@ -34,8 +34,11 @@ jobs:
python -m pip install --upgrade pip
python -m pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
working-directory: ./python
working-directory: python
- name: Test with pytest
env:
IEEG_USERNAME: ${{ secrets.IEEG_USERNAME }}
IEEG_PASSWORD: ${{ secrets.IEEG_PASSWORD }}
run: |
pytest
working-directory: ./python
working-directory: python
23 changes: 16 additions & 7 deletions matlab/iEEGData.m
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ The ending time (in seconds) of the data clip.
dura
data
fs
nchs
ch_names
ref_chnames
raw
Expand Down Expand Up @@ -94,6 +95,8 @@ The ending time (in seconds) of the data clip.
obj.data = p.Results.data;
obj.fs = p.Results.fs;
obj.ch_names = p.Results.ch_names;
assert(size(obj.data,2)==length(obj.ch_names));
obj.nchs = length(obj.ch_names);
obj.ref_chnames = {};
obj.index = [];
obj.power = struct();
Expand All @@ -108,6 +111,7 @@ function download(obj, user)
obj.data = output.values;
obj.fs = output.fs;
obj.ch_names = output.chLabels;
obj.nchs = length(obj.ch_names);
obj.raw = obj.data;
obj.raw_chs = obj.ch_names;
obj.username = user.usr;
Expand Down Expand Up @@ -142,6 +146,7 @@ function reject_nonieeg(obj)
obj.nonieeg = find_non_ieeg(obj.ch_names);
obj.data = obj.data(:, ~obj.nonieeg);
obj.ch_names = obj.ch_names(~obj.nonieeg);
obj.nchs = length(obj.ch_names);
obj.history{end + 1} = 'reject_nonieeg';
end

Expand All @@ -151,6 +156,7 @@ function reject_artifact(obj)
[obj.bad, obj.reject_details] = identify_bad_chs(obj.data, obj.fs);
obj.data = obj.data(:, ~obj.bad);
obj.ch_names = obj.ch_names(~obj.bad);
obj.nchs = length(obj.ch_names);
obj.history{end + 1} = 'reject_artifact';
end

Expand Down Expand Up @@ -196,9 +202,9 @@ function filter(obj, varargin)
end
end
p = inputParser;
addOptional(p, 'low_freq', 1, @isnumeric);
addOptional(p, 'high_freq', 120, @isnumeric);
addOptional(p, 'notch_freq', 60, @isnumeric);
addOptional(p, 'low_freq', defaults{1}, @isnumeric);
addOptional(p, 'high_freq', defaults{2}, @isnumeric);
addOptional(p, 'notch_freq', defaults{3}, @isnumeric);
parse(p, varargin{:});
low_freq = p.Results.low_freq;
high_freq = p.Results.high_freq;
Expand All @@ -224,6 +230,7 @@ function bipolar(obj)
obj.data(:, inds) = [];
obj.ch_names(inds) = [];
obj.ref_chnames(inds) = [];
obj.nchs = length(obj.ch_names);
obj.history{end + 1} = 'bipolar';
end

Expand Down Expand Up @@ -271,6 +278,7 @@ function laplacian(obj, varargin)
obj.data(:, inds) = [];
obj.ch_names(inds) = [];
obj.ref_chnames(inds) = [];
obj.nchs = length(obj.ch_names);
obj.history{end + 1} = 'laplacian';
end

Expand Down Expand Up @@ -328,6 +336,7 @@ function reref(obj, ref, varargin)
obj.ch_names(inds) = [];
obj.ref_chnames(inds) = [];
end
obj.nchs = length(obj.ch_names);
obj.history{end + 1} = ['reref-', ref];
end

Expand Down Expand Up @@ -356,7 +365,7 @@ If False (default), return the absolute power.
%}
freqs = default_freqs;
p = inputParser;
defaults = {freqs,nan,false};
defaults = {freqs,[],false};
for i = 1:length(varargin)
if isempty(varargin{i})
varargin{i} = defaults{i};
Expand All @@ -373,7 +382,7 @@ If False (default), return the absolute power.
nbands = size(band,1);
for i = 1:nbands
obj.power(i).freq = band(i,:);
obj.power(i).power = bandpower(obj.data, obj.fs, obj.power(i).freq, window, relative);
obj.power(i).power = band_power(obj.data, obj.fs, obj.power(i).freq, window, relative);
end
obj.history{end + 1} = 'bandpower';
end
Expand Down Expand Up @@ -634,7 +643,6 @@ function heatmap_settings(obj,varargin)
% or apply customized settings
% customized color could be 1:colormap of size nX3,
% 2:cell/string array of color hex codes
nchs = size(obj.data,2); % add formula here

% default colors
blue = hex2rgb('#3c5488');red = hex2rgb('#a5474e'); white = [1,1,1];
Expand All @@ -653,7 +661,7 @@ function heatmap_settings(obj,varargin)
h.CellLabelColor = 'none';
% fix figure size
% Calculate approximate figure size
figureHeight = nchs * (h.FontSize) / 0.8;
figureHeight = obj.nchs * (h.FontSize) / 0.8;
figureWidth = figureHeight + 50;
h.Position = [100, 100, figureWidth, figureHeight];
% fix colormap
Expand Down Expand Up @@ -714,6 +722,7 @@ function reverse(obj)
% Reverse by one processing step.
obj.data = obj.rev_data;
obj.ch_names = obj.rev_chs;
obj.nchs = length(obj.ch_names);
obj.ref_chnames = obj.rev_refchs;
obj.history{end + 1} = 'reverse';
end
Expand Down
17 changes: 14 additions & 3 deletions matlab/iEEGPreprocess.m
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,15 @@ function load_data(obj, path, varargin)
>>> session.load_data("/path/to/saved_data.mat", replace = True)
%}
p = inputParser;
defaults = {false,true};
for i = 1:length(varargin)
if isempty(varargin{i})
varargin{i} = defaults{i};
end
end
addRequired(p, 'path', @(x) isstring(x) || ischar(x));
addOptional(p, 'replace',false, @(x) ismember(x,[0,1]));
addOptional(p, 'default_folder',true, @(x) ismember(x,[0,1]));
addOptional(p, 'replace',defaults{1}, @(x) ismember(x,[0,1]));
addOptional(p, 'default_folder',defaults{2}, @(x) ismember(x,[0,1]));
parse(p, path, varargin{:});
path = p.Results.path;
replace = p.Results.replace;
Expand Down Expand Up @@ -205,7 +211,11 @@ The stop time (in seconds) of the data segment to download.
username = p.Results.username;

if isempty(obj.user)
obj.login('username', username);
if ~isempty(username)
obj.login(username);
else
obj.login()
end
end
data = iEEGData(filename, start, stop, select_elecs, ignore_elecs);
data.download(obj.user);
Expand All @@ -224,6 +234,7 @@ function remove_data(obj, dataIndex)
% Remove data instance from current session.
obj.meta(dataIndex, :) = [];
obj.datasets(dataIndex) = [];
obj.num_data = size(obj.meta, 1);
end

function save(obj, filename, varargin)
Expand Down
39 changes: 39 additions & 0 deletions matlab/test/authTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
%% Test Class Definition
classdef authTest < matlab.unittest.TestCase
%% Test Method Block
methods (Test)
% includes unit test functions
function testAuth(testCase)
% see https://www.mathworks.com/help/matlab/matlab_prog/types-of-qualifications.html
% for qualification method
% addpath(genpath('./../..')); % always add to ensure loading of other files/func
paths;
assert(exist(USER_DIR,'dir') == 7);
if ~isempty(getenv('GITHUB_ACTIONS'))
% Use GitHub secrets to retrieve credentials
config.usr = getenv('IEEG_USERNAME');
config.pwd = getenv('IEEG_PASSWORD');
pwd_path = fullfile(USER_DIR, strcat(config.usr(1:3), '_ieeglogin.bin'));
PATH = IEEGSession.createPwdFile(config.usr, config.pwd, pwd_path);
config.pwd = strcat(config.usr(1:3), '_ieeglogin.bin');
file_name = fullfile(USER_DIR, strcat(config.usr(1:3), '_config.json'));
jsonStr = jsonencode(config);
fid = fopen(file_name, 'w');
fprintf(fid, '%s', jsonStr);
fclose(fid);
fprintf('-- -- IEEG user config file saved -- --\n');
user_data_dir = fullfile(DATA_DIR, config.usr(1:3));
end
files = dir(fullfile(USER_DIR,'*.json'));
if isempty(files)
error('Login info unavailable.')
else
for i = 1:length(files)
f = fullfile(files(i).folder, files(i).name);
login = jsondecode(fileread(f));
assert(exist(fullfile(USER_DIR,login.pwd)))
end
end
end
end
end
74 changes: 74 additions & 0 deletions matlab/test/bandPowerTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
classdef bandPowerTest < matlab.unittest.TestCase

methods (Test)
function testBandPowerAbsolute(tc)
addpath(genpath('./..'));
% Generate example data with known band power in the alpha range
fs = 250;
duration = 10; % seconds
time = [0:1/fs:duration-1/fs]';
alpha_band = [8, 12];
alpha_power = 0.5; % Known alpha band power

data = zeros(length(time), 1);
data = data + sin(2*pi*10*time) + 0.1 * randn(size(time)); % Alpha band (10 Hz)

% Call the function under test
bp = band_power(data, fs, alpha_band);

% Assertions
tc.verifyEqual(bp, alpha_power, 'Alpha band power mismatch', 'AbsTol', 0.1); % Allow some tolerance

% Generate example data
data = randn(100, 5);
fs = 250;
band = [1, 30];

% Call the function under test
bp = band_power(data, fs, band);

% Assertions
tc.verifyEqual(size(bp), [1, size(data, 2)], 'Band power size mismatch');
tc.verifyGreaterThan(bp, 0, 'Band power should be greater than zero');
end

function testBandPowerRelative(tc)
addpath(genpath('./..'));
% Generate example data with known band power in the alpha range
fs = 250;
duration = 10; % seconds
time = [0:1/fs:duration-1/fs]';

% Simulate data with power in two frequency bands
alpha_band = [8, 12];
beta_band = [15, 30];
alpha_power = 0.6; % Known power ratio
beta_power = 0.4; % Known power ratio

data_alpha = sin(2*pi*10*time); % Alpha band (10 Hz)
data_beta = sin(2*pi*20*time); % Beta band (20 Hz)

data = alpha_power * data_alpha + beta_power * data_beta + 0.1 *randn(size(time));
% Call the function under test with relative power
bp = band_power(data, fs, alpha_band, [], true);

% Assertions
tc.verifyEqual(bp, alpha_power, 'Relative alpha band power mismatch', 'AbsTol', 0.1); % Allow some tolerance


% Generate example data
data = randn(100, 5);
fs = 250;
band = [1, 30];

% Call the function under test with relative power
bp = band_power(data, fs, band, [], true);

% Assertions
tc.verifyEqual(size(bp), [1, size(data, 2)], 'Relative band power size mismatch');
tc.verifyGreaterThanOrEqual(bp, 0, 'Relative band power should be greater than or equal to zero');
tc.verifyLessThanOrEqual(bp, 1, 'Relative band power should be less than or equal to one');
end
end

end
17 changes: 17 additions & 0 deletions matlab/test/bandpassFilterTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
classdef bandpassFilterTest < matlab.unittest.TestCase

methods (Test)
function test_BandpassFilter(tc)
% Test with default parameters

addpath(genpath('./..')); % always add to ensure loading of other files/func
paths;
load(fullfile(TESTDATA_DIR,'sampleData.mat'));
values = bandpass_filter(old_values, fs);
tc.verifyEqual(size(values), size(old_values), 'Output size mismatch');
values = bandpass_filter(old_values, fs, 5, 50, 6);
tc.verifyEqual(size(values), size(old_values), 'Output size mismatch');
end
end

end
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
%% Test Class Definition
classdef refBipolarTest < matlab.unittest.TestCase
classdef bipolarTest < matlab.unittest.TestCase
% This is a test class defined for the decompose_labels function
% Import test cases from decompLabel_testInput.csv file, which include
% input, expected output, and notes describes the test scenairo of the
Expand All @@ -9,14 +9,19 @@
%% Test Method Block
methods (Test)
% includes unit test functions
function testrefBipolar(testCase)
function testBipolar(testCase)
% This part test for wrong input types
% see https://www.mathworks.com/help/matlab/matlab_prog/types-of-qualifications.html
% for qualification method
addpath(genpath('./../..')); % always add to ensure loading of other files/func
load refBipolar_testInput.mat;
addpath(genpath('./..')); % always add to ensure loading of other files/func
paths;
load(fullfile(TESTDATA_DIR,'reref_testInput.mat'));
f = @() bipolar(old_values,labels);
testCase.verifyWarningFree(f)
[out_values,out_labels] = bipolar(old_values,labels);
n = length(find(strcmp('-',out_labels)));
testCase.verifyEqual(n,15);

end
end
end
Loading

0 comments on commit 300872f

Please sign in to comment.