diff --git a/src/pspm_cfg/pspm_cfg_selector_channel_action.m b/src/pspm_cfg/pspm_cfg_selector_channel_action.m index 1e3e71bb..e82e3ab2 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_convert_au2unit.m b/src/pspm_convert_au2unit.m index 55b6667a..207c4404 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. @@ -163,25 +163,32 @@ [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; 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/src/pspm_write_channel.m b/src/pspm_write_channel.m index 2f3dbd7d..b716b896 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; diff --git a/test/pspm_convert_au2unit_test.m b/test/pspm_convert_au2unit_test.m index b13056d9..2fb4c055 100644 --- a/test/pspm_convert_au2unit_test.m +++ b/test/pspm_convert_au2unit_test.m @@ -1,6 +1,230 @@ 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) + data = 100; + [sts, out] = pspm_convert_au2unit(data, 'mm', 60, 'area', 0.1, 50, 'mm'); + testCase.verifyEqual(sts, 1); + + % 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) + data = 400; + [sts, out] = pspm_convert_au2unit(data, 'mm', 100, 'area', 0.1, 100, 'm'); % reference_unit in m + testCase.verifyEqual(sts, 1); + + + % 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) + data = [25 36 49] + [sts, out] = pspm_convert_au2unit(data, 'mm', 80, 'area', 0.05, 40, 'mm'); + testCase.verifyEqual(sts, 1); + + + % 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) + data = [100 400 900]; + [sts,out]=pspm_convert_au2unit(data,'mm',100,'area',0.1,50,'m'); + 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); +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 + +%% 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) load original file + [sts, infos, data] = pspm_load_data(fn); + testCase.verifyEqual(sts, 1); + + %% 2) load pupil channel + [sts, original_channel, ~, pos] = pspm_load_channel(fn, options.channel, 'pupil'); + testCase.verifyEqual(sts, 1); + + %% 3) change units to au + au_data = unit2au(original_channel.data, unit, distance, record_method, ... + multiplicator, reference_distance, reference_unit); + + 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); + + %% 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); + + %% 5) load converted channel + [sts, reconverted_channel] = pspm_load_channel(fn_roundtrip, outchannel, 'pupil'); + testCase.verifyEqual(sts, 1); + + %% 8) Test + testCase.verifyEqual(reconverted_channel.data, original_channel.data, 'AbsTol', 1e-12); + testCase.verifyEqual(reconverted_channel.header.units, unit); + + %% 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_conv] = pspm_convert_unit(distance, unit, reference_unit); + + switch lower(record_method) + case 'diameter' + data = outchannel ./ ... + (multiplicator * (distance / reference_distance )); + case 'area' + + data = (outchannel ./ ... + (multiplicator * ( distance/ reference_distance))); + data = ((data./2).^2 ).*pi; + + otherwise + error('Invalid record_method'); + end +end + + + + diff --git a/test/pspm_convert_gaze_test.m b/test/pspm_convert_gaze_test.m index c839b127..bb934957 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; diff --git a/test/pspm_write_channel_test.m b/test/pspm_write_channel_test.m index dcafb909..1931e3c7 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