From 69f794301c41bb00bf751aa7b9d1f9c372d247fc Mon Sep 17 00:00:00 2001 From: Bernhard <90285552+4gwe@users.noreply.github.com> Date: Sat, 11 Jan 2025 14:20:10 +0100 Subject: [PATCH 1/7] updated helptext in pspm_pupil_pp_options.m changed the default setting in the batcheditor (pspm_cfg_pupil_preprocess.m ) ResdFiltLPCF.val 100 -> 16(raw) --- src/pspm_cfg/pspm_cfg_pupil_preprocess.m | 2 +- src/pspm_pupil_pp_options.m | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_pupil_preprocess.m b/src/pspm_cfg/pspm_cfg_pupil_preprocess.m index 452242f5b..62d767026 100644 --- a/src/pspm_cfg/pspm_cfg_pupil_preprocess.m +++ b/src/pspm_cfg/pspm_cfg_pupil_preprocess.m @@ -135,7 +135,7 @@ ResdFiltLPCF.name = 'Butterworth cutoff frequency (Hz)'; ResdFiltLPCF.tag = 'residualsFilter_interpFs'; ResdFiltLPCF.num = [1 1]; -ResdFiltLPCF.val = {100}; +ResdFiltLPCF.val = {16}; ResdFiltLPCF.help = pspm_cfg_help_format('pspm_pupil_pp_options', 'raw.residualsFilter_lowpassCF'); % Keep filter data diff --git a/src/pspm_pupil_pp_options.m b/src/pspm_pupil_pp_options.m index 4e4713ef8..539ca12f0 100644 --- a/src/pspm_pupil_pp_options.m +++ b/src/pspm_pupil_pp_options.m @@ -5,8 +5,8 @@ % structure used by pspm_pupil_pp for pupil preprocessing. You can modify the % returned structure and then pass it to pspm_pupil_pp. See below for % explanation of the parameters. Adapted from: -% - pspm/pupil-size/code/helperFunctions/rawDataFilter.m lines 63 to 149, -% - pspm/pupil-size/code/dataModels/ValidSamplesModel.m lines 357 to 373. +% - src/ext/pupil-size/code/helperFunctions/rawDataFilter.m lines 63 to 149, +% - src/ext/pupil-size/code/dataModels/ValidSamplesModel.m lines 357 to 373. % ● Format % [sts, default_settings] = pspm_pupil_pp_options() % ● Outputs @@ -101,10 +101,10 @@ % ┌───────────────valid % ├interp_upsamplingFreq:The upsampling frequency used to generate the smooth % │ signal. (Default: 1000 Hz) -% ├───LpFilt_cutoffFreq: Cutoff frequency of the lowpass filter used during -% │ final smoothing. (Default: 4 Hz) -% ├────────LpFilt_order: Filter order of the lowpass filter used during final -% │ smoothing. (Default: 4) +% ├─────────────LpFilt_B:The numerator coefficients of the digital Butterworth +% │ low-pass filter. +% ├─────────────LpFilt_A:The denominator coefficients of the digital +% │ Butterworth low-pass filter. % └───────interp_maxGap: Maximum gap in the used (valid) raw samples to % interpolate over. Sections that were interpolated % over distances larger than this value will be set From 8f2fe06265d5b70b7c96a9e58cc57dcd93d47ddb Mon Sep 17 00:00:00 2001 From: Bernhard <90285552+4gwe@users.noreply.github.com> Date: Sat, 11 Jan 2025 16:00:31 +0100 Subject: [PATCH 2/7] comment about wrong tag --- src/pspm_cfg/pspm_cfg_pupil_preprocess.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_pupil_preprocess.m b/src/pspm_cfg/pspm_cfg_pupil_preprocess.m index 62d767026..6ce7da693 100644 --- a/src/pspm_cfg/pspm_cfg_pupil_preprocess.m +++ b/src/pspm_cfg/pspm_cfg_pupil_preprocess.m @@ -133,9 +133,9 @@ % Residual filter for lowpass cut-off ResdFiltLPCF = cfg_entry; ResdFiltLPCF.name = 'Butterworth cutoff frequency (Hz)'; -ResdFiltLPCF.tag = 'residualsFilter_interpFs'; +ResdFiltLPCF.tag = 'residualsFilter_interpFs';% should be residualsFilter_lowpassCF? ResdFiltLPCF.num = [1 1]; -ResdFiltLPCF.val = {16}; +ResdFiltLPCF.val = {16};% should be 1 ResdFiltLPCF.help = pspm_cfg_help_format('pspm_pupil_pp_options', 'raw.residualsFilter_lowpassCF'); % Keep filter data From bb1c2ee266f0af96256197089bc83095eb57b4e7 Mon Sep 17 00:00:00 2001 From: Bernhard <90285552+4gwe@users.noreply.github.com> Date: Sat, 11 Jan 2025 16:13:44 +0100 Subject: [PATCH 3/7] fix comment --- src/pspm_cfg/pspm_cfg_pupil_preprocess.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pspm_cfg/pspm_cfg_pupil_preprocess.m b/src/pspm_cfg/pspm_cfg_pupil_preprocess.m index 6ce7da693..3e4024ee4 100644 --- a/src/pspm_cfg/pspm_cfg_pupil_preprocess.m +++ b/src/pspm_cfg/pspm_cfg_pupil_preprocess.m @@ -135,7 +135,7 @@ ResdFiltLPCF.name = 'Butterworth cutoff frequency (Hz)'; ResdFiltLPCF.tag = 'residualsFilter_interpFs';% should be residualsFilter_lowpassCF? ResdFiltLPCF.num = [1 1]; -ResdFiltLPCF.val = {16};% should be 1 +ResdFiltLPCF.val = {16};% ResdFiltLPCF.help = pspm_cfg_help_format('pspm_pupil_pp_options', 'raw.residualsFilter_lowpassCF'); % Keep filter data From a8f7ff15934644cd2a31000a87a04eb86341d680 Mon Sep 17 00:00:00 2001 From: Bernhard <90285552+4gwe@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:55:15 +0100 Subject: [PATCH 4/7] temporary check for 2nd channel is empty --- src/pspm_pupil_pp.m | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pspm_pupil_pp.m b/src/pspm_pupil_pp.m index 71603e3c7..34f5188c4 100644 --- a/src/pspm_pupil_pp.m +++ b/src/pspm_pupil_pp.m @@ -116,6 +116,11 @@ if nargin == 1 options = struct(); end +% temporary +if isempty(options.channel_combine) + options.channel_combine = 'none'; +end + options = pspm_options(options, 'pupil_pp'); if options.invalid return @@ -139,7 +144,7 @@ end end %% 4 Load -action_combine = ~strcmp(options.channel_combine, 'none'); +action_combine = ~strcmp(options.channel_combine, 'none');% check here? alldata = struct(); [sts_load, alldata.infos, alldata.data] = pspm_load_data(fn); if sts_load < 1, return, end From 17398ec779c6e54e39f7b278321c71a6f8c3bba8 Mon Sep 17 00:00:00 2001 From: Bernhard <90285552+4gwe@users.noreply.github.com> Date: Tue, 21 Jan 2025 22:52:30 +0100 Subject: [PATCH 5/7] pspm_pupil processes now custom_settings --- src/pspm_cfg/pspm_cfg_pupil_preprocess.m | 2 +- src/pspm_cfg/pspm_cfg_selector_channel.m | 2 +- src/pspm_pupil_pp.m | 35 +++++-------- src/pspm_pupil_pp_options.m | 67 +++++++++++++++++++++++- 4 files changed, 80 insertions(+), 26 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_pupil_preprocess.m b/src/pspm_cfg/pspm_cfg_pupil_preprocess.m index 3e4024ee4..2d7afcc2d 100644 --- a/src/pspm_cfg/pspm_cfg_pupil_preprocess.m +++ b/src/pspm_cfg/pspm_cfg_pupil_preprocess.m @@ -133,7 +133,7 @@ % Residual filter for lowpass cut-off ResdFiltLPCF = cfg_entry; ResdFiltLPCF.name = 'Butterworth cutoff frequency (Hz)'; -ResdFiltLPCF.tag = 'residualsFilter_interpFs';% should be residualsFilter_lowpassCF? +ResdFiltLPCF.tag = 'residualsFilter_lowpassCF'; ResdFiltLPCF.num = [1 1]; ResdFiltLPCF.val = {16};% ResdFiltLPCF.help = pspm_cfg_help_format('pspm_pupil_pp_options', 'raw.residualsFilter_lowpassCF'); diff --git a/src/pspm_cfg/pspm_cfg_selector_channel.m b/src/pspm_cfg/pspm_cfg_selector_channel.m index bf6ba8ac6..e32a0fe80 100644 --- a/src/pspm_cfg/pspm_cfg_selector_channel.m +++ b/src/pspm_cfg/pspm_cfg_selector_channel.m @@ -182,7 +182,7 @@ function out = pupil_chan(menu_set, menu_default) labels = {'Combined pupil channel', 'Both pupil channels', 'Left pupil', 'Right pupil', 'Default', 'None'}; - values = {'pupil_c', 'both', 'pupil_l', 'pupil_r', 'pupil', ''}; + values = {'pupil_c', 'both', 'pupil_l', 'pupil_r', 'pupil', 'none'}; out = cfg_menu; out.name = 'Channel specification'; diff --git a/src/pspm_pupil_pp.m b/src/pspm_pupil_pp.m index 34f5188c4..6712aa005 100644 --- a/src/pspm_pupil_pp.m +++ b/src/pspm_pupil_pp.m @@ -116,23 +116,25 @@ if nargin == 1 options = struct(); end -% temporary -if isempty(options.channel_combine) - options.channel_combine = 'none'; -end + options = pspm_options(options, 'pupil_pp'); if options.invalid return end -[lsts, default_settings] = pspm_pupil_pp_options(); + +% when batch editor has default settings +if ~isfield(options,'custom_settings') + [lsts, default_settings] = pspm_pupil_pp_options(); % so now warings are displayed +else + [lsts, default_settings] = pspm_pupil_pp_options(options.custom_settings); +end + + if lsts ~= 1 return end -if isfield(options, 'custom_settings') - default_settings = pspm_assign_fields_recursively(... - default_settings, options.custom_settings); -end + options.custom_settings = default_settings; %% 3 Input checks @@ -144,7 +146,7 @@ end end %% 4 Load -action_combine = ~strcmp(options.channel_combine, 'none');% check here? +action_combine = ~strcmp(options.channel_combine, 'none'); alldata = struct(); [sts_load, alldata.infos, alldata.data] = pspm_load_data(fn); if sts_load < 1, return, end @@ -357,16 +359,3 @@ end end end -function out_struct = pspm_assign_fields_recursively(out_struct, in_struct) -% Definition -% pspm_assign_fields_recursively assign all fields of in_struct to -% out_struct recursively, overwriting when necessary. -fnames = fieldnames(in_struct); -for i = 1:numel(fnames) - name = fnames{i}; - if isstruct(in_struct.(name)) && isfield(out_struct, name) - out_struct.(name) = pspm_assign_fields_recursively(out_struct.(name), in_struct.(name)); - else - out_struct.(name) = in_struct.(name); - end -end diff --git a/src/pspm_pupil_pp_options.m b/src/pspm_pupil_pp_options.m index 539ca12f0..7f0d94b7c 100644 --- a/src/pspm_pupil_pp_options.m +++ b/src/pspm_pupil_pp_options.m @@ -1,4 +1,4 @@ -function [sts, default_settings] = pspm_pupil_pp_options() +function [sts, default_settings] = pspm_pupil_pp_options(custom_settings) % ● Description % pspm_pupil_pp_options is a helper function that can be used to modify the % behaviour of pspm_pupil_pp function. This function returns the settings @@ -113,16 +113,81 @@ % Introduced In PsPM version ?. % Written in 2019 by Eshref Yozdemir (University of Zurich) % Maintained in 2022 by Teddy +% Maintained in 2024 by Bernhard von Raußendorf global settings if isempty(settings) pspm_init; end + sts = -1; +default_settings = struct(); + +if nargin < 1 + flag = 0; +elseif ~isstruct(custom_settings) + warning('Input must be a struct. Returning default settings.'); + flag = 0; +else + flag = 1; +end + + + +if nargin == 1 && flag == 1 + + reqFieldsRaw = {'residualsFilter_interpFs','residualsFilter_lowpassCF'}; + if isfield(custom_settings,'raw') && all(isfield(custom_settings.raw, reqFieldsRaw)) + % from src/ext/pupil-size/code/helperFunctions/rawDataFilter.m + + [custom_settings.raw.residualsFilter_lowpassB , custom_settings.raw.residualsFilter_lowpassA] ... + = butter(1 , custom_settings.raw.residualsFilter_lowpassCF/(custom_settings.raw.residualsFilter_interpFs/2) ); + else + warning('Missing required fields in custom_settings.raw: Default filter coefficients will be use.'); % change + end + + reqFieldsValid = {'LpFilt_cutoffFreq','interp_upsamplingFreq','LpFilt_order'}; + if isfield(custom_settings,'valid') && all(isfield(custom_settings.valid, reqFieldsValid)) + % settingsOut = getDefaultSettings() from ValidSamplesModel + [custom_settings.valid.LpFilt_B, custom_settings.valid.LpFilt_A] = butter(custom_settings.valid.LpFilt_order, ... + 2*custom_settings.valid.LpFilt_cutoffFreq/custom_settings.valid.interp_upsamplingFreq ); + else + warning('Missing required fields in custom_settings.valid: Default filter coefficients will be use.'); % change + end + +end + + libbase_path = pspm_path('ext','pupil-size', 'code'); libpath = {fullfile(libbase_path, 'dataModels'), fullfile(libbase_path, 'helperFunctions')}; addpath(libpath{:}); default_settings = PupilDataModel.getDefaultSettings(); + +default_settings.valid.LpFilt_cutoffFreq = 4; % could also be added to the default_settings in ValidSamplesModel +default_settings.valid.LpFilt_order = 4; % could also be added to the default_settings in ValidSamplesModel + +if nargin == 1 && flag == 1 + default_settings = pspm_assign_fields_recursively(default_settings, custom_settings); +end + + rmpath(libpath{:}); sts = 1; return +end + +function out_struct = pspm_assign_fields_recursively(out_struct, in_struct) +% Definition +% pspm_assign_fields_recursively assign all fields of in_struct to +% out_struct recursively, overwriting when necessary. +fnames = fieldnames(in_struct); +for i = 1:numel(fnames) + name = fnames{i}; + if isstruct(in_struct.(name)) && isfield(out_struct, name) + out_struct.(name) = pspm_assign_fields_recursively(out_struct.(name), in_struct.(name)); + else + out_struct.(name) = in_struct.(name); + end +end + +end \ No newline at end of file From c26064de4a1a6602ea00515e4822c6b2331e2181 Mon Sep 17 00:00:00 2001 From: Bernhard <90285552+4gwe@users.noreply.github.com> Date: Fri, 24 Jan 2025 19:25:34 +0100 Subject: [PATCH 6/7] added helptext, finished pspm_pupil_pp_options --- src/pspm_pupil_pp.m | 2 +- src/pspm_pupil_pp_options.m | 34 +++++++++++++++++++++++++++------- test/pspm_pupil_pp_test.m | 3 ++- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/pspm_pupil_pp.m b/src/pspm_pupil_pp.m index 6712aa005..c45025e0f 100644 --- a/src/pspm_pupil_pp.m +++ b/src/pspm_pupil_pp.m @@ -85,7 +85,7 @@ % ├─────.end : [decimal][Unit: second] Ending time of the segment. % ├────.name : [string] Name of the segment. Segment will be stored by this name. % ├.plot_data: [Boolean][Default: false or 0] Plot the preprocessing steps. -% ├─.chan_valid_cutoff : [optional][Default: 0.01] +% ├─.chan_valid_cutoff : [optional][Default: 0.2] % │ A cut-off value for checking whether there are too many missing values % │ in a data channel for combination. If the difference in % │ missing data percentage between the two channels exceeds diff --git a/src/pspm_pupil_pp_options.m b/src/pspm_pupil_pp_options.m index 7f0d94b7c..f76d5813c 100644 --- a/src/pspm_pupil_pp_options.m +++ b/src/pspm_pupil_pp_options.m @@ -90,14 +90,27 @@ % │ Cutoff frequency for first order Butterworth filter. % │ (Default: 16 Hz) % │ +% ├─residualsFilter_lowpassB: +% │ Numerator (B) coefficients of the first-order +% │ Butterworth filter used in the residuals filter passes. +% │ Automatically computed from residualsFilter_lowpassCF +% │ and residualsFilter_interpFs. +% ├─residualsFilter_lowpassA: +% │ Denominator (A) coefficients of the first-order +% │ Butterworth filter used in the residuals filter +% │ passes. Automatically computed. +% │ % │ // Keep filter data % │ -% └─keepFilterData: If true, intermediate filter data will be stored. +% └───────keepFilterData:If true, intermediate filter data will be stored. % Set to false to save memory and improve plotting % performance. (Default: true) -% -% // Final data smoothing % +% // Final data smoothing +% // The below computation is performed: +% // [LpFilt_B, LpFilt_A] = butter(LpFilt_order, ... +% // 2*LpFilt_cutoffFreq/interp_upsamplingFreq ); +% % ┌───────────────valid % ├interp_upsamplingFreq:The upsampling frequency used to generate the smooth % │ signal. (Default: 1000 Hz) @@ -105,10 +118,16 @@ % │ low-pass filter. % ├─────────────LpFilt_A:The denominator coefficients of the digital % │ Butterworth low-pass filter. -% └───────interp_maxGap: Maximum gap in the used (valid) raw samples to -% interpolate over. Sections that were interpolated -% over distances larger than this value will be set -% to NaN. (Default: 250 ms) +% ├───────interp_maxGap: Maximum gap in the used (valid) raw samples to +% │ interpolate over. Sections that were interpolated +% │ over distances larger than this value will be set +% │ to NaN. (Default: 250 ms) +% ├────LpFilt_cutoffFreq:The cutoff frequency (in Hz) for the low-pass Butterworth filter +% │ that is applied to the upsampled signal. (Default: 4 Hz) +% └─────────LpFilt_order:The order of the Butterworth filter used on the +% upsampled signal. (Default: 4) +% +% % ● History % Introduced In PsPM version ?. % Written in 2019 by Eshref Yozdemir (University of Zurich) @@ -163,6 +182,7 @@ addpath(libpath{:}); default_settings = PupilDataModel.getDefaultSettings(); +% gets default_settings.valid.LpFilt_cutoffFreq = 4; % could also be added to the default_settings in ValidSamplesModel default_settings.valid.LpFilt_order = 4; % could also be added to the default_settings in ValidSamplesModel diff --git a/test/pspm_pupil_pp_test.m b/test/pspm_pupil_pp_test.m index 271cffb11..bd9592c56 100644 --- a/test/pspm_pupil_pp_test.m +++ b/test/pspm_pupil_pp_test.m @@ -22,8 +22,9 @@ function backup(this) import{end + 1}.type = 'gaze_x_l'; import{end + 1}.type = 'gaze_y_l'; import{end + 1}.type = 'marker'; + options.overwrite = 1; [sts, this.pspm_input_filename] = pspm_import(... - this.raw_input_filename, 'eyelink', import, struct()); + this.raw_input_filename, 'eyelink', import, options); this.pspm_input_filename = this.pspm_input_filename; end end From 046389dc8ea2b36df308dc5b8ccc7cf721a34136 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Sat, 25 Jan 2025 08:21:25 +0100 Subject: [PATCH 7/7] stylistic edits --- src/pspm_pupil_pp.m | 2 +- src/pspm_pupil_pp_options.m | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/pspm_pupil_pp.m b/src/pspm_pupil_pp.m index c45025e0f..294b05539 100644 --- a/src/pspm_pupil_pp.m +++ b/src/pspm_pupil_pp.m @@ -125,7 +125,7 @@ % when batch editor has default settings if ~isfield(options,'custom_settings') - [lsts, default_settings] = pspm_pupil_pp_options(); % so now warings are displayed + [lsts, default_settings] = pspm_pupil_pp_options(); % so no warnings are displayed else [lsts, default_settings] = pspm_pupil_pp_options(options.custom_settings); end diff --git a/src/pspm_pupil_pp_options.m b/src/pspm_pupil_pp_options.m index f76d5813c..ee4670be7 100644 --- a/src/pspm_pupil_pp_options.m +++ b/src/pspm_pupil_pp_options.m @@ -105,27 +105,27 @@ % └───────keepFilterData:If true, intermediate filter data will be stored. % Set to false to save memory and improve plotting % performance. (Default: true) -% -% // Final data smoothing -% // The below computation is performed: -% // [LpFilt_B, LpFilt_A] = butter(LpFilt_order, ... -% // 2*LpFilt_cutoffFreq/interp_upsamplingFreq ); % % ┌───────────────valid % ├interp_upsamplingFreq:The upsampling frequency used to generate the smooth % │ signal. (Default: 1000 Hz) -% ├─────────────LpFilt_B:The numerator coefficients of the digital Butterworth -% │ low-pass filter. -% ├─────────────LpFilt_A:The denominator coefficients of the digital -% │ Butterworth low-pass filter. % ├───────interp_maxGap: Maximum gap in the used (valid) raw samples to % │ interpolate over. Sections that were interpolated % │ over distances larger than this value will be set % │ to NaN. (Default: 250 ms) +% │ +% │ // For final data smoothing, the below computation is performed: +% │ // [LpFilt_B, LpFilt_A] = butter(LpFilt_order, ... +% │ // 2*LpFilt_cutoffFreq/interp_upsamplingFreq ); +% │ % ├────LpFilt_cutoffFreq:The cutoff frequency (in Hz) for the low-pass Butterworth filter % │ that is applied to the upsampled signal. (Default: 4 Hz) -% └─────────LpFilt_order:The order of the Butterworth filter used on the -% upsampled signal. (Default: 4) +% ├─────────LpFilt_order:The order of the Butterworth filter used on the +% │ upsampled signal. (Default: 4) +% ├─────────────LpFilt_B:The numerator coefficients of the digital Butterworth +% │ low-pass filter. Automatically computed. +% └─────────────LpFilt_A:The denominator coefficients of the digital +% Butterworth low-pass filter. Automatically computed. % % % ● History @@ -162,7 +162,7 @@ [custom_settings.raw.residualsFilter_lowpassB , custom_settings.raw.residualsFilter_lowpassA] ... = butter(1 , custom_settings.raw.residualsFilter_lowpassCF/(custom_settings.raw.residualsFilter_interpFs/2) ); else - warning('Missing required fields in custom_settings.raw: Default filter coefficients will be use.'); % change + warning('Missing required fields in custom_settings.raw: Default filter coefficients will be used.'); % change end reqFieldsValid = {'LpFilt_cutoffFreq','interp_upsamplingFreq','LpFilt_order'}; @@ -171,7 +171,7 @@ [custom_settings.valid.LpFilt_B, custom_settings.valid.LpFilt_A] = butter(custom_settings.valid.LpFilt_order, ... 2*custom_settings.valid.LpFilt_cutoffFreq/custom_settings.valid.interp_upsamplingFreq ); else - warning('Missing required fields in custom_settings.valid: Default filter coefficients will be use.'); % change + warning('Missing required fields in custom_settings.valid: Default filter coefficients will be used.'); % change end end