From d0e706c4e6774b3ea224d3acbbe7cc85bfb8ca91 Mon Sep 17 00:00:00 2001 From: 4gwe Date: Mon, 16 Mar 2026 08:59:13 +0100 Subject: [PATCH 1/9] Fix pspm_convert_au2unit to work correctly with files and add a test --- src/pspm_convert_au2unit.m | 2 +- test/pspm_convert_au2unit_test.m | 178 ++++++++++++++++++++++++++++++- 2 files changed, 175 insertions(+), 5 deletions(-) diff --git a/src/pspm_convert_au2unit.m b/src/pspm_convert_au2unit.m index 55b6667ad..e15ebc25a 100644 --- a/src/pspm_convert_au2unit.m +++ b/src/pspm_convert_au2unit.m @@ -163,7 +163,7 @@ [sts, channeldata, infos, pos_of_channel(i)] = pspm_load_channel(alldata, channel{i}, 'pupil'); if sts < 1, return; end % recursive call to avoid the formula being stated twice in the same function - [sts, convert_data.data{i}] = pspm_convert_au2unit(channeldata.data, unit, distance, record_method, ... + [sts, convert_data{i}.data] = pspm_convert_au2unit(channeldata.data, unit, distance, record_method, ... multiplicator, reference_distance, reference_unit, options); if sts < 1, return; end convert_data{i}.header = channeldata.header; diff --git a/test/pspm_convert_au2unit_test.m b/test/pspm_convert_au2unit_test.m index b13056d9b..079d5ccd3 100644 --- a/test/pspm_convert_au2unit_test.m +++ b/test/pspm_convert_au2unit_test.m @@ -1,6 +1,176 @@ classdef pspm_convert_au2unit_test < pspm_testcase - % ● Description - % unittest class for the pspm_convert_au2unit function - % ● Authorship - % (C) 2019 Eshref Yozdemir (University of Zurich) +% ● Description +% unittest class for the pspm_convert_au2unit function +% ● Authorship +% (C) 2019 Eshref Yozdemir (University of Zurich) +% Updated in 2026 by Bernhard von Raußendorf + +methods (Test) + +%% data mode +% [sts, converted_data] = pspm_convert_au2unit(data, unit, distance, record_method, +% multiplicator, reference_distance, reference_unit, options) +function testDiameterSameUnits(testCase) + [sts, out] = pspm_convert_au2unit(20, 'mm', 60, 'diameter', 0.1, 50, 'mm'); + testCase.verifyEqual(sts, 1); + testCase.verifyEqual(out, 2.4); end +function testDiameterUnitConversion(testCase) + [sts, out] = pspm_convert_au2unit(30, 'cm', 6, 'diameter', 0.2, 50, 'mm'); + testCase.verifyEqual(sts, 1); + testCase.verifyEqual(out, 0.72); +end +function testAreaSameUnits(testCase) + [sts, out] = pspm_convert_au2unit(100, 'mm', 60, 'area', 0.1, 50, 'mm'); + testCase.verifyEqual(sts, 1); + testCase.verifyEqual(out, 1.2); +end +function testAreaUnitConversion(testCase) + [sts, out] = pspm_convert_au2unit(400, 'mm', 100, 'area', 0.1, 100, 'm'); + testCase.verifyEqual(sts, 1); + testCase.verifyEqual(out, 2, 'AbsTol', 1e-12); +end +function testVectorAreaSameUnits(testCase) + [sts, out] = pspm_convert_au2unit([25 36 49], 'mm', 80, 'area', 0.05, 40, 'mm'); + testCase.verifyEqual(sts, 1); + testCase.verifyEqual(out, [0.5 0.6 0.7], 'AbsTol', 1e-12); +end +function testVectorAreaUnitConversion(testCase) + [sts,out]=pspm_convert_au2unit([100 400 900],'mm',100,'area',0.1,50,'m'); + testCase.verifyEqual(sts,1); + testCase.verifyEqual(out,[2 4 6],'AbsTol',1e-12); +end + +%% Error handeling +function testErrorHandling(testCase) + % invalid inputs + [sts,out] = pspm_convert_au2unit(); + testCase.verifyEqual(sts,-1); + testCase.verifyEmpty(out); + + [sts,out] = pspm_convert_au2unit(20); + testCase.verifyEqual(sts,-1); + testCase.verifyEmpty(out); + + [sts,out] = pspm_convert_au2unit(20,'mm'); + testCase.verifyEqual(sts,-1); + testCase.verifyEmpty(out); + + [sts,out] = pspm_convert_au2unit(20,'mm',60); + testCase.verifyEqual(sts,-1); + testCase.verifyEmpty(out); + % invalid unit inputs + [sts,out] = pspm_convert_au2unit(20,'mm',60,'wrong',0.1,50,'mm'); + testCase.verifyEqual(sts,-1); + testCase.verifyEmpty(out); + + [sts,out] = pspm_convert_au2unit(20,'mm','far','diameter',0.1,50,'mm'); + testCase.verifyEqual(sts,-1); + testCase.verifyEmpty(out); + + [sts,out] = pspm_convert_au2unit(20,'mm',60,'diameter','bad',50,'mm'); + testCase.verifyEqual(sts,-1); + testCase.verifyEmpty(out); + + [sts,out] = pspm_convert_au2unit(20,'mm',60,'diameter',0.1,'bad','mm'); + testCase.verifyEqual(sts,-1); + testCase.verifyEmpty(out); +end + +%% Pspm files + + +function testFileRoundTripConvertAu2Unit(testCase) + + fn = '/home/bernd/git/PsPM/ImportTestData/eyelink/pspm_u_sc4b31.mat'; + fn_roundtrip = '/home/bernd/git/PsPM/ImportTestData/eyelink/pspm_u_sc4b31_au.mat'; + + % copy file + S = load(fn); + save(fn_roundtrip, '-struct', 'S'); + + unit = 'mm'; + distance = 600; + record_method = 'diameter'; + multiplicator = 0.04; + reference_distance = 500; + reference_unit = 'mm'; + + options = struct(); + options.channel = 'pupil'; + options.channel_action = 'replace'; + + %% 1) Original laden + [sts, infos, data] = pspm_load_data(fn); + testCase.verifyEqual(sts, 1); + + + %% 3) Originalkanal mit Einheiten laden + [sts, original_channel, ~, pos] = pspm_load_channel(fn, options.channel, 'pupil'); + testCase.verifyEqual(sts, 1); + + %% 4) Werte künstlich zurück in arbitrary units rechnen + au_data = unit2au(original_channel.data, unit, distance, record_method, ... + multiplicator, reference_distance, reference_unit); + + %% 5) AU-Kanal in die neue Datei schreiben + newdata = original_channel; + newdata.data = au_data; + newdata.header.units = 'au'; + + [sts, info_write] = pspm_write_channel( ... + fn_roundtrip, newdata, 'replace', struct('channel', pos)); + testCase.verifyEqual(sts, 1); + + %% 6) Neue Datei wieder mit convert_au2unit umrechnen + [sts, outchannel] = pspm_convert_au2unit( ... + fn_roundtrip, unit, distance, record_method, ... + multiplicator, reference_distance, reference_unit, ... + struct('channel', pos, 'channel_action', 'replace')); + testCase.verifyEqual(sts, 1); + + %% 7) Rekonvertierten Kanal laden + [sts, reconverted_channel] = pspm_load_channel(fn_roundtrip, outchannel, 'pupil'); + testCase.verifyEqual(sts, 1); + + %% 8) Vergleich + testCase.verifyEqual(reconverted_channel.data, original_channel.data, 'AbsTol', 1e-12); + testCase.verifyEqual(reconverted_channel.header.units, unit); + + %% optional cleanup + if exist(fn_roundtrip, 'file') + delete(fn_roundtrip); + end +end + + + + + + + + +end +end + +function data = unit2au(outchannel, unit, distance, record_method, ... + multiplicator, reference_distance, reference_unit) + + [~, outchannel_ref] = pspm_convert_unit(outchannel, unit, reference_unit); + [~, distance_ref] = pspm_convert_unit(distance, unit, reference_unit); + + switch lower(record_method) + case 'diameter' + data = outchannel_ref ./ ... + (multiplicator * (distance_ref / reference_distance)); + case 'area' + data = (outchannel_ref ./ ... + (multiplicator * (distance_ref / reference_distance))).^2; + otherwise + error('Invalid record_method'); + end +end + + + + From 2773414f8867d3218b61b92fdb2ae470b919e3d8 Mon Sep 17 00:00:00 2001 From: 4gwe Date: Sun, 29 Mar 2026 19:53:22 +0200 Subject: [PATCH 2/9] pspm_write_channel does no longer use units with pspm_select_channels and update to pspm_cfg_selector_channel_action helptext --- src/pspm_cfg/pspm_cfg_selector_channel_action.m | 2 +- src/pspm_write_channel.m | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_selector_channel_action.m b/src/pspm_cfg/pspm_cfg_selector_channel_action.m index 1e3e71bba..e82e3ab2d 100644 --- a/src/pspm_cfg/pspm_cfg_selector_channel_action.m +++ b/src/pspm_cfg/pspm_cfg_selector_channel_action.m @@ -6,4 +6,4 @@ channel_action.values = {'add', 'replace'}; channel_action.labels = {'Add', 'Replace'}; channel_action.val = {'add'}; -channel_action.help = {'Choose whether to add a new channel, or to replace the last existing channel of the same type and with the same units (if any).'}; +channel_action.help = {'Choose whether to add a new channel, or to replace the processed channel.'}; diff --git a/src/pspm_write_channel.m b/src/pspm_write_channel.m index 2f3dbd7d0..b716b8963 100644 --- a/src/pspm_write_channel.m +++ b/src/pspm_write_channel.m @@ -111,7 +111,7 @@ for iChannel = 1:numel(newdata) warning off [sts, ~, pos_of_channels] = pspm_select_channels(data, ... - newdata{iChannel}.header.chantype, newdata{iChannel}.header.units); + newdata{iChannel}.header.chantype); warning on if sts < 1 channeli(iChannel) = 0; @@ -132,8 +132,7 @@ warning off sts = pspm_select_channels( ... data(options.channel(iChannel)), ... - newdata{iChannel}.header.chantype, ... - newdata{iChannel}.header.units); + newdata{iChannel}.header.chantype); warning on if sts < 1 channeli(iChannel) = 0; From f8476ec6433743971b47e3ac04d0990ba377f4e6 Mon Sep 17 00:00:00 2001 From: 4gwe Date: Wed, 1 Apr 2026 17:02:56 +0200 Subject: [PATCH 3/9] pspm_convert_au2unit uses now the pspm_convert_area2diameter and updated error handling, and updated pspm_convert_au2unit_test. --- src/pspm_convert_au2unit.m | 21 +++++++---- test/pspm_convert_au2unit_test.m | 63 +++++++++++++++++++++++++++----- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/pspm_convert_au2unit.m b/src/pspm_convert_au2unit.m index e15ebc25a..207c44042 100644 --- a/src/pspm_convert_au2unit.m +++ b/src/pspm_convert_au2unit.m @@ -11,7 +11,7 @@ % input data if the recording method is area. This is performed to always % return linear units. % Using the given variables, the following calculations are performed: -% 0. Take square root of data if recording is 'area'. +% 0. Take 2*sqrt(data/pi) of data if recording is 'area'. % 1. Let from unit to reference_unit converted recording distance be Dconv. % 2. x ← A*(Dconv/Dref)*x % 3. Convert x from ref_unit to unit. @@ -169,19 +169,26 @@ convert_data{i}.header = channeldata.header; convert_data{i}.header.units = unit; end - [f_sts, f_info] = pspm_write_channel(fn, convert_data, options.channel_action, struct('channel', pos_of_channel)); - if f_sts < 1, return; end + [sts, f_info] = pspm_write_channel(fn, convert_data, options.channel_action, struct('channel', pos_of_channel)); + if sts < 1, return; end outchannel = f_info.channel; % convert data case 'data' convert_data = data; if strcmpi(record_method, 'area') - convert_data = sqrt(convert_data); + [f_sts, convert_data] = pspm_convert_area2diameter(convert_data); + if f_sts < 1, return; end end - [~, distance] = pspm_convert_unit(distance, unit, reference_unit); + + [f_sts, distance] = pspm_convert_unit(distance, unit, reference_unit); + if f_sts < 1, return; end convert_data = multiplicator * (distance / reference_distance) * convert_data; + %% convert data from reference_unit to unit - [~, convert_data] = pspm_convert_unit(convert_data, reference_unit, unit); + [f_sts, convert_data] = pspm_convert_unit(convert_data, reference_unit, unit); + if f_sts < 1, return; end outchannel = convert_data; + sts = 1; end -sts = 1; + +end \ No newline at end of file diff --git a/test/pspm_convert_au2unit_test.m b/test/pspm_convert_au2unit_test.m index 079d5ccd3..45c5a6c92 100644 --- a/test/pspm_convert_au2unit_test.m +++ b/test/pspm_convert_au2unit_test.m @@ -21,24 +21,70 @@ function testDiameterUnitConversion(testCase) testCase.verifyEqual(out, 0.72); end function testAreaSameUnits(testCase) - [sts, out] = pspm_convert_au2unit(100, 'mm', 60, 'area', 0.1, 50, 'mm'); + data = 100; + [sts, out] = pspm_convert_au2unit(data, 'mm', 60, 'area', 0.1, 50, 'mm'); testCase.verifyEqual(sts, 1); - testCase.verifyEqual(out, 1.2); + + % expected with formula diameter = 2.*sqrt(area./pi); + expected_from_formula = 0.1 * (60 / 50) * 2*sqrt(data./pi); + testCase.verifyEqual(out, expected_from_formula); + + % expected with [sts, diameter] = pspm_convert_area2diameter(area) + [sts, diam] = pspm_convert_area2diameter(data); + expected_from_pspm = 0.1 * (60 / 50) * diam; + testCase.verifyEqual(out, expected_from_pspm); + end function testAreaUnitConversion(testCase) - [sts, out] = pspm_convert_au2unit(400, 'mm', 100, 'area', 0.1, 100, 'm'); + data = 400; + [sts, out] = pspm_convert_au2unit(data, 'mm', 100, 'area', 0.1, 100, 'm'); % reference_unit in m testCase.verifyEqual(sts, 1); - testCase.verifyEqual(out, 2, 'AbsTol', 1e-12); + + + % expected with formula diameter = 2.*sqrt(area./pi); + % ref. units in m: Dconv 100mm -> 0.1 m and A(Dconv/Dref) m -> mm (*1000) + expected_from_formula = 0.1 * (0.1 / 100) * 2 * sqrt(data ./ pi) * 1000; + testCase.verifyEqual(out, expected_from_formula); + + % expected with [sts, diameter] = pspm_convert_area2diameter(area) + [sts, diam] = pspm_convert_area2diameter(data); + expected_from_pspm = 0.1 * (0.1 / 100) * diam * 1000; + testCase.verifyEqual(out, expected_from_pspm); + + + end function testVectorAreaSameUnits(testCase) - [sts, out] = pspm_convert_au2unit([25 36 49], 'mm', 80, 'area', 0.05, 40, 'mm'); + data = [25 36 49] + [sts, out] = pspm_convert_au2unit(data, 'mm', 80, 'area', 0.05, 40, 'mm'); testCase.verifyEqual(sts, 1); - testCase.verifyEqual(out, [0.5 0.6 0.7], 'AbsTol', 1e-12); + + + % expected with formula diameter = 2.*sqrt(area./pi); + expected_from_formula = 0.05 * (80 / 40) * 2 * sqrt(data ./ pi) ; + testCase.verifyEqual(out, expected_from_formula); + + % expected with [sts, diameter] = pspm_convert_area2diameter(area) + [sts, diam] = pspm_convert_area2diameter(data); + expected_from_pspm = 0.05 * (80 / 40) * diam ; + testCase.verifyEqual(out, expected_from_pspm); + end function testVectorAreaUnitConversion(testCase) - [sts,out]=pspm_convert_au2unit([100 400 900],'mm',100,'area',0.1,50,'m'); + data = [100 400 900]; + [sts,out]=pspm_convert_au2unit(data,'mm',100,'area',0.1,50,'m'); testCase.verifyEqual(sts,1); - testCase.verifyEqual(out,[2 4 6],'AbsTol',1e-12); + + % expected with formula diameter = 2.*sqrt(area./pi); + expected_from_formula = 0.1 * (0.1 / 50) * 2 * sqrt(data ./ pi) * 1000 ; + testCase.verifyEqual(out, expected_from_formula); + + % expected with [sts, diameter] = pspm_convert_area2diameter(area) + [sts, diam] = pspm_convert_area2diameter(data); + expected_from_pspm = 0.1 * (0.1 / 50) * diam * 1000; + testCase.verifyEqual(out, expected_from_pspm); + + end %% Error handeling @@ -79,7 +125,6 @@ function testErrorHandling(testCase) %% Pspm files - function testFileRoundTripConvertAu2Unit(testCase) fn = '/home/bernd/git/PsPM/ImportTestData/eyelink/pspm_u_sc4b31.mat'; From 867e3248b385d07798dd018b8d1c787e6a15becf Mon Sep 17 00:00:00 2001 From: 4gwe Date: Wed, 1 Apr 2026 17:17:12 +0200 Subject: [PATCH 4/9] updated pspm_convert_gaze_test to the new pspm_write_channels new way to call pspm_select_channels --- test/pspm_convert_gaze_test.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pspm_convert_gaze_test.m b/test/pspm_convert_gaze_test.m index c839b1275..bb934957d 100644 --- a/test/pspm_convert_gaze_test.m +++ b/test/pspm_convert_gaze_test.m @@ -98,7 +98,7 @@ function conversion(this, target, from, channel_action) this.verifyTrue(~isempty(out_channel)); if strcmpi(target, 'sps') extra = 1; - elseif strcmpi(channel_action, 'add') || ~strcmpi(target, from) + elseif strcmpi(channel_action, 'add') %|| ~strcmpi(target, from) extra = 2; else extra = 0; From fc654e60709aaef2b77e7d1cf5d560a03618711c Mon Sep 17 00:00:00 2001 From: 4gwe Date: Thu, 2 Apr 2026 11:19:31 +0200 Subject: [PATCH 5/9] updated pspm_write_channel_test to the new pspm_write_channels new way to call pspm_select_channels --- test/pspm_write_channel_test.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/pspm_write_channel_test.m b/test/pspm_write_channel_test.m index dcafb9099..1931e3c7f 100644 --- a/test/pspm_write_channel_test.m +++ b/test/pspm_write_channel_test.m @@ -197,11 +197,11 @@ function test_replace_units(this) gen_data.data{1}.header.units = 'degree'; [~, ~] = this.verifyWarningFree(@() pspm_write_channel(this.testdatafile, gen_data.data{1}, 'replace')); [~, post_unit_change.infos, post_unit_change.data] = pspm_load_data(this.testdatafile); - % should be one more channel as degrees did not exist - this.verifyEqual(length(post_unit_change.data), length(new.data) + 1); + % should be the same nr. of channels + this.verifyEqual(length(post_unit_change.data), length(new.data)); % assert one mm gaze channel and one degree gaze channel - this.verifyEqual(length(find(cellfun(@(c) strcmp(c.header.units, 'mm') && ... - strcmp(c.header.chantype, 'gaze_x_l'), post_unit_change.data))), 1); + % this.verifyEqual(length(find(cellfun(@(c) strcmp(c.header.units, 'mm') && ... + % strcmp(c.header.chantype, 'gaze_x_l'), post_unit_change.data))), 1); this.verifyEqual(length(find(cellfun(@(c) strcmp(c.header.units, 'degree') && ... strcmp(c.header.chantype, 'gaze_x_l'), post_unit_change.data))), 1); end From c3cb9d8274a93f60923230839968b394a6a017d0 Mon Sep 17 00:00:00 2001 From: 4gwe Date: Thu, 2 Apr 2026 12:48:05 +0200 Subject: [PATCH 6/9] small updates to test/pspm_convert_au2unit_test.m --- test/pspm_convert_au2unit_test.m | 61 ++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/test/pspm_convert_au2unit_test.m b/test/pspm_convert_au2unit_test.m index 45c5a6c92..2fb4c0551 100644 --- a/test/pspm_convert_au2unit_test.m +++ b/test/pspm_convert_au2unit_test.m @@ -83,7 +83,25 @@ function testVectorAreaUnitConversion(testCase) [sts, diam] = pspm_convert_area2diameter(data); expected_from_pspm = 0.1 * (0.1 / 50) * diam * 1000; testCase.verifyEqual(out, expected_from_pspm); +end +function testVectorAreaUnitConversionTEST(testCase) + data = [100 400 900]; + [sts,out]=pspm_convert_au2unit(data,'mm',100,'area',0.1,50,'mm'); + testCase.verifyEqual(sts,1); + % + % % expected with formula diameter = 2.*sqrt(area./pi); + % expected_from_formula = 0.1 * (0.1 / 50) * 2 * sqrt(data ./ pi) * 1000 ; + % testCase.verifyEqual(out, expected_from_formula); + % + % % expected with [sts, diameter] = pspm_convert_area2diameter(area) + % [sts, diam] = pspm_convert_area2diameter(data); + % expected_from_pspm = 0.1 * (0.1 / 50) * diam * 1000; + % testCase.verifyEqual(out, expected_from_pspm); + + data2 = unit2au(out,'mm',100,'area',0.1,50,'mm'); + testCase.verifyEqual(data , data2, 'AbsTol', 1e-12); + end @@ -124,9 +142,7 @@ function testErrorHandling(testCase) end %% Pspm files - function testFileRoundTripConvertAu2Unit(testCase) - fn = '/home/bernd/git/PsPM/ImportTestData/eyelink/pspm_u_sc4b31.mat'; fn_roundtrip = '/home/bernd/git/PsPM/ImportTestData/eyelink/pspm_u_sc4b31_au.mat'; @@ -145,72 +161,65 @@ function testFileRoundTripConvertAu2Unit(testCase) options.channel = 'pupil'; options.channel_action = 'replace'; - %% 1) Original laden + %% 1) load original file [sts, infos, data] = pspm_load_data(fn); testCase.verifyEqual(sts, 1); - - %% 3) Originalkanal mit Einheiten laden + %% 2) load pupil channel [sts, original_channel, ~, pos] = pspm_load_channel(fn, options.channel, 'pupil'); testCase.verifyEqual(sts, 1); - %% 4) Werte künstlich zurück in arbitrary units rechnen + %% 3) change units to au au_data = unit2au(original_channel.data, unit, distance, record_method, ... multiplicator, reference_distance, reference_unit); - %% 5) AU-Kanal in die neue Datei schreiben newdata = original_channel; newdata.data = au_data; newdata.header.units = 'au'; - [sts, info_write] = pspm_write_channel( ... - fn_roundtrip, newdata, 'replace', struct('channel', pos)); + [sts, info_write] = pspm_write_channel(fn_roundtrip, newdata, 'replace', struct('channel', pos)); testCase.verifyEqual(sts, 1); - %% 6) Neue Datei wieder mit convert_au2unit umrechnen + %% 4) convert back to units [sts, outchannel] = pspm_convert_au2unit( ... fn_roundtrip, unit, distance, record_method, ... multiplicator, reference_distance, reference_unit, ... struct('channel', pos, 'channel_action', 'replace')); testCase.verifyEqual(sts, 1); - %% 7) Rekonvertierten Kanal laden + %% 5) load converted channel [sts, reconverted_channel] = pspm_load_channel(fn_roundtrip, outchannel, 'pupil'); testCase.verifyEqual(sts, 1); - %% 8) Vergleich + %% 8) Test testCase.verifyEqual(reconverted_channel.data, original_channel.data, 'AbsTol', 1e-12); testCase.verifyEqual(reconverted_channel.header.units, unit); - %% optional cleanup + %% cleanup if exist(fn_roundtrip, 'file') delete(fn_roundtrip); end end - - - - - - - end end function data = unit2au(outchannel, unit, distance, record_method, ... multiplicator, reference_distance, reference_unit) - [~, outchannel_ref] = pspm_convert_unit(outchannel, unit, reference_unit); - [~, distance_ref] = pspm_convert_unit(distance, unit, reference_unit); + % [~, outchannel_ref] = pspm_convert_unit(outchannel, unit, reference_unit); + % [~, distance_conv] = pspm_convert_unit(distance, unit, reference_unit); switch lower(record_method) case 'diameter' - data = outchannel_ref ./ ... - (multiplicator * (distance_ref / reference_distance)); + data = outchannel ./ ... + (multiplicator * (distance / reference_distance )); case 'area' - data = (outchannel_ref ./ ... - (multiplicator * (distance_ref / reference_distance))).^2; + + data = (outchannel ./ ... + (multiplicator * ( distance/ reference_distance))); + data = ((data./2).^2 ).*pi; + otherwise error('Invalid record_method'); end From a06bf8681086318fea849be11a891a6f6074bdf9 Mon Sep 17 00:00:00 2001 From: 4gwe Date: Mon, 13 Apr 2026 00:11:11 +0200 Subject: [PATCH 7/9] Fix ecg editor and batch config issues; update pspm_convert_hb2hp and add tests --- doc/Tests_Current_Status.docx | Bin 16184 -> 15224 bytes src/pspm_cfg/pspm_cfg_convert_cardiac.m | 4 +- src/pspm_cfg/pspm_cfg_ecg_editor.m | 4 +- src/pspm_cfg/pspm_cfg_run_convert_cardiac.m | 3 +- src/pspm_cfg/pspm_cfg_run_ecg_editor.m | 15 ++- src/pspm_convert_hb2hp.m | 10 +- test/pspm_convert_hb2hp_test.m | 104 +++++++++++++++++--- 7 files changed, 112 insertions(+), 28 deletions(-) diff --git a/doc/Tests_Current_Status.docx b/doc/Tests_Current_Status.docx index 80c83abb6569cba255ffad982be08b06c8f7811d..16e3edeb51baf1a3ae4c72c7935b25fa70457dbb 100644 GIT binary patch delta 13152 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 MPX;N^@!S3X0Vym`-~a#s delta 14101 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 options.limit.lower & ibi < options.limit.upper); +idx = find(ibi > options.limit_lower & ibi < options.limit_upper); hp = 1000 * ibi; % in ms newt = (1/sr):(1/sr):dinfos.duration; try @@ -103,7 +103,7 @@ o.msg.prefix = 'Heart beat converted to heart period and'; try [nsts,winfos] = pspm_write_channel(fn, newdata, options.channel_action, o); - if nsts == -1, return; + if nsts < 1, return; end catch warning('ID:invalid_input', 'call of pspm_write_channel failed'); diff --git a/test/pspm_convert_hb2hp_test.m b/test/pspm_convert_hb2hp_test.m index 6cd6ea8fd..8aa0da16b 100644 --- a/test/pspm_convert_hb2hp_test.m +++ b/test/pspm_convert_hb2hp_test.m @@ -4,13 +4,33 @@ % * History % Written in 2019 by Ivan Rojkov (University of Zurich) % Updated in 2024 by Teddy +% Updated in 2026 by Bernhard von Raußendorf + +properties (Constant) + input_filename = fullfile(fileparts(mfilename('fullpath')), 'ImportTestData', 'ecg2hb', 'test_ecg_outlier_data_short_hb.mat'); + backup_filename = fullfile(fileparts(mfilename('fullpath')),'ImportTestData', 'ecg2hb', 'test_backup.mat'); +end + +methods (TestClassSetup) + function backup(this) + this.assertTrue(isfile(this.input_filename), ... + sprintf('Input file not found: %s', this.input_filename)); + + [sts, msg] = copyfile(this.input_filename, this.backup_filename); + this.assertTrue(sts, msg); + end +end + +methods (TestMethodSetup) + function reset_input_file(this) + [sts, msg] = copyfile(this.backup_filename, this.input_filename); + this.assertTrue(sts, msg); + end +end + +% Tests methods (Test) function invalid_input(this) - files = {... - fullfile('ImportTestData', 'ecg2hb', 'test_ecg_outlier_data_short.mat'),... - fullfile('ImportTestData', 'ecg2hb', 'test_hb2hp_data1.mat'),... - fullfile('ImportTestData', 'ecg2hb', 'test_hb2hp_data2.mat')... - }; % Verify no input this.verifyWarning(@() pspm_convert_hb2hp(), 'ID:invalid_input'); % Verify not a string filename @@ -21,18 +41,74 @@ function invalid_input(this) this.verifyWarning(@() pspm_convert_hb2hp('abc','abc'), 'ID:invalid_input'); % Verify not a numeric channel this.verifyWarning(@() pspm_convert_hb2hp('abc',2,'abc'), 'ID:invalid_input'); - % Verify that call of pspm_load_data fails - this.verifyWarning(@() pspm_convert_hb2hp(files{1},100), 'ID:nonexistent_file'); - % Verify that interpolation does not have enough points - % this.verifyWarning(@() pspm_convert_hb2hp(files{2}, 100), 'ID:too_strict_limits'); - % Verify that call of pspm_write_channel fails - options.channel_action = 'abc'; - this.verifyWarning(@() pspm_convert_hb2hp(files{3},100,[],options),'ID:invalid_input'); - %options.channel_action = 'add'; - %this.verifyWarningFree(@()pspm_convert_hb2hp(files{1},100,[],options)); + + % + % % Verify that call of pspm_load_data fails + % this.verifyWarning(@() pspm_convert_hb2hp(files{1},100), 'ID:nonexistent_file'); + % % Verify that interpolation does not have enough points + % % this.verifyWarning(@() pspm_convert_hb2hp(files{2}, 100), 'ID:too_strict_limits'); + % % Verify that call of pspm_write_channel fails + % options.channel_action = 'abc'; + % this.verifyWarning(@() pspm_convert_hb2hp(files{3},100,[],options),'ID:invalid_input'); + % %options.channel_action = 'add'; + % %this.verifyWarningFree(@()pspm_convert_hb2hp(files{1},100,[],options)); + +end + +function basic_conversion(this) + sr = 1000; + + % this.verifyWarningFree(@() pspm_convert_hb2hp(this.input_filename, sr)); + + [sts, outchannel] = pspm_convert_hb2hp(this.input_filename, sr); + + this.verifyEqual(sts, 1); + this.verifyTrue(isnumeric(outchannel)); + this.verifyGreaterThan(outchannel, 0); +end + +function too_strict_limits(this) + % fn = fullfile('ImportTestData', 'ecg2hb', 'test_hb2hp_data2.mat'); + sr = 100; + options = struct(); + options = struct('limit_lower', 10, 'limit_upper', 11); + + this.verifyWarning(@() pspm_convert_hb2hp(this.input_filename, sr, options), 'ID:too_strict_limits'); + + [sts, outchannel] = pspm_convert_hb2hp(this.input_filename, sr, options); + this.verifyEqual(sts, 1); + this.verifyTrue(isnumeric(outchannel)); + this.verifyGreaterThan(outchannel, 0); +end + +function add_channel_action(this) + fn = fullfile('ImportTestData', 'ecg2hb', 'test_ecg_outlier_data_short_hb.mat'); + sr = 100; + options = struct(); + options.channel_action = 'add'; + + this.verifyWarningFree(@() pspm_convert_hb2hp(fn, sr, options)); + + [sts, outchannel] = pspm_convert_hb2hp(fn, sr, options); + + this.verifyEqual(sts, 1); + this.verifyTrue(isnumeric(outchannel)); + this.verifyGreaterThan(outchannel, 0); +end + + + end +methods (TestClassTeardown) + function restore(this) + if isfile(this.backup_filename) + [sts, msg] = copyfile(this.backup_filename, this.input_filename); + this.assertTrue(sts, msg); + delete(this.backup_filename); + end + end end end From 1f14f478e537f63a8167ca351bc1d8de31bf03ec Mon Sep 17 00:00:00 2001 From: 4gwe Date: Mon, 27 Apr 2026 00:50:35 +0200 Subject: [PATCH 8/9] Update functions that use channel_actions and improve/add related tests --- src/pspm_convert_ecg2hb_amri.m | 2 +- src/pspm_convert_ppg2hb.m | 12 +- test/pspm_combine_markerchannels_test.m | 20 +++ test/pspm_convert_au2unit_test.m | 3 - test/pspm_convert_ecg2hb_amri_test.m | 31 +++- test/pspm_convert_ecg2hb_test.m | 34 ++++ test/pspm_convert_hb2hp_test.m | 117 ++++++++----- test/pspm_convert_pixel2unit_core_test.m | 101 +++++++++++ test/pspm_convert_ppg2hb_test.m | 142 +++++++++++++++ test/pspm_emg_pp_test.m | 130 ++++++++++++++ test/pspm_gaze_pp_test.m | 210 +++++++++++++++-------- test/pspm_resp_pp_test.m | 22 +++ 12 files changed, 698 insertions(+), 126 deletions(-) create mode 100644 test/pspm_convert_pixel2unit_core_test.m create mode 100644 test/pspm_convert_ppg2hb_test.m create mode 100644 test/pspm_emg_pp_test.m diff --git a/src/pspm_convert_ecg2hb_amri.m b/src/pspm_convert_ecg2hb_amri.m index ac90be0e4..1e8ab7286 100644 --- a/src/pspm_convert_ecg2hb_amri.m +++ b/src/pspm_convert_ecg2hb_amri.m @@ -102,7 +102,7 @@ heartbeats{1}.header.chantype = 'hb'; heartbeats{1}.header.units = 'events'; o.msg.prefix = 'QRS detection using AMRI algorithm'; -[lsts, infos] = pspm_write_channel(fn, heartbeats, options.channel_action); +[lsts, infos] = pspm_write_channel(fn, heartbeats, options.channel_action,o); if lsts ~= 1; return; end out_channel = infos.channel; sts = 1; diff --git a/src/pspm_convert_ppg2hb.m b/src/pspm_convert_ppg2hb.m index cd2cc87c6..b04a69276 100644 --- a/src/pspm_convert_ppg2hb.m +++ b/src/pspm_convert_ppg2hb.m @@ -67,6 +67,8 @@ % ------------------------------------------------------------------------- if nargin < 1 warning('ID:invalid_input', 'No input. Don''t know what to do.'); return; +elseif ~(ischar(fn) || isstruct(fn)) + warning('ID:invalid_input', 'Need file name string or struct as first input.'); return; elseif nargin < 2 options = struct(); options.channel = 'ppg'; @@ -84,7 +86,7 @@ % get data % ------------------------------------------------------------------------- [nsts, data, infos, pos_of_channel] = pspm_load_channel(fn, options.channel, 'ppg'); -if nsts == -1, return; end +if nsts < 1, return; end ppg = data.data; sr = data.header.sr; @@ -245,13 +247,13 @@ write_options = struct(); write_options.msg = msg; -write_options.channel = pos_of_channel; +% write_options.channel = pos_of_channel; +write_options.channel = 0; % Replace last existing channel or save as new channel [nsts, nout] = pspm_write_channel(fn, newdata, options.channel_action, write_options); -if ~nsts - return -end +if nsts < 1; return; end + % user output fprintf(' done.\n'); sts = 1; diff --git a/test/pspm_combine_markerchannels_test.m b/test/pspm_combine_markerchannels_test.m index 295c80a7c..b0c509387 100644 --- a/test/pspm_combine_markerchannels_test.m +++ b/test/pspm_combine_markerchannels_test.m @@ -70,5 +70,25 @@ function test_combine_specific_channel_numbers(this) this.verifyTrue(fstruct.numofchan == 7, 'the output has a different size'); delete(fn) end + function test_combine_specific_channel_number_zero(this) + fn = pspm_find_free_fn(this.fn1, '.mat'); + channels{1}.chantype = 'scr'; + channels{2}.chantype = 'hb'; + channels{3}.chantype = 'marker'; + channels{4}.chantype = 'marker'; + channels{5}.chantype = 'marker'; + channels{6}.chantype = 'marker'; + channels{7}.chantype = 'marker'; + channels{8}.chantype = 'marker'; + channels{9}.chantype = 'marker'; + pspm_testdata_gen(channels, this.duration1, fn); + % test defaultly combining all marker channels + sts = pspm_combine_markerchannels(fn, struct('marker_chan_num', 0)); + this.verifyTrue(sts == 1, 'the function run successfully'); + [sts_out, ~, ~, fstruct] = pspm_load_data(fn, 'none'); + this.verifyTrue(sts_out == 1, 'the processed file couldn''t be loaded'); + this.verifyTrue(fstruct.numofchan == numel(channels)+1, 'the output has a different size'); + delete(fn) + end end end diff --git a/test/pspm_convert_au2unit_test.m b/test/pspm_convert_au2unit_test.m index 2fb4c0551..c6c56c147 100644 --- a/test/pspm_convert_au2unit_test.m +++ b/test/pspm_convert_au2unit_test.m @@ -100,9 +100,6 @@ function testVectorAreaUnitConversionTEST(testCase) data2 = unit2au(out,'mm',100,'area',0.1,50,'mm'); testCase.verifyEqual(data , data2, 'AbsTol', 1e-12); - - - end %% Error handeling diff --git a/test/pspm_convert_ecg2hb_amri_test.m b/test/pspm_convert_ecg2hb_amri_test.m index a429a87e4..10e049851 100644 --- a/test/pspm_convert_ecg2hb_amri_test.m +++ b/test/pspm_convert_ecg2hb_amri_test.m @@ -20,9 +20,38 @@ function backup(this) methods(Test) function check_if_heartbeat_channel_is_saved(this) [sts, out_channel] = pspm_convert_ecg2hb_amri(this.input_filename); - load(this.input_filename); + [sts, infos, data, filestruct] = pspm_load_data(this.input_filename); this.verifyEqual(data{out_channel}.header.chantype, 'hb'); +end +function test_write_channel_add_replace(this) + % restore file + sts = copyfile(this.backup_filename, this.input_filename); + assert(sts == 1); + + o.channel_action = 'add'; + [sts, out_channel] = pspm_convert_ecg2hb_amri(this.input_filename,o); + [sts, infos, data, filestruct] = pspm_load_data(this.input_filename); + + this.verifyEqual(data{out_channel}.header.chantype, 'hb'); + this.verifyEqual(numel(data), 2); + + o.channel_action = 'add'; + [sts, out_channel] = pspm_convert_ecg2hb_amri(this.input_filename,o); + [sts, infos, data, filestruct] = pspm_load_data(this.input_filename); + + this.verifyEqual(data{out_channel}.header.chantype, 'hb'); + this.verifyEqual(numel(data), 3); + + o.channel_action = 'replace'; + [sts, out_channel] = pspm_convert_ecg2hb_amri(this.input_filename,o); + [sts, infos, data, filestruct] = pspm_load_data(this.input_filename); + + this.verifyEqual(data{out_channel}.header.chantype, 'hb'); + this.verifyEqual(numel(data), 3); + + + end end diff --git a/test/pspm_convert_ecg2hb_test.m b/test/pspm_convert_ecg2hb_test.m index 040d742af..f671c4e8f 100644 --- a/test/pspm_convert_ecg2hb_test.m +++ b/test/pspm_convert_ecg2hb_test.m @@ -174,6 +174,40 @@ function valid_input_all(this) this.valid_input(filename, chan_struct, num_channels); end end + function channel_action_add_replace_one_file(this) + fn_original = this.testdata{1}.filename; + [pathstr, name, ext] = fileparts(fn_original); + fn = fullfile(pathstr, [name, '_single', ext]); + copyfile(fn_original, fn); + + + % Check copied file + [nsts, ~, data] = pspm_load_data(fn); + this.verifyEqual(nsts, 1); + n_channels = numel(data); + + % First add one hb channel + options.channel_action = 'add'; + [sts, outch_add] = pspm_convert_ecg2hb(fn, options); + this.verifyEqual(sts, 1); + + [nsts, ~, data] = pspm_load_data(fn); + this.verifyEqual(nsts, 1); + this.verifyEqual(numel(data), n_channels + 1); + + % Then replace on the same file + options.channel_action = 'replace'; + [sts, outch_replace] = pspm_convert_ecg2hb(fn, options); + this.verifyEqual(sts, 1); + + [nsts, ~, data] = pspm_load_data(fn); + this.verifyEqual(nsts, 1); + this.verifyEqual(numel(data), n_channels + 1); + this.verifyEqual(outch_replace, outch_add); + + % remove copied file + delete(fn); + end end end function max_num = max_beats_in_k_seconds(seconds, k) diff --git a/test/pspm_convert_hb2hp_test.m b/test/pspm_convert_hb2hp_test.m index 8aa0da16b..5585acebb 100644 --- a/test/pspm_convert_hb2hp_test.m +++ b/test/pspm_convert_hb2hp_test.m @@ -1,4 +1,4 @@ -classdef pspm_convert_hb2hp_test < matlab.unittest.TestCase +classdef pspm_convert_hb2hp_test < pspm_testcase % * Description % Unittest class for the pspm_convert_hb2hp function % * History @@ -6,26 +6,34 @@ % Updated in 2024 by Teddy % Updated in 2026 by Bernhard von Raußendorf -properties (Constant) - input_filename = fullfile(fileparts(mfilename('fullpath')), 'ImportTestData', 'ecg2hb', 'test_ecg_outlier_data_short_hb.mat'); - backup_filename = fullfile(fileparts(mfilename('fullpath')),'ImportTestData', 'ecg2hb', 'test_backup.mat'); +properties + original_filename = fullfile(fileparts(mfilename('fullpath')), '..', ... + 'ImportTestData', 'ecg2hb', 'test_ecg_outlier_data_short_hb.mat'); + + input_filename = fullfile(fileparts(mfilename('fullpath')), '..', ... + 'ImportTestData', 'ecg2hb', 'totest_test_ecg_outlier_data_short_hb.mat'); end methods (TestClassSetup) - function backup(this) - this.assertTrue(isfile(this.input_filename), ... - sprintf('Input file not found: %s', this.input_filename)); - - [sts, msg] = copyfile(this.input_filename, this.backup_filename); - this.assertTrue(sts, msg); - end + function check_original_file(this) + this.assertTrue(exist(this.original_filename, 'file') == 2, ... + sprintf('Input file not found: %s', this.original_filename)); + end end methods (TestMethodSetup) - function reset_input_file(this) - [sts, msg] = copyfile(this.backup_filename, this.input_filename); - this.assertTrue(sts, msg); - end + function reset_input_file(this) + [sts, msg] = copyfile(this.original_filename, this.input_filename); + this.assertTrue(sts, msg); + end +end + +methods (TestClassTeardown) + function cleanup(this) + if exist(this.input_filename, 'file') == 2 + delete(this.input_filename); + end + end end % Tests @@ -34,6 +42,8 @@ function invalid_input(this) % Verify no input this.verifyWarning(@() pspm_convert_hb2hp(), 'ID:invalid_input'); % Verify not a string filename + this.verifyWarning(@() pspm_convert_hb2hp(2, 100), 'ID:invalid_input'); + % Verify no sample rate this.verifyWarning(@() pspm_convert_hb2hp(2), 'ID:invalid_input'); % Verify no sample rate this.verifyWarning(@() pspm_convert_hb2hp('abc'), 'ID:invalid_input'); @@ -56,59 +66,74 @@ function invalid_input(this) end function basic_conversion(this) + fn = this.input_filename; sr = 1000; % this.verifyWarningFree(@() pspm_convert_hb2hp(this.input_filename, sr)); - - [sts, outchannel] = pspm_convert_hb2hp(this.input_filename, sr); - + [sts, outchannel] = pspm_convert_hb2hp(fn, sr); this.verifyEqual(sts, 1); - this.verifyTrue(isnumeric(outchannel)); - this.verifyGreaterThan(outchannel, 0); + [sts, infos, data, filestruct] = pspm_load_data(fn); + this.verifyEqual(sts, 1); + this.verifyEqual(data{outchannel}.header.chantype, 'hp'); end function too_strict_limits(this) - % fn = fullfile('ImportTestData', 'ecg2hb', 'test_hb2hp_data2.mat'); - sr = 100; - options = struct(); - options = struct('limit_lower', 10, 'limit_upper', 11); + fn = this.input_filename; + sr = 1; + options = struct('limit_lower', 11, 'limit_upper', 11); - this.verifyWarning(@() pspm_convert_hb2hp(this.input_filename, sr, options), 'ID:too_strict_limits'); + % options = struct('limit_lower', 10, 'limit_upper', 11); + % this.verifyWarning(@() pspm_convert_hb2hp(fn, sr, options), ... + % 'ID:too_strict_limits'); - [sts, outchannel] = pspm_convert_hb2hp(this.input_filename, sr, options); + % this.verifyWarningFree(@() pspm_convert_hb2hp(this.input_filename, sr)); + [sts, outchannel] = pspm_convert_hb2hp(fn, sr,options); + this.verifyEqual(sts, 1); + [sts, infos, data, filestruct] = pspm_load_data(fn); this.verifyEqual(sts, 1); - this.verifyTrue(isnumeric(outchannel)); - this.verifyGreaterThan(outchannel, 0); + this.verifyEqual(data{outchannel}.header.chantype, 'hp'); + this.verifyTrue(any(isnan(data{outchannel}.data))) end -function add_channel_action(this) - fn = fullfile('ImportTestData', 'ecg2hb', 'test_ecg_outlier_data_short_hb.mat'); - sr = 100; - options = struct(); +function add_replace_channel_action(this) + fn = this.input_filename; + sr = 1; options.channel_action = 'add'; - this.verifyWarningFree(@() pspm_convert_hb2hp(fn, sr, options)); + [sts, infos, ~, filestruct] = pspm_load_data(fn); + this.verifyEqual(sts, 1); + % display(infos.history) + % add 1st hp [sts, outchannel] = pspm_convert_hb2hp(fn, sr, options); + this.verifyEqual(sts, 1); + [sts, ~, data, ~ ] = pspm_load_data(fn); + this.verifyEqual(sts, 1); + this.verifyEqual(data{outchannel}.header.chantype, 'hp'); + this.verifyEqual(filestruct.numofchan + 1 , numel(data)); + % display(infos.history) + % add 2nd hp + [sts, outchannel] = pspm_convert_hb2hp(fn, sr, options); this.verifyEqual(sts, 1); - this.verifyTrue(isnumeric(outchannel)); - this.verifyGreaterThan(outchannel, 0); -end + [sts, ~, data, ~ ] = pspm_load_data(fn); + this.verifyEqual(sts, 1); + this.verifyEqual(data{outchannel}.header.chantype, 'hp'); + this.verifyEqual(filestruct.numofchan + 2 , numel(data)); + % replace hp (last) + options.channel_action = 'replace'; + [sts, outchannel] = pspm_convert_hb2hp(fn, sr, options); + this.verifyEqual(sts, 1); + [sts, infos, data, ~] = pspm_load_data(fn); + + % infos.history + this.verifyEqual(sts, 1); + this.verifyEqual(data{outchannel}.header.chantype, 'hp'); + this.verifyEqual(filestruct.numofchan + 2 , numel(data)); end - -methods (TestClassTeardown) - function restore(this) - if isfile(this.backup_filename) - [sts, msg] = copyfile(this.backup_filename, this.input_filename); - this.assertTrue(sts, msg); - delete(this.backup_filename); - end - end end - end diff --git a/test/pspm_convert_pixel2unit_core_test.m b/test/pspm_convert_pixel2unit_core_test.m new file mode 100644 index 000000000..11678e5dc --- /dev/null +++ b/test/pspm_convert_pixel2unit_core_test.m @@ -0,0 +1,101 @@ +classdef pspm_convert_pixel2unit_core_test < matlab.unittest.TestCase +% Unit tests for pspm_convert_pixel2unit_core +% ● Description +% unittest class for the pspm_convert_ecg2hb function +% ● Authorship +% (C) 2026 Bernhard von Raußendorf (University of Bonn) +methods (Test) + +function testBasicConversion(testCase) + data = [1 401 801]; + data_range = [1 801]; + screen_length = 400; % mm + + [out_data, out_range] = pspm_convert_pixel2unit_core(data, data_range, screen_length); + + expected_length_per_pixel = 400 / (801 - 1 + 1); % 400/801 + expected_out_data = [0 400 800] * expected_length_per_pixel; + expected_out_range = [0 800] * expected_length_per_pixel; + + testCase.verifyEqual(out_data, expected_out_data, 'AbsTol', 1e-12); + testCase.verifyEqual(out_range, expected_out_range, 'AbsTol', 1e-12); +end + +function testScalarData(testCase) + data = 51; + data_range = [1 101]; + screen_length = 500; + + [out_data, out_range] = pspm_convert_pixel2unit_core(data, data_range, screen_length); + + expected_length_per_pixel = 500 / (101 - 1 + 1); % 500/101 + expected_out_data = (51 - 1) * expected_length_per_pixel; + expected_out_range = [0 100] * expected_length_per_pixel; + + testCase.verifyEqual(out_data, expected_out_data, 'AbsTol', 1e-12); + testCase.verifyEqual(out_range, expected_out_range, 'AbsTol', 1e-12); +end + +function testNegativeAndOffsetRange(testCase) + data = [-100 0 100]; + data_range = [-200 200]; + screen_length = 600; + + [out_data, out_range] = pspm_convert_pixel2unit_core(data, data_range, screen_length); + + expected_length_per_pixel = 600 / (200 - (-200) + 1); % 600/401 + expected_out_data = ([100 200 300]) * expected_length_per_pixel; + expected_out_range = [0 400] * expected_length_per_pixel; + + testCase.verifyEqual(out_data, expected_out_data, 'AbsTol', 1e-12); + testCase.verifyEqual(out_range, expected_out_range, 'AbsTol', 1e-12); +end + +function testRangeEndpoints(testCase) + data = [10 20]; + data_range = [10 20]; + screen_length = 110; + + [out_data, out_range] = pspm_convert_pixel2unit_core(data, data_range, screen_length); + + expected_length_per_pixel = 110 / (20 - 10 + 1); % 110/11 + expected_out_data = [0 10] * expected_length_per_pixel; + expected_out_range = [0 10] * expected_length_per_pixel; + + testCase.verifyEqual(out_data, expected_out_data, 'AbsTol', 1e-12); + testCase.verifyEqual(out_range, expected_out_range, 'AbsTol', 1e-12); +end + +function testColumnVectorPreserved(testCase) + data = [1; 6; 11]; + data_range = [1 11]; + screen_length = 55; + + [out_data, out_range] = pspm_convert_pixel2unit_core(data, data_range, screen_length); + + expected_length_per_pixel = 55 / (11 - 1 + 1); % 55/11 + expected_out_data = [0; 5; 10] * expected_length_per_pixel; + expected_out_range = [0 10] * expected_length_per_pixel; + + testCase.verifyEqual(out_data, expected_out_data, 'AbsTol', 1e-12); + testCase.verifySize(out_data, size(data)); + testCase.verifyEqual(out_range, expected_out_range, 'AbsTol', 1e-12); +end + +function testSinglePixelRange(testCase) + data = 5; + data_range = [5 5]; + screen_length = 20; + + [out_data, out_range] = pspm_convert_pixel2unit_core(data, data_range, screen_length); + + % diff([5 5]) + 1 = 1 + expected_length_per_pixel = 20; + expected_out_data = 0; + expected_out_range = [0 0]; + + testCase.verifyEqual(out_data, expected_out_data, 'AbsTol', 1e-12); + testCase.verifyEqual(out_range, expected_out_range, 'AbsTol', 1e-12); +end +end +end \ No newline at end of file diff --git a/test/pspm_convert_ppg2hb_test.m b/test/pspm_convert_ppg2hb_test.m new file mode 100644 index 000000000..67036b761 --- /dev/null +++ b/test/pspm_convert_ppg2hb_test.m @@ -0,0 +1,142 @@ +classdef pspm_convert_ppg2hb_test < pspm_testcase + +properties +original_filename = '/home/bernd/git/PsPM/test/DatenZumTesten/ppg/pspm_SCAN_test.mat'; +input_filename = '/home/bernd/git/PsPM/test/DatenZumTesten/ppg/totest_pspm_SCAN_test.mat'; +end + +methods (TestClassSetup) +function check_original_file(this) +this.assertTrue(exist(this.original_filename, 'file') == 2, ... + sprintf('Input file not found: %s', this.original_filename)); +end +end + +methods (TestMethodSetup) +function reset_input_file(this) +[sts, msg] = copyfile(this.original_filename, this.input_filename); +this.assertTrue(sts, msg); +end +end + +methods (TestClassTeardown) +function cleanup(this) +if exist(this.input_filename, 'file') == 2 + delete(this.input_filename); +end +end +end + +methods (Test) + +% needs a test for py method + +function invalid_input(this) +this.verifyWarning(@() pspm_convert_ppg2hb(), 'ID:invalid_input'); +this.verifyWarning(@() pspm_convert_ppg2hb(1),'ID:invalid_input'); + +options = struct(); +options.method = 'wrong_method'; +this.verifyWarning(@() pspm_convert_ppg2hb(this.input_filename, options), 'ID:invalid_input'); + +end + +function basic_conversion_classic(this) +fn = this.input_filename; + +options = struct(); +options.method = 'classic'; +options.channel = 'ppg'; +options.channel_action = 'add'; +options.diagnostics = false; + +[nsts, ~, data] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); +n_channels = numel(data); + +[sts, outchannel] = pspm_convert_ppg2hb(fn, options); + +this.verifyEqual(sts, 1); +this.verifyTrue(isnumeric(outchannel)); +this.verifyGreaterThan(outchannel, 0); + +[nsts, infos, data] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); +this.verifyEqual(numel(data), n_channels + 1); + +this.verifyEqual(data{outchannel}.header.chantype, 'hb'); +this.verifyEqual(data{outchannel}.header.units, 'events'); +this.verifyEqual(data{outchannel}.header.sr, 1); + +this.verifyGreaterThan(numel(data{outchannel}.data), 1); +this.verifyTrue(all(diff(data{outchannel}.data) > 0)); + +% this.verifyTrue(isfield(infos, 'history')); +% this.verifyTrue(contains(infos.history{end}, ... +% 'Heart beat detection from ppg')); +end + +function channel_action_add_replace(this) +fn = this.input_filename; + +options = struct(); +options.method = 'classic'; +options.channel = 'ppg'; +options.channel_action = 'add'; +options.diagnostics = false; + +[nsts, ~, data, filestruct] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); + +% add hb +[sts, outch_add] = pspm_convert_ppg2hb(fn, options); +this.verifyEqual(sts, 1); + +[nsts, ~, data] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); +this.verifyEqual(numel(data), filestruct.numofchan + 1); +this.verifyEqual(data{outch_add}.header.chantype, 'hb'); + +% replace hb +options.channel_action = 'replace'; + +[sts, outch_replace] = pspm_convert_ppg2hb(fn, options); +this.verifyEqual(sts, 1); + +[nsts, ~, data] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); +this.verifyEqual(numel(data), filestruct.numofchan + 1); +this.verifyEqual(outch_replace, outch_add); +this.verifyEqual(data{outch_replace}.header.chantype, 'hb'); +end + +function no_pulse_found(this) +fn = this.input_filename; + +[nsts, infos, data] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); + +for i = 1:numel(data) + if strcmpi(data{i}.header.chantype, 'ppg') + data{i}.data(:) = 0; + break; + end +end + +outdata.infos = infos; +outdata.data = data; +outdata.options.overwrite = 1; + +nsts = pspm_load_data(fn, outdata); +this.verifyEqual(nsts, 1); + +options = struct(); +options.method = 'classic'; +options.channel = 'ppg'; +options.channel_action = 'add'; +options.diagnostics = false; + +this.verifyWarning(@() pspm_convert_ppg2hb(fn, options), 'ID:NoPulse'); +end +end +end \ No newline at end of file diff --git a/test/pspm_emg_pp_test.m b/test/pspm_emg_pp_test.m new file mode 100644 index 000000000..59d1f1c0a --- /dev/null +++ b/test/pspm_emg_pp_test.m @@ -0,0 +1,130 @@ +classdef pspm_emg_pp_test < pspm_testcase +% * Description +% Unittest class for the pspm_emg_pp function + +properties +original_filename = '/home/bernd/git/PsPM/test/DatenZumTesten/emg/pspm_TM012face.mat'; +input_filename = '/home/bernd/git/PsPM/test/DatenZumTesten/emg/totest_pspm_TM012face.mat'; +end + +methods (TestClassSetup) +function check_original_file(this) +this.assertTrue(exist(this.original_filename, 'file') == 2, ... + sprintf('Input file not found: %s', this.original_filename)); +end +end + +methods (TestMethodSetup) +function reset_input_file(this) +[sts, msg] = copyfile(this.original_filename, this.input_filename); +this.assertTrue(sts, msg); +end +end + +methods (TestClassTeardown) +function cleanup(this) +if exist(this.input_filename, 'file') == 2 + delete(this.input_filename); +end +end +end + +methods (Test) + +function invalid_input(this) +fn = this.input_filename; + +options = struct(); +options.mains_freq = 'abc'; + +this.verifyWarning(@() pspm_emg_pp(fn, options), ... + 'ID:invalid_input'); +end + +function basic_preprocessing(this) +fn = this.input_filename; + +options = struct(); +options.channel = 'emg'; +options.channel_action = 'add'; +options.mains_freq = 50; + +[nsts, ~, data, filestruct] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); + +[sts, outchannel] = pspm_emg_pp(fn, options); + +this.verifyEqual(sts, 1); +this.verifyTrue(isnumeric(outchannel)); +this.verifyGreaterThan(outchannel, 0); + +[nsts, infos, data] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); + +this.verifyEqual(numel(data), filestruct.numofchan + 1); +this.verifyEqual(data{outchannel}.header.chantype, 'emg_pp'); +this.verifyGreaterThan(numel(data{outchannel}.data), 0); +this.verifyTrue(all(isfinite(data{outchannel}.data))); + +% this.verifyTrue(isfield(infos, 'history')); +% this.verifyTrue(contains(infos.history{end}, 'EMG preprocessing')); +% this.verifyTrue(contains(infos.history{end}, 'Output channeltype: emg_pp')); +end + +function channel_action_add_replace(this) +fn = this.input_filename; + +options = struct(); +options.channel = 'emg'; +options.channel_action = 'add'; +options.mains_freq = 50; + +[nsts, ~, data, filestruct] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); + +% add emg_pp +[sts, outch_add] = pspm_emg_pp(fn, options); +this.verifyEqual(sts, 1); + +[nsts, ~, data] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); +this.verifyEqual(numel(data), filestruct.numofchan + 1); +this.verifyEqual(data{outch_add}.header.chantype, 'emg_pp'); + +% repace emg_pp +options.channel_action = 'replace'; +[sts, outch_replace] = pspm_emg_pp(fn, options); +this.verifyEqual(sts, 1); + +[nsts, ~, data] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); + +this.verifyEqual(numel(data), filestruct.numofchan + 1); +this.verifyEqual(outch_replace, outch_add); +this.verifyEqual(data{outch_replace}.header.chantype, 'emg_pp'); +end + +function channel_action_replace(this) +fn = this.input_filename; + +options = struct(); +options.channel = 'emg'; +options.channel_action = 'replace'; +options.mains_freq = 50; + +[nsts, ~, data, filestruct] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); + +% replace emg_pp +[sts, outch] = pspm_emg_pp(fn, options); +this.verifyEqual(sts, 1); + +[nsts, ~, data] = pspm_load_data(fn); +this.verifyEqual(nsts, 1); +this.verifyEqual(numel(data), filestruct.numofchan + 1); +this.verifyEqual(data{outch}.header.chantype, 'emg_pp'); + + +end +end +end \ No newline at end of file diff --git a/test/pspm_gaze_pp_test.m b/test/pspm_gaze_pp_test.m index dcb67c32c..eab532bfb 100644 --- a/test/pspm_gaze_pp_test.m +++ b/test/pspm_gaze_pp_test.m @@ -1,76 +1,146 @@ classdef pspm_gaze_pp_test < pspm_testcase - % Definition - % pspm_gaze_pp_test unittest classes for the pspm_gaze_pp function - % PsPM TestEnvironment - % (C) 2021 Teddy - properties +% Definition +% pspm_gaze_pp_test unittest classes for the pspm_gaze_pp function +% PsPM TestEnvironment +% (C) 2021 Teddy +% (C) Updated 2025 Bernhard von Raußendorf +properties raw_input_fn = fullfile('ImportTestData', 'eyelink', 'S114_s2.asc'); pspm_input_fn = ''; - end - methods(TestClassSetup) - function backup(this) - import = {}; - import{end + 1}.type = 'pupil_l'; - import{end}.eyelink_trackdist = 600; - import{end}.distance_unit = 'mm'; - import{end + 1}.type = 'pupil_r'; - import{end}.eyelink_trackdist = 600; - import{end}.distance_unit = 'mm'; - import{end + 1}.type = 'gaze_x_l'; - import{end}.eyelink_trackdist = 600; - import{end}.distance_unit = 'mm'; - import{end + 1}.type = 'gaze_y_l'; - import{end}.eyelink_trackdist = 600; - import{end}.distance_unit = 'mm'; - import{end + 1}.type = 'gaze_x_r'; - import{end}.eyelink_trackdist = 600; - import{end}.distance_unit = 'mm'; - import{end + 1}.type = 'gaze_y_r'; - import{end}.eyelink_trackdist = 600; - import{end}.distance_unit = 'mm'; - import{end + 1}.type = 'marker'; - options = struct(); - options.overwrite = 1; % to always overwrite - [sts, this.pspm_input_fn] = pspm_import(... - this.raw_input_fn, 'eyelink', import, options); - end - end - methods(Test) - function invalid_input(this) - % the function checks if the input filename and options are valid - % the input filename is only a number - this.verifyWarning(@()pspm_gaze_pp(52), 'ID:invalid_input'); - % the input filename refers to a non-existing file - this.verifyWarning(@()pspm_gaze_pp('abc'), 'ID:nonexistent_file'); - % the input filename is valid, but the channel definition is wrong - opt.channel = 'scr'; - this.verifyWarning(@()pspm_gaze_pp(this.pspm_input_fn, opt), 'ID:invalid_input'); - % the input filename is valid, but there are only 3 channels - opt.channel = 1:3; - this.verifyWarning(@()pspm_gaze_pp(this.pspm_input_fn, opt), 'ID:invalid_input'); - % the input filename is valid, but the channels are of the wrong type - opt.channel = 1:4; - this.verifyWarning(@()pspm_gaze_pp(this.pspm_input_fn, opt), 'ID:unexpected_channeltype'); - end - function preprocessed_channel(this) - % check if the channel name of the preprocessed file is correct - opt.channel = 'gaze'; - [~, out_channel] = this.verifyWarningFree(@() ... - pspm_gaze_pp(this.pspm_input_fn, opt)); - testdata = load(this.pspm_input_fn); - this.verifyEqual(testdata.data{out_channel(1)}.header.chantype,'gaze_x_c'); - this.verifyEqual(testdata.data{out_channel(2)}.header.chantype,'gaze_y_c'); - opt.channel = [5,3,6,4]; - [~, out_channel] = this.verifyWarningFree(@() ... - pspm_gaze_pp(this.pspm_input_fn, opt)); - testdata = load(this.pspm_input_fn); - this.verifyEqual(testdata.data{out_channel(1)}.header.chantype,'gaze_x_c'); - this.verifyEqual(testdata.data{out_channel(2)}.header.chantype,'gaze_y_c'); + backup_fn = ''; +end + +methods(TestClassSetup) +function backup(this) + import = {}; + import{end + 1}.type = 'pupil_l'; + import{end}.eyelink_trackdist = 600; + import{end}.distance_unit = 'mm'; + import{end + 1}.type = 'pupil_r'; + import{end}.eyelink_trackdist = 600; + import{end}.distance_unit = 'mm'; + import{end + 1}.type = 'gaze_x_l'; + import{end}.eyelink_trackdist = 600; + import{end}.distance_unit = 'mm'; + import{end + 1}.type = 'gaze_y_l'; + import{end}.eyelink_trackdist = 600; + import{end}.distance_unit = 'mm'; + import{end + 1}.type = 'gaze_x_r'; + import{end}.eyelink_trackdist = 600; + import{end}.distance_unit = 'mm'; + import{end + 1}.type = 'gaze_y_r'; + import{end}.eyelink_trackdist = 600; + import{end}.distance_unit = 'mm'; + import{end + 1}.type = 'marker'; + options = struct(); + options.overwrite = 1; % to always overwrite + + [sts, this.pspm_input_fn] = pspm_import( ... + this.raw_input_fn, 'eyelink', import, options); + + this.assertEqual(sts, 1); + this.assertTrue(exist(this.pspm_input_fn, 'file') == 2); + + [pathstr, name, ext] = fileparts(this.pspm_input_fn); + this.backup_fn = fullfile(pathstr, [name, '_backup', ext]); + + [sts, msg] = copyfile(this.pspm_input_fn, this.backup_fn); + this.assertTrue(sts, msg); +end +end +methods (TestMethodSetup) + function reset_input_file(this) + [sts, msg] = copyfile(this.backup_fn, this.pspm_input_fn); + this.assertTrue(sts, msg); end - end - methods(TestClassTeardown) - function restore(this) - delete(this.pspm_input_fn); +end + +methods(Test) +function invalid_input(this) + this.verifyWarning(@()pspm_gaze_pp(52), 'ID:invalid_input'); + this.verifyWarning(@()pspm_gaze_pp('abc'), 'ID:nonexistent_file'); + + opt.channel = 'scr'; + this.verifyWarning(@()pspm_gaze_pp(this.pspm_input_fn, opt), ... + 'ID:invalid_input'); + + opt.channel = 1:3; + this.verifyWarning(@()pspm_gaze_pp(this.pspm_input_fn, opt), ... + 'ID:invalid_input'); + + opt.channel = 1:4; + this.verifyWarning(@()pspm_gaze_pp(this.pspm_input_fn, opt), ... + 'ID:unexpected_channeltype'); +end +function preprocessed_channel(this) + opt.channel = 'gaze'; + opt.channel_action = 'add'; + + [sts, out_channel] = this.verifyWarningFree(@() ... + pspm_gaze_pp(this.pspm_input_fn, opt)); + + this.verifyEqual(sts, 1); + this.verifyEqual(numel(out_channel), 2); + + testdata = load(this.pspm_input_fn); + this.verifyEqual(testdata.data{out_channel(1)}.header.chantype, 'gaze_x_c'); + this.verifyEqual(testdata.data{out_channel(2)}.header.chantype, 'gaze_y_c'); + + opt.channel = [5, 3, 6, 4]; + opt.channel_action = 'add'; + + [sts, out_channel] = this.verifyWarningFree(@() ... + pspm_gaze_pp(this.pspm_input_fn, opt)); + + this.verifyEqual(sts, 1); + this.verifyEqual(numel(out_channel), 2); + + testdata = load(this.pspm_input_fn); + this.verifyEqual(testdata.data{out_channel(1)}.header.chantype, 'gaze_x_c'); + this.verifyEqual(testdata.data{out_channel(2)}.header.chantype, 'gaze_y_c'); +end +function channel_action_add_replace(this) + [nsts, ~, data] = pspm_load_data(this.pspm_input_fn); + this.verifyEqual(nsts, 1); + n_channels = numel(data); + + opt.channel = 'gaze'; + opt.channel_action = 'add'; + + [sts, out_add] = pspm_gaze_pp(this.pspm_input_fn, opt); + this.verifyEqual(sts, 1); + + [nsts, ~, data] = pspm_load_data(this.pspm_input_fn); + this.verifyEqual(nsts, 1); + this.verifyEqual(numel(data), n_channels + 2); + this.verifyEqual(data{out_add(1)}.header.chantype, 'gaze_x_c'); + this.verifyEqual(data{out_add(2)}.header.chantype, 'gaze_y_c'); + + opt.channel_action = 'replace'; + + [sts, out_replace] = pspm_gaze_pp(this.pspm_input_fn, opt); + this.verifyEqual(sts, 1); + + [nsts, ~, data] = pspm_load_data(this.pspm_input_fn); + this.verifyEqual(nsts, 1); + this.verifyEqual(numel(data), n_channels + 2); + this.verifyEqual(out_replace, out_add); + this.verifyEqual(data{out_replace(1)}.header.chantype, 'gaze_x_c'); + this.verifyEqual(data{out_replace(2)}.header.chantype, 'gaze_y_c'); +end + +end + + +methods (TestClassTeardown) + function cleanup(this) + if exist(this.pspm_input_fn, 'file') == 2 + delete(this.pspm_input_fn); + end + + if exist(this.backup_fn, 'file') == 2 + delete(this.backup_fn); + end end - end +end end diff --git a/test/pspm_resp_pp_test.m b/test/pspm_resp_pp_test.m index e4c2ddc81..b18c59b6e 100644 --- a/test/pspm_resp_pp_test.m +++ b/test/pspm_resp_pp_test.m @@ -60,6 +60,28 @@ function compare_results_to_results_obtained_from_r660_version(this) this.verifyThat(old_data{i}.data, IsEqualTo(new_data{i}.data, 'Within', RelativeTolerance(1e-10))); end end + function replace_original_channel_with_single_output(this) + [sts, ~, data_before] = pspm_load_data(this.input_filename); + assert(sts == 1); + assert(numel(data_before) == 1); + assert(strcmpi(data_before{1}.header.chantype, 'resp')); + + options = this.options; + options.channel = this.resp_channel; + options.datatype = {'rp'}; + options.channel_action = 'replace'; + + [sts, outchannel] = pspm_resp_pp(this.input_filename, this.sampling_rate, options); + assert(sts == 1); + assert(isequal(outchannel, 1)); + + [sts, ~, data_after] = pspm_load_data(this.input_filename); + assert(sts == 1); + assert(numel(data_after) == 1); + assert(strcmpi(data_after{1}.header.chantype, 'rp')); + assert(data_after{1}.header.sr == this.sampling_rate); + assert(~isempty(data_after{1}.data)); + end % TODO: Write more tests end methods(TestClassTeardown) From d1c00ad47dc38b57c3d46721f1e154a2f504ecbb Mon Sep 17 00:00:00 2001 From: 4gwe Date: Mon, 11 May 2026 02:04:13 +0200 Subject: [PATCH 9/9] more tests --- test/pspm_convert_au2unit_test.m | 84 +++- test/pspm_expand_epochs_test.m | 675 ++++++++++++++++--------------- test/pspm_pp_test.m | 31 ++ test/pspm_resp_pp_test.m | 37 +- 4 files changed, 503 insertions(+), 324 deletions(-) diff --git a/test/pspm_convert_au2unit_test.m b/test/pspm_convert_au2unit_test.m index c6c56c147..851322fe3 100644 --- a/test/pspm_convert_au2unit_test.m +++ b/test/pspm_convert_au2unit_test.m @@ -101,6 +101,86 @@ function testVectorAreaUnitConversionTEST(testCase) data2 = unit2au(out,'mm',100,'area',0.1,50,'mm'); testCase.verifyEqual(data , data2, 'AbsTol', 1e-12); end +function testFileChannelActionAddAndReplace(testCase) + fn = fullfile('ImportTestData', 'eyelink', 'pspm_u_sc4b31.mat'); + fn_test = fullfile('ImportTestData', 'eyelink', 'pspm_u_sc4b31_au_addreplace.mat'); + + S = load(fn); + save(fn_test, '-struct', 'S'); + + cleanupObj = onCleanup(@() delete(fn_test)); + + unit = 'mm'; + distance = 600; + record_method = 'diameter'; + multiplicator = 0.04; + reference_distance = 500; + reference_unit = 'mm'; + + %% Load original pupil channel + [sts, original_channel, ~, pos] = pspm_load_channel(fn_test, 'pupil', 'pupil'); + testCase.verifyEqual(sts, 1); + + original_data = original_channel.data; + + %% Convert original mm data to artificial au data + au_data = unit2au(original_data, unit, distance, record_method, ... + multiplicator, reference_distance, reference_unit); + + newdata = original_channel; + newdata.data = au_data; + newdata.header.units = 'au'; + + [sts, ~] = pspm_write_channel(fn_test, newdata, 'replace', struct('channel', pos)); + testCase.verifyEqual(sts, 1); + + %% Count channels before add + [sts, ~, ~, filestruct_before] = pspm_load_data(fn_test, 'none'); + testCase.verifyEqual(sts, 1); + numofchanbefore = filestruct_before.numofchan; + + %% Test add + options = struct(); + options.channel = pos; + options.channel_action = 'add'; + + [sts, out_add] = pspm_convert_au2unit(fn_test, unit, distance, record_method, ... + multiplicator, reference_distance, reference_unit, options); + + testCase.verifyEqual(sts, 1); + + [sts, ~, data_after_add, filestruct_after_add] = pspm_load_data(fn_test, 'none'); + testCase.verifyEqual(sts, 1); + + testCase.verifyEqual(filestruct_after_add.numofchan, numofchanbefore + 1); + testCase.verifyEqual(out_add, filestruct_after_add.numofchan); + testCase.verifyEqual(data_after_add{out_add}.header.units, unit); + testCase.verifyEqual(data_after_add{out_add}.header.chantype, original_channel.header.chantype); + + valid = isfinite(original_data); + testCase.verifyEqual(data_after_add{out_add}.data(valid), ... + original_data(valid), 'AbsTol', 1e-12); + + %% Test replace + options.channel = pos; + options.channel_action = 'replace'; + + [sts, out_replace] = pspm_convert_au2unit(fn_test, unit, distance, record_method, ... + multiplicator, reference_distance, reference_unit, options); + + testCase.verifyEqual(sts, 1); + + [sts, ~, data_after_replace, filestruct_after_replace] = pspm_load_data(fn_test, 'none'); + testCase.verifyEqual(sts, 1); + + testCase.verifyEqual(filestruct_after_replace.numofchan, filestruct_after_add.numofchan); + testCase.verifyEqual(out_replace, pos); + testCase.verifyEqual(data_after_replace{pos}.header.units, unit); + testCase.verifyEqual(data_after_replace{pos}.header.chantype, original_channel.header.chantype); + + testCase.verifyEqual(data_after_replace{pos}.data(valid), ... + original_data(valid), 'AbsTol', 1e-12); +end %% Error handeling function testErrorHandling(testCase) @@ -140,8 +220,8 @@ function testErrorHandling(testCase) %% Pspm files function testFileRoundTripConvertAu2Unit(testCase) - fn = '/home/bernd/git/PsPM/ImportTestData/eyelink/pspm_u_sc4b31.mat'; - fn_roundtrip = '/home/bernd/git/PsPM/ImportTestData/eyelink/pspm_u_sc4b31_au.mat'; + fn = fullfile('ImportTestData', 'eyelink', 'pspm_u_sc4b31.mat'); + fn_roundtrip = fullfile('ImportTestData', 'eyelink', 'pspm_u_sc4b31_au.mat'); % copy file S = load(fn); diff --git a/test/pspm_expand_epochs_test.m b/test/pspm_expand_epochs_test.m index 2b20de083..b8326b992 100644 --- a/test/pspm_expand_epochs_test.m +++ b/test/pspm_expand_epochs_test.m @@ -1,335 +1,376 @@ classdef pspm_expand_epochs_test < pspm_testcase - % ● Description - % Unit test class for the pspm_expand_epochs function. - % ● Authorship - % (C) 2024 Bernhard Agoué von Raußendorf - - properties(Constant) - % Define test data filenames - epochs_filename = 'test_epochs.mat'; - data_filename = 'test_data.mat'; - backup_data_filename = 'test_data_backup.mat'; - NoNaN_data_filename = 'test_data_NoNaN.mat'; - backup_NoNaN_data_filename = 'test_data_NoNaN_backup.mat'; - - expansion = [1, 1]; % Expand epochs by 1 second before and after - options = struct('overwrite', 1); +% ● Description +% Unit test class for the pspm_expand_epochs function. +% ● Authorship +% (C) 2024 Bernhard Agoué von Raußendorf + +properties(Constant) +% Define test data filenames +epochs_filename = 'test_epochs.mat'; +data_filename = 'test_data.mat'; +backup_data_filename = 'test_data_backup.mat'; +NoNaN_data_filename = 'test_data_NoNaN.mat'; +backup_NoNaN_data_filename = 'test_data_NoNaN_backup.mat'; + +expansion = [1, 1]; % Expand epochs by 1 second before and after +options = struct('overwrite', 1); +end + +methods(TestClassSetup) +function generate_test_data(this) + % Generate test epochs file + epochs = [5, 10; 15, 20]; + save(this.epochs_filename, 'epochs'); + + % Generate test data file with missing data in a channel + channels{1}.chantype = 'scr' ; + channels{1}.sr = 100; + + duration = 25; % seconds + outfile = pspm_testdata_gen(channels, duration, this.data_filename); + + copyfile(this.data_filename, this.NoNaN_data_filename); + +% Introduce missing data (NaNs) between 12 and 18 seconds + sr = channels{1}.sr; + data_length = duration * sr; + data = outfile.data{1}.data; + missing_indices = (12*sr):(18*sr); + data(missing_indices) = NaN; + + % Use pspm_write_channel to save the modified data + newdata = struct('header', outfile.data{1}.header, 'data', data); + options = struct('channel', 1); + [wsts, out] = pspm_write_channel(this.data_filename, {newdata}, 'replace', options); + if wsts < 1 + error('Failed to write modified channel data'); end - methods(TestClassSetup) - function generate_test_data(this) - % Generate test epochs file - epochs = [5, 10; 15, 20]; - save(this.epochs_filename, 'epochs'); + % Backup the data file + copyfile(this.data_filename, this.backup_data_filename); +end +end + +methods(Test) +function InValidInputError(this) + % no input + this.verifyWarning(@()pspm_expand_epochs(), 'ID:invalid_input'); + % invalid input + this.verifyWarning(@()pspm_expand_epochs('invalid input'), 'ID:invalid_input'); + % Invalid expansion vector + epochs = [5, 10; 15, 20]; + invalid_expansion = [1]; % Should be a 2-element vector + this.verifyWarning(@()pspm_expand_epochs(epochs, invalid_expansion), 'ID:invalid_input'); +end + +function InValidInputEpochfileError(this) + + expansion = this.expansion; + options = this.options; + fn = 'nofile.mat'; + + this.verifyWarning(@()pspm_expand_epochs(fn, expansion, options) , 'ID:invalid_input'); +end + +function InValidInputDataFileError(this) + + expansion = this.expansion; + options = this.options; + epoch = [1 , 1]; + fn = 'nofile.mat'; + this.verifyWarning(@()pspm_expand_epochs(fn, expansion, options) , 'ID:invalid_input'); +end + +function InValidEpochsFormatError(this) + options = this.options; + + % Test single column epochs + invalid_epochs = [1; 2; 3]; + [sts , ~] = pspm_expand_epochs(invalid_epochs, this.expansion,options); + this.verifyLessThan(sts,1) + % Test non-numeric epochs + cell_epochs = {[1,2]; [3,4]}; + [sts , ~] = pspm_expand_epochs(invalid_epochs, this.expansion,options); + this.verifyLessThan(sts,1) + +end + +function InValidInputDataFileChannelError(this) + % Tests the error handeling of the loading of the channel + + channel1 = 99; + channel2 = -99; + channel3 = 'not a channel'; + + Exp = this.expansion; + options = struct('channel_action', 'replace','overwrite',1); + fn = this.data_filename; + + this.verifyWarning(@()pspm_expand_epochs(fn, channel1, Exp, options), 'ID:invalid_input' ) + this.verifyWarning(@()pspm_expand_epochs(fn, channel2, Exp, options), 'ID:invalid_input' ) + this.verifyWarning(@()pspm_expand_epochs(fn, channel3, Exp, options), 'ID:invalid_chantype' ) + + % Restors the datafile + copyfile(this.backup_data_filename, this.data_filename); +end + +function InValidInputDataFileChannelActionError(this) + % Tests the error handeling of the loading of the channel + + channel1 = 1; + Exp = this.expansion; + options = struct('channel_action', 'no_action','overwrite', 1); + fn = this.data_filename; + + this.verifyWarning(@()pspm_expand_epochs(fn, channel1, Exp, options), 'ID:invalid_input' ) - % Generate test data file with missing data in a channel - channels{1}.chantype = 'scr' ; - channels{1}.sr = 100; + % Restors the datafile + copyfile(this.backup_data_filename, this.data_filename); - duration = 25; % seconds - outfile = pspm_testdata_gen(channels, duration, this.data_filename); - copyfile(this.data_filename, this.NoNaN_data_filename); +end + +function NoNaNDataFileEpochReplaceTest(this) + % Tests the error handeling of the loading of the channel - % Introduce missing data (NaNs) between 12 and 18 seconds - sr = channels{1}.sr; - data_length = duration * sr; - data = outfile.data{1}.data; - missing_indices = (12*sr):(18*sr); - data(missing_indices) = NaN; + channel1 = 1; + Exp = this.expansion; + options = struct('channel_action', 'replace','overwrite', 1); + + fn = this.NoNaN_data_filename; + [~, ~, ~, filestruct] = pspm_load_data(fn, 'none'); - % Use pspm_write_channel to save the modified data - newdata = struct('header', outfile.data{1}.header, 'data', data); - options = struct('channel', 1); - [wsts, out] = pspm_write_channel(this.data_filename, {newdata}, 'replace', options); - if wsts < 1 - error('Failed to write modified channel data'); - end + numofchanbefor = filestruct.numofchan; + + [sts, expanded_epochs] = pspm_expand_epochs(fn, channel1, Exp, options); + this.verifyEqual(sts,1) - % Backup the data file - copyfile(this.data_filename, this.backup_data_filename); - end - end + % checks if the number of channel stays the same + [~, ~, ~, filestruct2] = pspm_load_data(fn, 'none'); + numofchanafter = filestruct2.numofchan; - methods(Test) - function InValidInputError(this) - % no input - this.verifyWarning(@()pspm_expand_epochs(), 'ID:invalid_input'); - % invalid input - this.verifyWarning(@()pspm_expand_epochs('invalid input'), 'ID:invalid_input'); - % Invalid expansion vector - epochs = [5, 10; 15, 20]; - invalid_expansion = [1]; % Should be a 2-element vector - this.verifyWarning(@()pspm_expand_epochs(epochs, invalid_expansion), 'ID:invalid_input'); - end - - function InValidInputEpochfileError(this) - - expansion = this.expansion; - options = this.options; - fn = 'nofile.mat'; - - this.verifyWarning(@()pspm_expand_epochs(fn, expansion, options) , 'ID:invalid_input'); - end - - function InValidInputDataFileError(this) - - expansion = this.expansion; - options = this.options; - epoch = [1 , 1]; - fn = 'nofile.mat'; - this.verifyWarning(@()pspm_expand_epochs(fn, expansion, options) , 'ID:invalid_input'); - end - - function InValidEpochsFormatError(this) - options = this.options; - - % Test single column epochs - invalid_epochs = [1; 2; 3]; - [sts , ~] = pspm_expand_epochs(invalid_epochs, this.expansion,options); - this.verifyLessThan(sts,1) - % Test non-numeric epochs - cell_epochs = {[1,2]; [3,4]}; - [sts , ~] = pspm_expand_epochs(invalid_epochs, this.expansion,options); - this.verifyLessThan(sts,1) - - end - - function InValidInputDataFileChannelError(this) - % Tests the error handeling of the loading of the channel - - channel1 = 99; - channel2 = -99; - channel3 = 'not a channel'; - - Exp = this.expansion; - options = struct('channel_action', 'replace','overwrite',1); - fn = this.data_filename; - - this.verifyWarning(@()pspm_expand_epochs(fn, channel1, Exp, options), 'ID:invalid_input' ) - this.verifyWarning(@()pspm_expand_epochs(fn, channel2, Exp, options), 'ID:invalid_input' ) - this.verifyWarning(@()pspm_expand_epochs(fn, channel3, Exp, options), 'ID:invalid_chantype' ) - - % Restors the datafile - copyfile(this.backup_data_filename, this.data_filename); - end - - function InValidInputDataFileChannelActionError(this) - % Tests the error handeling of the loading of the channel - - channel1 = 1; - Exp = this.expansion; - options = struct('channel_action', 'no_action','overwrite', 1); - fn = this.data_filename; + this.verifyEqual(numofchanbefor,numofchanafter) +end + +function NoNaNDataFileEpochAddTest(this) + % Tests the error handeling of the loading of the channel + + channel1 = 1; + Exp = this.expansion; + options = struct('channel_action', 'add','overwrite',1); + + fn = this.NoNaN_data_filename; + + % makes a backup + copyfile(fn,this.backup_NoNaN_data_filename) + + % count the channels + [~, ~, ~, filestruct] = pspm_load_data(fn, 'none'); + numofchanbefor = filestruct.numofchan; + + [sts, expanded_epochs] = pspm_expand_epochs(fn, channel1, Exp, options); + this.verifyEqual(sts,1) + + % checks if the number of channel increases + [~, ~, ~, filestruct2] = pspm_load_data(fn, 'none'); + numofchanafter = filestruct2.numofchan; + + this.verifyEqual(numofchanbefor+1,numofchanafter) + + movefile(this.backup_NoNaN_data_filename,fn) + +end + + +% function NoNaNDataFileNoEpochOverwriteError(this) +% % Tests the error handeling of the loading of the channel +% +% channel1 = 1; +% Exp = this.expansion; +% options = struct('channel_action', 'replace','overwrite', 1); % +% +% fn = this.NoNaN_data_filename; +% +% [pathstr, name, ext] = fileparts(fn); +% fn_out = fullfile(pathstr, ['e', name, ext]); +% copyfile(fn,fn_out) +% +% this.verifyWarning(@()pspm_expand_epochs(fn, channel1, Exp, options), 'ID:empty_channel') +% +% % add pspm_get_timing +% this.verifyTrue(exist(fn_out,'file'),'False') +% +% end + +function EpandEpochsWithEpochsTest(this) + % Test expanding epochs given an epoch matrix + import matlab.unittest.constraints.IsEqualTo + + epochs = [5, 10; 15, 20]; + expansion = this.expansion; + [sts, expanded_epochs] = pspm_expand_epochs(epochs, expansion); + + % Expected expanded epochs + expected_epochs = [4, 11; 14, 21]; + + this.verifyEqual(sts, 1); + this.verifyThat(expanded_epochs, IsEqualTo(expected_epochs)); +end + +function ExpandEpochsWithEpochsFileTest(this) + expansion = this.expansion; + options = this.options; + fn = this.epochs_filename; + [sts, output_file] = pspm_expand_epochs(fn, expansion, options); + + % Load the expanded epochs + loaded_epochs = load(output_file); + expanded_epochs = loaded_epochs.epochs; + + % Expected expanded epochs + expected_epochs = [4, 11; 14, 21]; + + + this.verifyEqual(sts, 1); + this.verifyEqual(expanded_epochs,expected_epochs) +end + +function ExpandEpochsWithDataFileReplaceTest(this) + % Test expanding epochs given a data file + + channel = 1; % Assuming the missing data is in channel 1 + Exp = [1,1]; %this.expansion; + options = struct('channel_action', 'replace','overwrite',1); + fn = this.data_filename; + + % Run the function + [sts, channel_index] = pspm_expand_epochs(fn, channel, Exp, options); + + % Load the data and check that missing data has been expanded + [sts, ~, data,~] = pspm_load_data(this.data_filename); + this.verifyEqual(sts, 1); + + sr = data{channel_index}.header.sr; + data_values = data{channel_index}.data; + + % Expected missing data indices + original_missing_indices = (12*sr):(18*sr); + expanded_missing_indices = ((12-Exp(1))*sr):((18+Exp(2))*sr); + + % Verify that data is NaN in the expected expanded intervals + % makes a logical array of the real missing values: NaN -> 1 + missing_indices = isnan(data_values); + % makes a logical array of expacted missing values: NaN -> 1 + expected_missing_logical = false(size(data_values)); + expected_missing_logical(expanded_missing_indices) = true; + + this.verifyEqual(sts, 1) + this.verifyEqual(missing_indices,expected_missing_logical); + + % Restors the datafile + copyfile(this.backup_data_filename, this.data_filename); +end + +function ExpandEpochsWithDataFileAddTest(this) + % Test expanding epochs given a data file with 'add' option + + channel = 1; + Exp = [5,2]; % different expansion then above + options = struct('channel_action', 'add','overwrite',1); + fn = this.data_filename; + + % Run the function + [sts, channel_index] = pspm_expand_epochs(fn, channel, Exp, options); + + % Load the data and check that missing data has been expanded + [sts, ~, data, filestruct] = pspm_load_data(this.data_filename); + this.verifyEqual(sts, 1); + + sr = data{channel_index}.header.sr; + data_values = data{channel_index}.data; + + % Expected missing data indices + original_missing_indices = (12*sr):(18*sr); + expanded_missing_indices = ((12-Exp(1))*sr):((18+Exp(2))*sr); + + + % Verify that data is NaN in the expected expanded intervals + missing_indices = isnan(data_values); + % + expected_missing_logical = false(size(data_values)); + %expected_missing_logical(original_missing_indices) = true; + expected_missing_logical(expanded_missing_indices) = true; + + this.verifyEqual(sts,1) + this.verifyEqual(filestruct.numofchan , 2) % checks if the channel was added? + this.verifyEqual(missing_indices, expected_missing_logical); + - this.verifyWarning(@()pspm_expand_epochs(fn, channel1, Exp, options), 'ID:invalid_input' ) - - % Restors the datafile - copyfile(this.backup_data_filename, this.data_filename); - - - end - - function NoNaNDataFileEpochReplaceTest(this) - % Tests the error handeling of the loading of the channel - - channel1 = 1; - Exp = this.expansion; - options = struct('channel_action', 'replace','overwrite', 1); - - fn = this.NoNaN_data_filename; - [~, ~, ~, filestruct] = pspm_load_data(fn, 'none'); - - numofchanbefor = filestruct.numofchan; - - [sts, expanded_epochs] = pspm_expand_epochs(fn, channel1, Exp, options); - this.verifyEqual(sts,1) - - % checks if the number of channel stays the same - [~, ~, ~, filestruct2] = pspm_load_data(fn, 'none'); - numofchanafter = filestruct2.numofchan; - - this.verifyEqual(numofchanbefor,numofchanafter) - end - - function NoNaNDataFileEpochAddTest(this) - % Tests the error handeling of the loading of the channel - - channel1 = 1; - Exp = this.expansion; - options = struct('channel_action', 'add','overwrite',1); - - fn = this.NoNaN_data_filename; - - % makes a backup - copyfile(fn,this.backup_NoNaN_data_filename) - - % count the channels - [~, ~, ~, filestruct] = pspm_load_data(fn, 'none'); - numofchanbefor = filestruct.numofchan; - - [sts, expanded_epochs] = pspm_expand_epochs(fn, channel1, Exp, options); - this.verifyEqual(sts,1) - - % checks if the number of channel increases - [~, ~, ~, filestruct2] = pspm_load_data(fn, 'none'); - numofchanafter = filestruct2.numofchan; - - this.verifyEqual(numofchanbefor+1,numofchanafter) - - movefile(this.backup_NoNaN_data_filename,fn) - - end - - - % function NoNaNDataFileNoEpochOverwriteError(this) - % % Tests the error handeling of the loading of the channel - % - % channel1 = 1; - % Exp = this.expansion; - % options = struct('channel_action', 'replace','overwrite', 1); % - % - % fn = this.NoNaN_data_filename; - % - % [pathstr, name, ext] = fileparts(fn); - % fn_out = fullfile(pathstr, ['e', name, ext]); - % copyfile(fn,fn_out) - % - % this.verifyWarning(@()pspm_expand_epochs(fn, channel1, Exp, options), 'ID:empty_channel') - % - % % add pspm_get_timing - % this.verifyTrue(exist(fn_out,'file'),'False') - % - % end - - function EpandEpochsWithEpochsTest(this) - % Test expanding epochs given an epoch matrix - import matlab.unittest.constraints.IsEqualTo - - epochs = [5, 10; 15, 20]; - expansion = this.expansion; - [sts, expanded_epochs] = pspm_expand_epochs(epochs, expansion); - - % Expected expanded epochs - expected_epochs = [4, 11; 14, 21]; - - this.verifyEqual(sts, 1); - this.verifyThat(expanded_epochs, IsEqualTo(expected_epochs)); - end - - function ExpandEpochsWithEpochsFileTest(this) - - - expansion = this.expansion; - options = this.options; - fn = this.epochs_filename; - [sts, output_file] = pspm_expand_epochs(fn, expansion, options); - - % Load the expanded epochs - loaded_epochs = load(output_file); - expanded_epochs = loaded_epochs.epochs; - - % Expected expanded epochs - expected_epochs = [4, 11; 14, 21]; - - - this.verifyEqual(sts, 1); - this.verifyEqual(expanded_epochs,expected_epochs) - end - - function ExpandEpochsWithDataFileReplaceTest(this) - % Test expanding epochs given a data file - - channel = 1; % Assuming the missing data is in channel 1 - Exp = [1,1]; %this.expansion; - options = struct('channel_action', 'replace','overwrite',1); - fn = this.data_filename; - - % Run the function - [sts, channel_index] = pspm_expand_epochs(fn, channel, Exp, options); - - % Load the data and check that missing data has been expanded - [sts, ~, data,~] = pspm_load_data(this.data_filename); - this.verifyEqual(sts, 1); - - sr = data{channel_index}.header.sr; - data_values = data{channel_index}.data; - - % Expected missing data indices - original_missing_indices = (12*sr):(18*sr); - expanded_missing_indices = ((12-Exp(1))*sr):((18+Exp(2))*sr); - - % Verify that data is NaN in the expected expanded intervals - % makes a logical array of the real missing values: NaN -> 1 - missing_indices = isnan(data_values); - % makes a logical array of expacted missing values: NaN -> 1 - expected_missing_logical = false(size(data_values)); - expected_missing_logical(expanded_missing_indices) = true; - - this.verifyEqual(sts, 1) - this.verifyEqual(missing_indices,expected_missing_logical); - - % Restors the datafile - copyfile(this.backup_data_filename, this.data_filename); - end - - function ExpandEpochsWithDataFileAddTest(this) - % Test expanding epochs given a data file with 'add' option - - channel = 1; - Exp = [5,2]; % different expansion then above - options = struct('channel_action', 'add','overwrite',1); - fn = this.data_filename; - - % Run the function - [sts, channel_index] = pspm_expand_epochs(fn, channel, Exp, options); - - % Load the data and check that missing data has been expanded - [sts, ~, data, filestruct] = pspm_load_data(this.data_filename); - this.verifyEqual(sts, 1); - - sr = data{channel_index}.header.sr; - data_values = data{channel_index}.data; - - % Expected missing data indices - original_missing_indices = (12*sr):(18*sr); - expanded_missing_indices = ((12-Exp(1))*sr):((18+Exp(2))*sr); - - - % Verify that data is NaN in the expected expanded intervals - missing_indices = isnan(data_values); - % - expected_missing_logical = false(size(data_values)); - %expected_missing_logical(original_missing_indices) = true; - expected_missing_logical(expanded_missing_indices) = true; - - this.verifyEqual(sts,1) - this.verifyEqual(filestruct.numofchan , 2) % checks if the channel was added? - this.verifyEqual(missing_indices, expected_missing_logical); - - - % Restors the datafile - copyfile(this.backup_data_filename, this.data_filename); - - end + % Restors the datafile + copyfile(this.backup_data_filename, this.data_filename); + +end - end +function ChannelActionAddAndReplaceTest(this) + % Tests if channel_action add and replace behave correctly + + channel1 = 1; + Exp = this.expansion; + fn = this.data_filename; + + % Restore clean datafile before test + copyfile(this.backup_data_filename, fn); + + % Count channels before + [~, ~, ~, filestruct] = pspm_load_data(fn, 'none'); + numofchanbefor = filestruct.numofchan; + + % Test add + options = struct('channel_action', 'add', 'overwrite', 1); + + [sts, channel_index] = pspm_expand_epochs(fn, channel1, Exp, options); + this.verifyEqual(sts, 1) + + [~, ~, ~, filestruct2] = pspm_load_data(fn, 'none'); + numofchanafteradd = filestruct2.numofchan; + + this.verifyEqual(numofchanbefor + 1, numofchanafteradd) + this.verifyEqual(channel_index, numofchanafteradd) - methods(TestClassTeardown) - function cleanup(this) - % Restore the original data file - movefile(this.backup_data_filename, this.data_filename); + % Test replace + options = struct('channel_action', 'replace', 'overwrite', 1); - % Delete the test files - delete(this.epochs_filename); - - if isfile(['e', this.epochs_filename]) - delete(['e', this.epochs_filename]); - end + [sts, channel_index] = pspm_expand_epochs(fn, channel1, Exp, options); + this.verifyEqual(sts, 1) + [~, ~, ~, filestruct3] = pspm_load_data(fn, 'none'); + numofchanafterreplace = filestruct3.numofchan; - delete(this.data_filename); - delete(this.NoNaN_data_filename); - end + this.verifyEqual(numofchanafteradd, numofchanafterreplace) + this.verifyEqual(channel_index, channel1) + + % Restors the datafile + copyfile(this.backup_data_filename, this.data_filename); +end + + +end + +methods(TestClassTeardown) +function cleanup(this) + % Restore the original data file + movefile(this.backup_data_filename, this.data_filename); + + % Delete the test files + delete(this.epochs_filename); + + if isfile(['e', this.epochs_filename]) + delete(['e', this.epochs_filename]); end + + + delete(this.data_filename); + delete(this.NoNaN_data_filename); +end +end end diff --git a/test/pspm_pp_test.m b/test/pspm_pp_test.m index 2e6425bd4..07e71d226 100644 --- a/test/pspm_pp_test.m +++ b/test/pspm_pp_test.m @@ -35,6 +35,21 @@ function median_test(this) this.verifyTrue(filestruct.numofchan == numel(channels), 'the returned file contains not as many channels as the inputfile'); delete(fn); end + function median_test_add(this) + %generate testdata + channels{1}.chantype = 'scr'; + channels{2}.chantype = 'hb'; + channels{3}.chantype = 'scr'; + fn = 'testfile549813.mat'; + pspm_testdata_gen(channels, 10, fn); + %filter one channel + [sts, outchannel] = pspm_pp('median', fn, 3, 50, struct('channel_action', 'add')); + this.verifyTrue(sts == 1); + [sts, infos, data, filestruct] = pspm_load_data(fn, 'none'); + this.verifyTrue(sts == 1, 'the returned file couldn''t be loaded'); + this.verifyTrue(filestruct.numofchan == numel(channels)+1, 'the returned file does not contain one more channels as the inputfile'); + delete(fn); + end function butter_test(this) %generate testdata channels{1}.chantype = 'scr'; @@ -51,5 +66,21 @@ function butter_test(this) this.verifyTrue(filestruct.numofchan == numel(channels), 'the returned file contains not as many channels as the inputfile'); delete(fn); end + function butter_test_add(this) + %generate testdata + channels{1}.chantype = 'scr'; + channels{2}.chantype = 'hb'; + channels{3}.chantype = 'scr'; + fn = 'testfile549814.mat'; + pspm_testdata_gen(channels, 10, fn); + %filter one channel + filt = struct('hporder', 1, 'lporder', 1, 'hpfreq', 1, 'lpfreq', 4, 'down', 8); + [sts, outchannel] = pspm_pp('butter', fn, 'scr', filt, struct('channel_action', 'add')); + this.verifyTrue(sts == 1); + [sts, infos, data, filestruct] = pspm_load_data(fn, 'none'); + this.verifyTrue(sts == 1, 'the returned file couldn''t be loaded'); + this.verifyTrue(filestruct.numofchan == numel(channels)+1, 'the returned file does not contain one more channels as the inputfile'); + delete(fn); + end end end diff --git a/test/pspm_resp_pp_test.m b/test/pspm_resp_pp_test.m index b18c59b6e..ce9192a8e 100644 --- a/test/pspm_resp_pp_test.m +++ b/test/pspm_resp_pp_test.m @@ -17,6 +17,12 @@ function backup(this) assert(sts == 1); end end + methods(TestMethodSetup) + function restore_before_each_test(this) + sts = copyfile(this.backup_filename, this.input_filename); + assert(sts == 1); + end + end methods(Test) function invalid_input(this) % no argument @@ -66,21 +72,38 @@ function replace_original_channel_with_single_output(this) assert(numel(data_before) == 1); assert(strcmpi(data_before{1}.header.chantype, 'resp')); + % add options = this.options; options.channel = this.resp_channel; options.datatype = {'rp'}; + options.channel_action = 'add'; + + [sts, outchannel] = pspm_resp_pp(this.input_filename, this.sampling_rate, options); + assert(sts == 1); + assert(isequal(outchannel, 2)); + + [sts, ~, data_after] = pspm_load_data(this.input_filename); + assert(sts == 1); + assert(numel(data_after) == 2); + assert(strcmpi(data_after{2}.header.chantype, 'rp')); + assert(data_after{2}.header.sr == this.sampling_rate); + assert(~isempty(data_after{2}.data)); + + % replace options.channel_action = 'replace'; [sts, outchannel] = pspm_resp_pp(this.input_filename, this.sampling_rate, options); assert(sts == 1); - assert(isequal(outchannel, 1)); + assert(isequal(outchannel, 2)); [sts, ~, data_after] = pspm_load_data(this.input_filename); assert(sts == 1); - assert(numel(data_after) == 1); - assert(strcmpi(data_after{1}.header.chantype, 'rp')); - assert(data_after{1}.header.sr == this.sampling_rate); - assert(~isempty(data_after{1}.data)); + assert(numel(data_after) == 2); + assert(strcmpi(data_after{2}.header.chantype, 'rp')); + assert(data_after{2}.header.sr == this.sampling_rate); + assert(~isempty(data_after{2}.data)); + + end % TODO: Write more tests end @@ -89,6 +112,10 @@ function restore(this) sts = copyfile(this.backup_filename, this.input_filename); assert(sts == 1); delete(this.backup_filename); + + if exist(this.backup_filename, 'file') + delete(this.backup_filename); + end end end end