Skip to content
4 changes: 2 additions & 2 deletions src/pspm_cfg/pspm_cfg_pupil_preprocess.m
Original file line number Diff line number Diff line change
Expand Up @@ -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_lowpassCF';
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
Expand Down
2 changes: 1 addition & 1 deletion src/pspm_cfg/pspm_cfg_selector_channel.m
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
32 changes: 13 additions & 19 deletions src/pspm_pupil_pp.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -116,18 +116,25 @@
if nargin == 1
options = struct();
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 no warnings 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
Expand Down Expand Up @@ -352,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
115 changes: 100 additions & 15 deletions src/pspm_pupil_pp_options.m
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
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
% 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
Expand Down Expand Up @@ -90,39 +90,124 @@
% │ 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
%
%
% ┌───────────────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)
% └───────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)
% │
% │ // 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_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
% 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 used.'); % 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 used.'); % 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();

% 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

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
3 changes: 2 additions & 1 deletion test/pspm_pupil_pp_test.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down