From 4cb12ceb900a537c398a1af81d2180b9b2fae80b Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Fri, 15 Nov 2024 09:19:12 +0100 Subject: [PATCH 01/29] correct PS-FC-GLM defaults in GUI, and make clearer in pspm_init --- src/pspm_cfg/pspm_cfg_glm_ps_fc.m | 8 ++++---- src/pspm_init.m | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_glm_ps_fc.m b/src/pspm_cfg/pspm_cfg_glm_ps_fc.m index 3fa4f248..f76a1149 100644 --- a/src/pspm_cfg/pspm_cfg_glm_ps_fc.m +++ b/src/pspm_cfg/pspm_cfg_glm_ps_fc.m @@ -33,8 +33,8 @@ psrf_fc{i}.tag = ['psrf_fc' num2str(i-1)]; psrf_fc{i}.val = {i-1}; end -psrf_fc{1}.help = {'PSRF_FC: CS-evoked response only and without derivatives.'}; -psrf_fc{2}.help = {'PSRF_FC: CS-evoked response and time derivative (default).'}; +psrf_fc{1}.help = {'PSRF_FC: CS-evoked response only and without derivatives (default).'}; +psrf_fc{2}.help = {'PSRF_FC: CS-evoked response and time derivative.'}; psrf_fc{3}.help = {['PSRF_FC: CS- and US-evoked response (3.5 s SOA) without derivatives. ', ... 'For other SOAs, specify the basis function outside the GUI.']}; psrf_fc{4}.help = {['PSRF_FC: US-evoked response only (3.5 s SOA) and without derivatives. ', ... @@ -53,10 +53,10 @@ bf = cfg_choice; bf.name = 'Basis Function'; bf.tag = 'bf'; -bf.val = {psrf_fc{2}}; +bf.val = {psrf_fc{1}}; bf.values = {psrf_fc{:}}; bf.help = {['Basis functions. Standard is to use a canonical pupil size response function ' ... - 'for fear conditioning (PSRF_FC) with time derivative for later reconstruction of the response peak. ', ... + 'for fear conditioning (PSRF_FC) without time derivative. ', ... 'For help on the options, click on the basis function in the window above. NOTE: These basis functions were developed with pupil size data ', ... 'recorded in diameter values. Therefore pupil size data analyzed ', ... 'using these models should also be in diameter.']}; diff --git a/src/pspm_init.m b/src/pspm_init.m index b450a262..469a0505 100644 --- a/src/pspm_init.m +++ b/src/pspm_init.m @@ -680,7 +680,7 @@ defaults.glm(end+1) = struct(... 'modality', 'pupil',... 'modelspec', 'ps_fc',... - 'cbf', struct('fhandle', @pspm_bf_psrf_fc, 'args', 1),... + 'cbf', struct('fhandle', @pspm_bf_psrf_fc, 'args', []),... 'filter', struct('lpfreq', 50, 'lporder', 1, 'hpfreq', NaN, 'hporder', NaN, 'down', 100, 'direction', 'bi'),... 'default', 0); % GLM for RA (evoked) From a3823981a46d08e731a639899c41ccc447b41e46 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Fri, 15 Nov 2024 09:34:11 +0100 Subject: [PATCH 02/29] improve help text for SPS basis functions --- src/pspm_bf_spsrf_box.m | 11 +++++------ src/pspm_cfg/pspm_cfg_glm_sps.m | 12 ++++-------- src/pspm_cfg/pspm_cfg_help_format.m | 2 ++ 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/pspm_bf_spsrf_box.m b/src/pspm_bf_spsrf_box.m index de71efa5..b1964061 100644 --- a/src/pspm_bf_spsrf_box.m +++ b/src/pspm_bf_spsrf_box.m @@ -1,18 +1,17 @@ function [bs, x] = pspm_bf_spsrf_box(varargin) % ● Description % pspm_bf_spsrf_box constructs a boxcar function to produce the averaged -% scanpath speed over a 2-s time window in the end of SOA, which equals -% to scanpath length over a 2-s time window divided by 2/s +% scanpath speed over a 2-s time window in the end of SOA. This equals +% to scanpath length over a 2-s time window divided by 2. % ● Format % [bs, x] = pspm_bf_spsrf_box(td, soa) % [bs, x] = pspm_bf_spsrf_box([td, soa]) % ● Arguments % * td : time resolution in second. % ● References -% Xia Y, Melinscak F, Bach DR (2020) -% Saccadic Scanpath Length: An Index for Human Threat Conditioning -% Behavioral Research Methods 53, pages 1426–1439 (2021) -% doi: 10.3758/s13428-020-01490-5 +% Xia Y, Melinščak F, Bach DR (2020). Saccadic scanpath length: an +% index for human threat conditioning. Behavior Research Methods, 53, +% 1426-1439. % ● History % Introduced in PsPM 4.0 diff --git a/src/pspm_cfg/pspm_cfg_glm_sps.m b/src/pspm_cfg/pspm_cfg_glm_sps.m index c4f177aa..9a23611f 100644 --- a/src/pspm_cfg/pspm_cfg_glm_sps.m +++ b/src/pspm_cfg/pspm_cfg_glm_sps.m @@ -42,20 +42,16 @@ % SPS % bf = boxfunction spsrf_box = cfg_const; -spsrf_box.name = 'Average scanpath speed'; +spsrf_box.name = 'Average scanpath speed (2 s pre-US)'; spsrf_box.tag = 'spsrf_box'; spsrf_box.val = {'spsrf_box'}; -spsrf_box.help = {['This option implements a boxcar function over the SOA, and yields the average ',... - 'scan path speed over that interval (i.e.', ... - 'the scan path length over that interval, divided by the SOA).', ... - '(default).']}; +spsrf_box.help = pspm_cfg_help_format('pspm_bf_spsrf_box'); % bf = gammafunction spsrf_gamma = cfg_const; -spsrf_gamma.name = 'SPSRF_FC'; +spsrf_gamma.name = 'Gamma function'; spsrf_gamma.tag = 'spsrf_gamma'; spsrf_gamma.val = {'spsrf_gamma'}; -spsrf_gamma.help = {['This option implements a gamma function for fear-conditioned scan path speed', ... - 'responses, time-locked to the end of the CS-US interval.']}; +spsrf_gamma.help = pspm_cfg_help_format('pspm_bf_spsrf_gamma'); % rf function rf = cfg_choice; rf.name = 'Function'; diff --git a/src/pspm_cfg/pspm_cfg_help_format.m b/src/pspm_cfg/pspm_cfg_help_format.m index 78509b05..acb0b2a8 100644 --- a/src/pspm_cfg/pspm_cfg_help_format.m +++ b/src/pspm_cfg/pspm_cfg_help_format.m @@ -34,6 +34,8 @@ 'References:'; ... settings.help.(funcname).References(:)]; end + helptext = [ helptext(:); ''; ... + '------------------------------------------------------------------------------------------------']; end elseif strcmpi(funcname, 'import') A = strrep(argname, newline, [newline, newline]); From 2d79f8e213179fdba795d5c71561f202be645746 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Fri, 15 Nov 2024 10:21:50 +0100 Subject: [PATCH 03/29] improve help text and GUI for find_valid_fixations and pupil_pp --- src/pspm_cfg/pspm_cfg_find_valid_fixations.m | 14 +++++++------- src/pspm_cfg/pspm_cfg_run_find_valid_fixations.m | 4 ++-- src/pspm_find_valid_fixations.m | 6 ++---- src/pspm_pupil_pp.m | 9 ++++----- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_find_valid_fixations.m b/src/pspm_cfg/pspm_cfg_find_valid_fixations.m index 7651a257..43bc0255 100644 --- a/src/pspm_cfg/pspm_cfg_find_valid_fixations.m +++ b/src/pspm_cfg/pspm_cfg_find_valid_fixations.m @@ -12,12 +12,12 @@ ChanAct = pspm_cfg_selector_channel_action; %% Visual angle -box_degree = cfg_entry; -box_degree.name = 'Visual angle'; -box_degree.tag = 'box_degree'; -box_degree.strtype = 'i'; -box_degree.num = [1 1]; -box_degree.help = pspm_cfg_help_format('pspm_find_valid_fixations', 'circle_degree'); +circle_degree = cfg_entry; +circle_degree.name = 'Visual angle'; +circle_degree.tag = 'circle_degree'; +circle_degree.strtype = 'i'; +circle_degree.num = [1 1]; +circle_degree.help = pspm_cfg_help_format('pspm_find_valid_fixations', 'circle_degree'); %% Distance distance = cfg_entry; @@ -79,7 +79,7 @@ ValidSet = cfg_branch; ValidSet.name = 'Fixation point'; ValidSet.tag = 'validation_settings'; -ValidSet.val = {box_degree, distance, unit, Resol, FixPt}; +ValidSet.val = {circle_degree, distance, unit, Resol, FixPt}; ValidSet.help = {}; %% Validate on bitmap diff --git a/src/pspm_cfg/pspm_cfg_run_find_valid_fixations.m b/src/pspm_cfg/pspm_cfg_run_find_valid_fixations.m index 5b63cb7f..f51136a3 100644 --- a/src/pspm_cfg/pspm_cfg_run_find_valid_fixations.m +++ b/src/pspm_cfg/pspm_cfg_run_find_valid_fixations.m @@ -22,7 +22,7 @@ bitmap = indata.bitmap; else % ValidSet - box_degree = job.val_method.validation_settings.box_degree; + circle_degree = job.val_method.validation_settings.circle_degree; distance = job.val_method.validation_settings.distance; unit = job.val_method.validation_settings.unit; options = pspm_update_struct(options, job.val_method.validation_settings, 'resolution'); @@ -42,6 +42,6 @@ if isfield(job.val_method,'bitmap_file') [~, out] = pspm_find_valid_fixations(fn, bitmap, options); else - [~, out] = pspm_find_valid_fixations(fn, box_degree, ... + [~, out] = pspm_find_valid_fixations(fn, circle_degree, ... distance, unit, options); end diff --git a/src/pspm_find_valid_fixations.m b/src/pspm_find_valid_fixations.m index 27317fa2..8f27822c 100644 --- a/src/pspm_find_valid_fixations.m +++ b/src/pspm_find_valid_fixations.m @@ -31,10 +31,8 @@ % ● Arguments % * fn : The actual data file containing the eyelink recording with gaze % data converted to cm. -% * bitmap : A nxm matrix representing the display window and holding for each -% poisition a one, where a gaze value is valid. If there exists -% gaze data at a point with a zero value in the bitmap the -% corresponding data is set to NaN. IMPORTANT: the bitmap has to +% * bitmap : A nxm matrix of the same size as the display, with 1 +% for valid and 0 for invalid gaze points. IMPORTANT: the bitmap has to % be defined in terms of the eyetracker coordinate system, i.e. % bitmap(1,1) must correpond to the origin of the eyetracker % coordinate system, and must be of the same size as diff --git a/src/pspm_pupil_pp.m b/src/pspm_pupil_pp.m index 8d84614b..71603e3c 100644 --- a/src/pspm_pupil_pp.m +++ b/src/pspm_pupil_pp.m @@ -5,11 +5,10 @@ % It performs the steps described in [1]. This function uses % a modified version of [2]. The modified version with a list of changes % from the original is shipped with PsPM under pupil-size directory. -% The steps performed are listed below: -% 1. Pupil preprocessing is performed in two main steps. In the first -% step, the “valid” samples are determined. The samples that are not -% valid are not used in the second step. Determining valid samples is -% done by the following procedures. +% Pupil preprocessing is performed in three main steps: +% 1. In the first step, the "valid" samples are determined. Samples that +% are not valid are not used in the second step. Determining valid +% samples is done by the following procedures: % (a) Range filtering: Pupil size values outside a predefined range % are considered invalid. This range is configurable. % (b) Speed filtering: Speed is computed as the 1st difference of From a44f751f7bfe3481e262ccdc2e3db50fbb861ec0 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Fri, 22 Nov 2024 14:57:45 +0100 Subject: [PATCH 04/29] align hprf_fc defaults with GUI and publication --- src/pspm_bf_hprf_fc.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pspm_bf_hprf_fc.m b/src/pspm_bf_hprf_fc.m index 1c6a05ed..b730dba3 100644 --- a/src/pspm_bf_hprf_fc.m +++ b/src/pspm_bf_hprf_fc.m @@ -6,7 +6,7 @@ % [bs, x] = pspm_bf_hprf_fc([TD, D, soa]) % ● Arguments % * td : time resolution in second. -% * d : number of derivatives. Default as 0. +% * d : number of derivatives. Default is 1. % ● History % Introduced in PsPM 3.0 % Written in 2015 by Tobias Moser (University of Zurich) @@ -21,7 +21,7 @@ end td = varargin{1}(1); if numel(varargin{1}) == 1 && nargin == 1 - d = 0; + d = 1; soa = 3.5; else if numel(varargin) > 1 @@ -32,7 +32,7 @@ if numel(va) > 1 d = va(2); else - d = 0; + d = 1; end if numel(va) > 2 soa = va(3); From 592bc4c55d64a5bb008c68d6d5a721a59e7c55fb Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Fri, 22 Nov 2024 15:01:33 +0100 Subject: [PATCH 05/29] align rarf_fc help with GUI and publication --- src/pspm_bf_rarf_fc.m | 4 ++-- src/pspm_cfg/pspm_cfg_glm_ra_fc.m | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pspm_bf_rarf_fc.m b/src/pspm_bf_rarf_fc.m index 6b1879af..7d1193d7 100644 --- a/src/pspm_bf_rarf_fc.m +++ b/src/pspm_bf_rarf_fc.m @@ -7,8 +7,8 @@ % ● Arguments % * td: The time the response function should have. % * bf_type: Which type of response function should be generated. Can be either 1 or 2. -% If 1, first type response function is generated, (default) = gamma_early + gamma_late. -% If 2, second type response function is generated, (default) = gamma_early + gamma_early'. +% If 1, first type response function is generated (default) : gamma_early + gamma_late. +% If 2, second type response function is generated (recommended for longer SOAs): gamma_early + gamma_early'. % ● History % Introduced in PsPM 3.1 % Written in 2016 by G Castegnetti, Tobias Moser (University of Zurich) diff --git a/src/pspm_cfg/pspm_cfg_glm_ra_fc.m b/src/pspm_cfg/pspm_cfg_glm_ra_fc.m index c467a56a..ced35e1f 100644 --- a/src/pspm_cfg/pspm_cfg_glm_ra_fc.m +++ b/src/pspm_cfg/pspm_cfg_glm_ra_fc.m @@ -34,7 +34,7 @@ rarf_fc{i}.val = {i}; end rarf_fc{1}.help = {'RARF_FC early and late response (default).'}; -rarf_fc{2}.help = {'RARF_FC early response with derivative.'}; +rarf_fc{2}.help = {'RARF_FC early response with derivative (recommended for longer SOAs).'}; bf = cfg_choice; From e426d4cde836d8d3e7310e9e5a071db9c746e53c Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Sat, 23 Nov 2024 10:42:51 +0100 Subject: [PATCH 06/29] make import GUI and help textx consistent, and change start/quit message --- src/pspm_cfg/pspm_cfg_import.m | 51 +++++++++--------------------- src/pspm_cfg/pspm_cfg_run_import.m | 3 +- src/pspm_get_events.m | 8 +++-- src/pspm_get_marker.m | 9 ++++-- src/pspm_get_scr.m | 5 +-- src/pspm_import.m | 4 +-- src/pspm_msg.txt | 18 +++++------ src/pspm_quit.m | 2 +- src/pspm_transfer_function.m | 25 +++++++++------ 9 files changed, 58 insertions(+), 67 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_import.m b/src/pspm_cfg/pspm_cfg_import.m index fd80a67f..d1dd6ab8 100644 --- a/src/pspm_cfg/pspm_cfg_import.m +++ b/src/pspm_cfg/pspm_cfg_import.m @@ -29,8 +29,7 @@ sample_rate.strtype = 'r'; sample_rate.num = [1 1]; sample_rate.help = pspm_cfg_help_format('pspm_import', 'import.sr'); -% arguments(contains(arguments(:,1),'import.sr'),2); -% will restore when it is finished. + % Transfer function scr_file = cfg_files; @@ -38,42 +37,28 @@ scr_file.tag = 'file'; scr_file.num = [1 1]; scr_file.filter = '.*\.(mat|MAT)$'; -scr_file.help = {['Enter the name of the .mat file that contains the ', ... - 'transfer function constants. This file needs to contain the ', ... - 'following variables: ''c'' is the transfer constant: ', ... - 'data = c * (measured total conductance in mcS or total resistance ', ... - 'in MOhm); ''Rs'' is the series resistance in Ohm (usually 0), ', ... - 'and ''offset'' any offset in the data (stated in data units, ', ... - 'usually 0) and optionally, a variable ''recsys'' to whether the ', ... - 'recorded signal is proportional to measured ''resistance'' ', ... - '(R, data=R*c=c/G) or from ''conductance'' (G, data=G*c=c/R).']}; +scr_file.help = {}; scr_transf_const = cfg_entry; scr_transf_const.name = 'Transfer Constant'; scr_transf_const.tag = 'transfer_const'; scr_transf_const.strtype = 'r'; scr_transf_const.num = [1 1]; -scr_transf_const.help = {['Constant by which the measured conductance or ', ... - 'resistance is multiplied to give the recorded signal ', ... - '(and by which the signal needs to be divided to give the original ', ... - 'conductance/resistance): data = c * (measured total conductance ', ... - 'in mcS or total resistance in MOhm).']}; +scr_transf_const.help = pspm_cfg_help_format('pspm_transfer_function', 'c'); scr_offset = cfg_entry; scr_offset.name = 'Offset'; scr_offset.tag = 'offset'; scr_offset.strtype = 'r'; scr_offset.num = [1 1]; -scr_offset.help = {'Fixed offset in data units (i. e. measured signal when ', ... - 'true conductance is zero, i.e. when the measurement circuit is open).'}; +scr_offset.help = pspm_cfg_help_format('pspm_transfer_function', 'offset'); scr_resistor = cfg_entry; scr_resistor.name = 'Series Resistor'; scr_resistor.tag = 'resistor'; scr_resistor.strtype = 'r'; scr_resistor.num = [1 1]; -scr_resistor.help = {'Resistance of any resistors in series with the ', ... - 'subject, given in Ohm.'}; +scr_resistor.help = pspm_cfg_help_format('pspm_transfer_function', 'Rs'); scr_recsys = cfg_menu; scr_recsys.name = 'Recording System'; @@ -81,9 +66,7 @@ scr_recsys.values = {'conductance', 'resistance'}; scr_recsys.labels = {'conductance', 'resistance'}; scr_recsys.val = {'conductance'}; -scr_recsys.help = {['Choose whether the recorded signal is proportional ', ... - 'to measured ''resistance'' (R, data=R*c=c/G) or from ''conductance'' ', ... - '(G, data=G*c=c/R).']}; +scr_recsys.help = pspm_cfg_help_format('pspm_transfer_function', 'recsys'); scr_input = cfg_branch; scr_input.name = 'Input'; @@ -95,15 +78,13 @@ none.name = 'None'; none.tag = 'none'; none.val = {true}; -none.help = {['No transfer function. Use this only if you are not interested in ' ... - 'absolute values, and if the recording settings were the same for all subjects.']}; +none.help = {}; scr_transfer = cfg_choice; scr_transfer.name = 'Transfer Function'; scr_transfer.tag = 'scr_transfer'; scr_transfer.values = {scr_file,scr_input,none}; -scr_transfer.help = {['Enter the conversion from recorded data to ', ... - 'Microsiemens or Megaohm.']}; +scr_transfer.help = pspm_cfg_help_format('pspm_transfer_function'); eyelink_trackdist = cfg_entry; eyelink_trackdist.name = 'Eyetracker distance'; @@ -112,9 +93,8 @@ eyelink_trackdist.num = [1 1]; eyelink_trackdist.strtype = 'r'; eyelink_trackdist.help = {['Distance between eyetracker camera and ', ... - 'recorded eyes. Disabled if 0 or less (use only if you are interested ', ... - 'in relative values), then pupil data will remain unchanged. If ', ... - 'enabled (> 0) the data will be converted from arbitrary units to ', ... + 'recorded eyes. Not used if 0 or less. If ', ... + 'spedified (> 0) the data will be converted from arbitrary units to ', ... 'length units.']}; distance_unit = cfg_menu; @@ -227,13 +207,12 @@ %% Flank option for 'event' channel types flank_option = cfg_menu; - flank_option.name = 'Flank of the event impulses to import'; + flank_option.name = 'Marker flank (for continuous marker data only)'; flank_option.tag = 'flank_option'; - flank_option.values = {'ascending', 'descending', 'all', 'both', 'default'}; - flank_option.labels = {'ascending', 'descending', 'both', 'middle', 'default'}; - flank_option.val = {'default'}; - flank_option.help = {''};%arguments(contains(arguments(:,1),'import.flank'),2); - % will restore when this is finished + flank_option.values = {'both', 'ascending', 'descending', 'all'}; + flank_option.labels = {'both', 'ascending', 'descending', 'all'}; + flank_option.val = {'both'}; + flank_option.help = pspm_cfg_help_format('pspm_get_events', 'import.flank'); %% Channel/Column Type Items importtype_item = cell(1,length(channeltypes)); diff --git a/src/pspm_cfg/pspm_cfg_run_import.m b/src/pspm_cfg/pspm_cfg_run_import.m index cf186a3c..c8933bf3 100644 --- a/src/pspm_cfg/pspm_cfg_run_import.m +++ b/src/pspm_cfg/pspm_cfg_run_import.m @@ -32,8 +32,7 @@ import{i}.sr = job.datatype.(datatype).importtype{i}.(type{1}).sample_rate; end % Check if flank option is available - if isfield(job.datatype.(datatype).importtype{i}.(type{1}), 'flank_option') && ... - ~strcmp(job.datatype.(datatype).importtype{i}.(type{1}).flank_option, 'default') + if isfield(job.datatype.(datatype).importtype{i}.(type{1}), 'flank_option') import{i}.flank = job.datatype.(datatype).importtype{i}.(type{1}).flank_option; end % Check if transfer function available diff --git a/src/pspm_get_events.m b/src/pspm_get_events.m index c5f39171..0fabf402 100644 --- a/src/pspm_get_events.m +++ b/src/pspm_get_events.m @@ -9,8 +9,12 @@ % ├───.marker : mandatory, accepts 'timestamps' and 'continuous'. % ├───────.sr : timestamps: timeunits in seconds, continuous: sample rate % │ in 1/seconds) -% ├────.flank : optional for continuous channels; default: both; accepts -% │ 'ascending', 'descending', 'both', 'all'. +% ├────.flank : [optional, string] Only used for importing continuous +% │ event channels. This specifies which flank of the event +% │ marker to use. 'both' (default, specifies the event at +% │ the middle between the ascending and descending flank), +% │ 'ascending', 'descending', 'all' (imports ascending and +% │ descending flank as separate events). % └──.denoise : for continuous marker channels: only retains markers of % duration longer than the value given here (in seconds). % ● Output diff --git a/src/pspm_get_marker.m b/src/pspm_get_marker.m index df45ecd3..72e4b165 100644 --- a/src/pspm_get_marker.m +++ b/src/pspm_get_marker.m @@ -11,9 +11,12 @@ % ├────────.sr: mandatory, double % │ timestamps: timeunits in seconds % │ continuous: sample rate in 1/seconds) -% ├─────.flank: optional, string, applicable for continuous channels only -% │ accepted values: 'ascending', 'descending', 'both' -% │ default: 'both' +% ├────.flank : [optional, string] Only used for importing continuous +% │ event channels. This specifies which flank of the event +% │ marker to use. 'both' (default, specifies the event at +% │ the middle between the ascending and descending flank), +% │ 'ascending', 'descending', 'all' (imports ascending and +% │ descending flank as separate events). % └.markerinfo: optional, struct, returns marker timestamps in seconds. % It has two fields, name and value. % ● History diff --git a/src/pspm_get_scr.m b/src/pspm_get_scr.m index 4997ffa1..29e5bb22 100644 --- a/src/pspm_get_scr.m +++ b/src/pspm_get_scr.m @@ -1,6 +1,6 @@ function [sts, data] = pspm_get_scr(import) % ● Description -% pspm_get_scr is a common function for importing scr data +% pspm_get_scr is a common function for importing scr data. % ● Format % [sts, data] = pspm_get_scr(import) % ● Arguments @@ -9,7 +9,8 @@ % ├──────.sr : sampling rate % └.transfer : transfer parameters, either a struct with fields .Rs, .c, % .offset, .recsys, or a file containing variables 'Rs' 'c', -% 'offset', 'recsys'. +% 'offset', 'recsys'. See pspm_transfer_function for more +% details. % ● History % Introduced in PsPM 3.0 % Written in 2008-2015 by Dominik R Bach (Wellcome Trust Centre for Neuroimaging) diff --git a/src/pspm_import.m b/src/pspm_import.m index 0caf39a8..de119359 100644 --- a/src/pspm_import.m +++ b/src/pspm_import.m @@ -1,7 +1,7 @@ function [sts, outfile] = pspm_import(datafile, datatype, import, options) % ● Description -% pspm_import imports data from different formats and writes them to -% a file on the same path, with the original file name prepended with +% pspm_import imports data from various formats and writes them to +% a PsPM file on the same path, with the original file name prepended with % 'pspm_'. Please refer to the PsPM manual or the help of the individual % 'pspm_get_[datatype] functions for data-type specific information. % ● Format diff --git a/src/pspm_msg.txt b/src/pspm_msg.txt index 485cdf6b..aacc20a4 100644 --- a/src/pspm_msg.txt +++ b/src/pspm_msg.txt @@ -1,16 +1,14 @@ $___________________________________________________________________________ -Welcome to PsPM -- PsychoPhysiological Modelling -(incorporating SCRalyze) -Version 6.1.2 (25.01.2024) +Welcome to PsPM (Psychophysiological Modelling) +Version 7.0 (xx.xx.2025) $ ------------------------------------------ -(c) 2008--2024 -Dominik R Bach -* Wellcome Centre for Human Neuroimaging -University College London -University of Bonn -University of Zurich -d.bach@ucl.ac.uk +(c) 2008--2025 +PsPM team +University of Bonn, DE +University College London, UK +University of Zurich, CH +pspm@bachlab.org bachlab.github.io/PsPM ------------------------------------------ $ diff --git a/src/pspm_quit.m b/src/pspm_quit.m index 744d64b9..823afea4 100644 --- a/src/pspm_quit.m +++ b/src/pspm_quit.m @@ -34,6 +34,6 @@ disp(' '); disp('Thanks for using PsPM.'); disp(repelem('-',20)); -disp('PsPM 6.1.1 (c) 2008-2024 The PsPM development team'); +disp('PsPM 7.0 (c) 2008-2025 The PsPM development team'); disp('Developed at: Uni Bonn, DE | UCL, UK | UZH, CH'); return diff --git a/src/pspm_transfer_function.m b/src/pspm_transfer_function.m index be2b2084..71351dac 100644 --- a/src/pspm_transfer_function.m +++ b/src/pspm_transfer_function.m @@ -1,29 +1,36 @@ function [sts, scr] = pspm_transfer_function(data, c, Rs, offset, recsys) % ● Description % pspm_transfer_function converts input data into SCR in microsiemens -% assuming a linear transfer from total conductance to measured data +% using the following transfer function: G = 1/((c/data-offset)^recsys - Rs), +% where G is conductance, data is the recorded data, c/offset/Rs are +% specified by the user, and recsys is 1 for conductunce measurements +% and -1 for resistance measurements. If the measurement system already +% outputs the conductance G, then the following settings should be used: +% c = 1, offset = 0, Rs = 0. If the transfer function is not specified, +% then the imported data will be assigned "arbitrary units". % ● Format % scr = pspm_transfer_function(data, c, Rs, offset, recsys) % scr = pspm_transfer_function(data, c, [Rs, offset, recsys]) % ● Arguments % * data : the input data into SCR in microsiemens -% * c : the transfer constant. Depending on the recording setting -% data = c * (measured total conductance in mcS) -% or +% * c : Transfer constant c. Depending on the recording system: +% data = c * (measured total conductance in mcS) +% - or - % data = c * (measured total resistance in MOhm) = c / (total conductance in mcS) % * Rs : [optional] % Series resistors (Rs) are often used as current limiters in MRI and will -% render the function non-linear. They can be taken into account (in Ohm) +% render the function non-linear. This should be taken into +% account. Specify Rs in Ohm. Note that some MRI recording +% systems use resistive wires. % default: Rs=0. % * offset : [optional, default as 0] % Some systems have an offset (e.g. when using fiber optics in MRI, a minimum % pulsrate), which can also be taken into account. Offset must be stated in % data units. % * recsys : [optional] -% There are two different recording settings which have an influence on the -% transfer function. Recsys defines in which setting the data (given in -% voltage) has been generated. Either the system is a 'conductance' based -% system (which is the default) or it is a 'resistance' based system. +% Most SCR measurement systems record a conductance (or a linear +% transformation). Some systems record resistance. Specify +% 'conductance' (default) or 'resistance'. % ● History % Introduced in PsPM 3.0 % Written in 2008-2015 by Dominik R Bach (Wellcome Trust Centre for Neuroimaging) From f4f34906716bc5cdb2ca4647329aa8991dbd3e38 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Mon, 25 Nov 2024 08:45:37 +0100 Subject: [PATCH 07/29] fix dependencies in GUI --- src/pspm_cfg/pspm_cfg_run_import.m | 3 ++- src/pspm_cfg/pspm_cfg_run_trim.m | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_run_import.m b/src/pspm_cfg/pspm_cfg_run_import.m index c8933bf3..2dcf796f 100644 --- a/src/pspm_cfg/pspm_cfg_run_import.m +++ b/src/pspm_cfg/pspm_cfg_run_import.m @@ -1,4 +1,4 @@ -function [out,datafile, datatype, import, options] = pspm_cfg_run_import(job) +function out = pspm_cfg_run_import(job) % Updated on 08-01-2024 by Teddy global settings if isempty(settings), pspm_init; end @@ -90,3 +90,4 @@ options = struct(); options = pspm_update_struct(options, job, 'overwrite'); [sts, out] = pspm_import(datafile, datatype, import, options); +out = {out}; % convert to cell for dependency handling of file names \ No newline at end of file diff --git a/src/pspm_cfg/pspm_cfg_run_trim.m b/src/pspm_cfg/pspm_cfg_run_trim.m index f5064db3..1c885adb 100644 --- a/src/pspm_cfg/pspm_cfg_run_trim.m +++ b/src/pspm_cfg/pspm_cfg_run_trim.m @@ -20,5 +20,4 @@ error('Reference invalid'); end [sts, out] = pspm_trim(job.datafile{1}, from, to, ref, options); -out = {out}; - +out = {out}; % convert to cell for dependency handling of file names From 54e395e371f87b77d55a25a2b3f92b0bcd4f1976 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Fri, 29 Nov 2024 09:56:03 +0100 Subject: [PATCH 08/29] correct text in pspm_overwrite --- src/pspm_overwrite.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pspm_overwrite.m b/src/pspm_overwrite.m index 3f0e906a..54549bc9 100644 --- a/src/pspm_overwrite.m +++ b/src/pspm_overwrite.m @@ -80,7 +80,7 @@ if settings.developmode error('Overwrite not correctly defined for testing.') else - msg = ['Model file already exists. Overwrite?', ... + msg = ['This file already exists. Overwrite?', ... newline, 'Existing file: ', fn]; overwrite = questdlg(msg, ... 'File already exists', 'Yes', 'No', 'Yes'); From 9c6365e06c180efa849adf103a712c9a80c98d69 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Sat, 30 Nov 2024 07:58:39 +0100 Subject: [PATCH 09/29] fix ylabel in pspm_display --- src/pspm_display.m | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/pspm_display.m b/src/pspm_display.m index 1be00d39..7ef5221f 100644 --- a/src/pspm_display.m +++ b/src/pspm_display.m @@ -863,24 +863,17 @@ function pp_plot(handles) xlabel('Time [s]','Fontsize',settings.ui.FontSizeText); wv_chanid = handles.prop.wavechans(handles.prop.idwave); unit = deblank(handles.data{wv_chanid}.header.units); - Ylab = ['Unknown unit [',unit,']']; % default value + channeltypeindx = find(strcmpi(handles.data{wv_chanid}.header.chantype, ... + {settings.channeltypes.type})); + Ylab = [settings.channeltypes(channeltypeindx).description, ' [', unit,']']; % default value switch handles.prop.wave - case 'ecg' - Ylab = ['Amplitude [',unit,']']; case 'scr' - Ylab = ['Amplitude [',unit,']']; - case 'emg' - Ylab = ['Amplitude [',unit,']']; - case 'hp' - Ylab = 'Interpolated IBI [ms]'; - case {'pupil','pupil_l','pupil_r',... - 'pupil_pp','pupil_pp_l','pupil_pp_r'} + Ylab = ['Skin conductance [',unit,']']; + case {'pupil','pupil_l','pupil_r', 'pupil_c'} Ylab = ['Pupil size [',unit,']']; - case {'gaze_x','gaze_x_l','gaze_x_r',... - 'gaze_x_pp','gaze_x_pp_l','gaze_x_pp_r'} + case {'gaze_x','gaze_x_l','gaze_x_r', 'gaze_x_c'} Ylab = ['Gaze x coordinate [',unit,']']; - case {'gaze_y','gaze_y_l','gaze_y_r',... - 'gaze_y_pp','gaze_y_pp_l','gaze_y_pp_r'} + case {'gaze_y','gaze_y_l','gaze_y_r', 'gaze_y_c'} Ylab = ['Gaze y coordinate [',unit,']']; end ylabel(Ylab,'Fontsize',settings.ui.FontSizeText); From 19085c18fa6b622a4ea2f135357bd2821cb52754 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Sat, 30 Nov 2024 08:01:50 +0100 Subject: [PATCH 10/29] fix error handling in pspm_convert_gaze --- src/pspm_convert_gaze.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pspm_convert_gaze.m b/src/pspm_convert_gaze.m index 2640e2dd..61a410b2 100644 --- a/src/pspm_convert_gaze.m +++ b/src/pspm_convert_gaze.m @@ -122,6 +122,7 @@ if isnumeric(channel{i}) if ismember(channel{i}, channels_correct_units) [lsts, data{i}, infos, pos_of_channel(i)] = pspm_load_channel(alldata, channel{i}, 'gaze'); + if lsts < 1, return, end else warning('ID:invalid_input', 'Channel %i is in units "%s", expected was "%s".', ... channel{i}, alldata.data{channel{i}}.header.units, from); @@ -132,9 +133,9 @@ gazedata = struct('infos', alldata.infos, 'data', {alldata.data(channels_correct_units)}); [lsts, data{i}, infos, pos_of_channel(i)] = pspm_load_channel(gazedata, channel{i}, channeltypes{i}); % map channel index from list of channels with correct units to list of all channels + if lsts < 1, return, end pos_of_channel(i) = channels_correct_units(pos_of_channel(i)); end - if lsts < 1, return, end end % find eye of channels to use From 70f2bd314945a348e67ccab1ca68c62c4d29a75a Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Sat, 30 Nov 2024 08:16:00 +0100 Subject: [PATCH 11/29] improve error handling in pspm_convert_gaze --- src/pspm_convert_gaze.m | 9 +++++++-- src/pspm_select_channels.m | 9 ++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/pspm_convert_gaze.m b/src/pspm_convert_gaze.m index 61a410b2..16d9ca19 100644 --- a/src/pspm_convert_gaze.m +++ b/src/pspm_convert_gaze.m @@ -16,7 +16,7 @@ % │ inspect the channels. % ├────────.target: Target unit of conversion: a metric distance unit, % │ 'degree' or 'sps'. -% ├──.screen_width: With of the display in mm (not required if 'from' is +% ├──.screen_width: Width of the display in mm (not required if 'from' is % │ 'degree', or if both source and target are metric). % ├─.screen_height: Height of the display in mm (not required if 'from' is % │ 'degree', or if both source and target are metric). @@ -131,9 +131,14 @@ else % for channeltype specification, just consider channels in the correct units gazedata = struct('infos', alldata.infos, 'data', {alldata.data(channels_correct_units)}); + warning off [lsts, data{i}, infos, pos_of_channel(i)] = pspm_load_channel(gazedata, channel{i}, channeltypes{i}); + warning on + if lsts < 1 + warning('ID:invalid_input', 'Either there is no gaze channel in the file, or all gaze channels are in the incorrect units.') + return; + end % map channel index from list of channels with correct units to list of all channels - if lsts < 1, return, end pos_of_channel(i) = channels_correct_units(pos_of_channel(i)); end end diff --git a/src/pspm_select_channels.m b/src/pspm_select_channels.m index c472790a..8d52dc0c 100644 --- a/src/pspm_select_channels.m +++ b/src/pspm_select_channels.m @@ -57,6 +57,9 @@ channelunits_list = cellfun(@(x) x.header.units, data, 'uni', false); if strcmp(units, 'any') units = channelunits_list; + unitflag = 0; % required for error handling +else + unitflag = 1; end % To facilitate identification of eyetracker channels we use 'startsWith' % rather than 'strcmpi' - therefore we need to catch cases where channel @@ -75,10 +78,14 @@ pos_of_channels = channel; end -if isempty(pos_of_channels) +if isempty(pos_of_channels) && ~unitflag warning('ID:non_existing_chantype',... 'There are no channels of type ''%s'' in the data file', channel); return +elseif isempty(pos_of_channels) + warning('ID:non_existing_chantype',... + 'There are no channels of type ''%s'' with the correct units in the data file', channel); + return elseif any(pos_of_channels > numel(data)) warning('ID:invalid_input',... 'Input channel number(s) are greater than the number of channels in the data file.'); From fb3ba1dbcbd2ecc8ada0f431ea8f6d42ecbcb2c8 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Sat, 30 Nov 2024 08:43:07 +0100 Subject: [PATCH 12/29] fix cfg for find_valid_fixations (wrong option added) --- src/pspm_cfg/pspm_cfg_find_valid_fixations.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_find_valid_fixations.m b/src/pspm_cfg/pspm_cfg_find_valid_fixations.m index 43bc0255..742d525c 100644 --- a/src/pspm_cfg/pspm_cfg_find_valid_fixations.m +++ b/src/pspm_cfg/pspm_cfg_find_valid_fixations.m @@ -97,7 +97,7 @@ %% Missing missing = cfg_menu; missing.name = 'Add channel with information invalid data points'; -missing.tag = 'missing'; +missing.tag = 'add_invalid'; missing.labels = {'Yes', 'No'}; missing.values = {1, 0}; missing.val = {0}; @@ -114,7 +114,7 @@ output = cfg_branch; output.name = 'Output settings'; output.tag = 'output_settings'; -output.val = {missing, plot_gaze_coords}; +output.val = {add_invalid, plot_gaze_coords}; %% Executable branch FindValidFixa = cfg_exbranch; FindValidFixa.name = 'Find valid fixations'; From 3f208ef747db26cd1765cd4cd9cce9d6f54da0a6 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Sat, 30 Nov 2024 09:28:04 +0100 Subject: [PATCH 13/29] improve display and fix cfg --- src/pspm_cfg/pspm_cfg_find_valid_fixations.m | 4 +- .../pspm_cfg_run_find_valid_fixations.m | 2 +- src/pspm_display.m | 43 ++++--------------- src/pspm_find_valid_fixations.m | 10 ++--- src/pspm_init.m | 8 ++-- 5 files changed, 20 insertions(+), 47 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_find_valid_fixations.m b/src/pspm_cfg/pspm_cfg_find_valid_fixations.m index 742d525c..923bdbb9 100644 --- a/src/pspm_cfg/pspm_cfg_find_valid_fixations.m +++ b/src/pspm_cfg/pspm_cfg_find_valid_fixations.m @@ -41,7 +41,7 @@ Resol.tag = 'resolution'; Resol.strtype = 'i'; Resol.num = [1 2]; -Resol.val = {[1280 1024]}; +Resol.val = {[1 1]}; Resol.help = pspm_cfg_help_format('pspm_find_valid_fixations', 'options.resolution'); %% Fixation point default FixPtDefault = cfg_const; @@ -114,7 +114,7 @@ output = cfg_branch; output.name = 'Output settings'; output.tag = 'output_settings'; -output.val = {add_invalid, plot_gaze_coords}; +output.val = {missing, plot_gaze_coords}; %% Executable branch FindValidFixa = cfg_exbranch; FindValidFixa.name = 'Find valid fixations'; diff --git a/src/pspm_cfg/pspm_cfg_run_find_valid_fixations.m b/src/pspm_cfg/pspm_cfg_run_find_valid_fixations.m index f51136a3..7a22a510 100644 --- a/src/pspm_cfg/pspm_cfg_run_find_valid_fixations.m +++ b/src/pspm_cfg/pspm_cfg_run_find_valid_fixations.m @@ -36,7 +36,7 @@ %% options options.channel = pspm_cfg_selector_channel('run', job.chan); options.channel_action = job.channel_action; -options = pspm_update_struct(options, job.output_settings, {'missing', ... +options = pspm_update_struct(options, job.output_settings, {'add_invalid', ... 'plot_gaze_coords'}); %% run if isfield(job.val_method,'bitmap_file') diff --git a/src/pspm_display.m b/src/pspm_display.m index 7ef5221f..a049472a 100644 --- a/src/pspm_display.m +++ b/src/pspm_display.m @@ -1057,46 +1057,19 @@ function update_summary_list (handles) [num2str(handles.info.duration), ' s']; handles.tag_summary_recording_duration_content.Value = ... handles.info.duration; -[r_channels,c_channels] = size(handles.data); -% array_channel_type = cell(r_channels,c_channels); +n_channels = numel(handles.data); string_channel_list = []; -if r_channels > 1 && c_channels > 1 - for i_r_channel = 1:r_channels - for i_c_channels = 1:c_channels - % array_channel_type(r_channels,c_channels) = ... - % handles.data{i_r_channel,i_c_channels}.header.chantype; - targeted_channel_reference = ... - handles.data{i_r_channel,i_c_channels}.header.chantype; - targeted_channel_display = ... - channel_list_full(strcmp(targeted_channel_reference, ... - {channel_type_reference_list.type})); - targeted_channel_display = ... - targeted_channel_display{1,1}; - string_channel_list = [string_channel_list, ... - num2str(i_r_channel), ',', num2str(i_c_channels), ' ', ... - targeted_channel_display, newline]; - end - end -else - number_channel = max(r_channels, c_channels); - for i_channel = 1:number_channel - if r_channels == 1 - i_r_channels = 1; - i_c_channels = i_channel; - else - i_c_channels = 1; - i_r_channels = i_channel; - end +for i_channel = 1:n_channels targeted_channel_reference = ... - handles.data{i_r_channels,i_c_channels}.header.chantype; + handles.data{i_channel}.header.chantype; targeted_channel_display = ... - channel_list_full(strcmp(targeted_channel_reference, ... - {channel_type_reference_list.type})); + channel_list_full(strcmp(targeted_channel_reference, ... + {channel_type_reference_list.type})); targeted_channel_display = targeted_channel_display{1,1}; string_channel_list = [string_channel_list, ... - num2str(i_channel), ' ', ... - targeted_channel_display, newline]; - end + num2str(i_channel), ' ', ... + targeted_channel_display, newline]; end + handles.tag_summary_channel_list_content.String = string_channel_list; end diff --git a/src/pspm_find_valid_fixations.m b/src/pspm_find_valid_fixations.m index 8f27822c..a2d7445d 100644 --- a/src/pspm_find_valid_fixations.m +++ b/src/pspm_find_valid_fixations.m @@ -45,13 +45,13 @@ % │ to the given resolution, and in the eyetracker coordinate system). % │ n should equal either 1 (constant fixation point) or the length % │ of the actual data. If resolution is not defined the values are -% │ given in percent. Therefore [0.5 0.5] would correspond to the -% │ middle of the screen. Default is [0.5 0.5]. Only taken into account +% │ given in percent. Therefore (0.5 0.5) would correspond to the +% │ middle of the screen. Default is (0.5 0.5). Only taken into account % │ if there is no bitmap. % ├────.resolution : Resolution with which the fixation point is defined (Maximum value % │ of the x and y coordinates). This can be the screen resolution in -% │ pixels (e.g. [1280 1024]) or the width and height of the screen -% │ in cm (e.g. [50 30]). Default is [1 1]. Only taken into account +% │ pixels (e.g. (1280 1024)) or the width and height of the screen +% │ in cm (e.g. (50 30)). Default is (1 1). Only taken into account % │ if there is no bitmap. % ├.plot_gaze_coords: Define whether to plot the gaze coordinates for visual % │ inspection of the validation process. Default is false. @@ -256,7 +256,7 @@ % check plotting if options.plot_gaze_coords - fg = figure; + fg = figure('Name', 'Fixation plot'); ax = axes('NextPlot', 'add'); set(ax, 'Parent', handle(fg)); diff --git a/src/pspm_init.m b/src/pspm_init.m index 469a0505..c8746f9f 100644 --- a/src/pspm_init.m +++ b/src/pspm_init.m @@ -190,10 +190,10 @@ defaults.channeltypes(end+1) = struct(s_t, 'pupil_r', s_de, 'Pupil right', s_i, @pspm_get_pupil_r, s_da, 'wave'); defaults.channeltypes(end+1) = struct(s_t, 'pupil_c', s_de, 'Pupil combined', s_i, @pspm_get_pupil_c, s_da, 'wave'); % Pupil missing -defaults.channeltypes(end+1) = struct(s_t, 'pupil_missing', s_de, 'Pupil data missing/interpolated', s_i, @none, s_da, 'wave'); -defaults.channeltypes(end+1) = struct(s_t, 'pupil_missing_l', s_de, 'Pupil data missing/interpolated left', s_i, @none, s_da, 'wave'); -defaults.channeltypes(end+1) = struct(s_t, 'pupil_missing_r', s_de, 'Pupil data missing/interpolated right', s_i, @none, s_da, 'wave'); -defaults.channeltypes(end+1) = struct(s_t, 'pupil_missing_c', s_de, 'Pupil data missing/interpolated combined', s_i, @none, s_da, 'wave'); +defaults.channeltypes(end+1) = struct(s_t, 'pupil_missing', s_de, 'Pupil data missing', s_i, @none, s_da, 'wave'); +defaults.channeltypes(end+1) = struct(s_t, 'pupil_missing_l', s_de, 'Pupil data missingleft', s_i, @none, s_da, 'wave'); +defaults.channeltypes(end+1) = struct(s_t, 'pupil_missing_r', s_de, 'Pupil data missing right', s_i, @none, s_da, 'wave'); +defaults.channeltypes(end+1) = struct(s_t, 'pupil_missing_c', s_de, 'Pupil data missing combined', s_i, @none, s_da, 'wave'); % Blink defaults.channeltypes(end+1) = struct(s_t, 'blink_l', s_de, 'Blink left', s_i, @pspm_get_blink_l, s_da, 'wave'); defaults.channeltypes(end+1) = struct(s_t, 'blink_r', s_de, 'Blink right', s_i, @pspm_get_blink_r, s_da, 'wave'); From f91b04ff8743f47e268d5969662c4506d0367240 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Mon, 2 Dec 2024 18:23:21 +0100 Subject: [PATCH 14/29] fix pupil size GUI and double overwrite question in pspm_glm --- src/pspm_cfg/pspm_cfg_run_glm_ps_fc.m | 2 +- src/pspm_glm.m | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_run_glm_ps_fc.m b/src/pspm_cfg/pspm_cfg_run_glm_ps_fc.m index b9b3e96e..85e950a1 100644 --- a/src/pspm_cfg/pspm_cfg_run_glm_ps_fc.m +++ b/src/pspm_cfg/pspm_cfg_run_glm_ps_fc.m @@ -5,7 +5,7 @@ options = struct(); % set modality -model.modality = 'ps'; +model.modality = 'pupil'; model.modelspec = 'ps_fc'; % basis function diff --git a/src/pspm_glm.m b/src/pspm_glm.m index 414cbd9c..6e281eb2 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -219,7 +219,8 @@ % 2.4 check files % stop the script if files are not allowed to overwrite -if ~pspm_overwrite(model.modelfile, options) +options.overwrite = pspm_overwrite(model.modelfile, options); +if ~options.overwrite warning('ID:invalid_input', 'Model file exists, and overwriting not allowed by user.'); return end From 88b2964981f86bc0cb876ca130769b09e9c9da4a Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Tue, 3 Dec 2024 09:45:58 +0100 Subject: [PATCH 15/29] improve pspm_extract_segments for DCM with trial names --- src/pspm_extract_segments.m | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/pspm_extract_segments.m b/src/pspm_extract_segments.m index 9fe869ff..a77f1a87 100644 --- a/src/pspm_extract_segments.m +++ b/src/pspm_extract_segments.m @@ -186,14 +186,29 @@ end if strcmpi(method, 'model') && strcmpi(data.modeltype, 'dcm') - % DCM has no condition information - onsets{1} = cellfun(@(x, y) pspm_time2index(x, sr , y), ... + temponsets = cellfun(@(x, y) pspm_time2index(x, sr , y), ... data.input.trlstart(:), ... num2cell(session_duration), ... 'UniformOutput', false); - names{1} = 'all'; - n_cond = 1; - options.timeunits = 'seconds'; + trlnum = cellfun(@(x) numel(x), temponsets); + trlnum = trlnum(:); + % if DCM has condition information + if isfield(data, 'trlnames') && numel(data.trlnames) == sum(trlnum) + names = unique(data.trlnames); + n_cond = numel(names); + prevtrlnum = [0; cumsum(trlnum(1:(end-1)))]; + for i_cond = 1:numel(names) + for i_sn = 1:numel(temponsets) + snindx = prevtrlnum(i_sn) + (1:trlnum(i_sn)); + onsets{i_cond}{i_sn} = temponsets{i_sn}(strcmpi(data.trlnames(snindx), names{i_cond})); + end + end + else + % if DCM has no condition information + names{1} = 'all'; + n_cond = 1; + options.timeunits = 'seconds'; + end else [lsts, multi] = pspm_get_timing('onsets', timing, options.timeunits); [msts, onsets] = pspm_multi2index(options.timeunits, multi, sr, session_duration, events); From 08ff63074ca1ae4852186c5a4d026383ec56a950 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Tue, 3 Dec 2024 12:21:17 +0100 Subject: [PATCH 16/29] fix extract_segments --- src/pspm_extract_segments.m | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pspm_extract_segments.m b/src/pspm_extract_segments.m index a77f1a87..619c39f6 100644 --- a/src/pspm_extract_segments.m +++ b/src/pspm_extract_segments.m @@ -208,6 +208,7 @@ names{1} = 'all'; n_cond = 1; options.timeunits = 'seconds'; + onsets{1} = temponsets; end else [lsts, multi] = pspm_get_timing('onsets', timing, options.timeunits); From ac8d1cad2ac664e1663bd4ed0a01e606ba9bfa1b Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Tue, 3 Dec 2024 17:01:10 +0100 Subject: [PATCH 17/29] gui updates --- src/pspm_cfg/pspm_cfg_combine_markerchannels.m | 4 +--- src/pspm_cfg/pspm_cfg_dcm.m | 4 ++-- src/pspm_cfg/pspm_cfg_find_valid_fixations.m | 14 +++++++------- src/pspm_cfg/pspm_cfg_glm.m | 17 +++-------------- src/pspm_cfg/pspm_cfg_glm_hp_e.m | 13 ++++--------- src/pspm_cfg/pspm_cfg_glm_scr.m | 17 +++++++++++++++++ src/pspm_combine_markerchannels.m | 7 ++++--- 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_combine_markerchannels.m b/src/pspm_cfg/pspm_cfg_combine_markerchannels.m index b5958752..79354391 100644 --- a/src/pspm_cfg/pspm_cfg_combine_markerchannels.m +++ b/src/pspm_cfg/pspm_cfg_combine_markerchannels.m @@ -12,9 +12,7 @@ channel_action = pspm_cfg_selector_channel_action; %% Specific items -marker_chan.help = {['Choose any number of marker channel numbers ',... - 'to combine. If 0, all marker ',... - 'channels in the file are combined.']}; +marker_chan.help = pspm_cfg_help_format('pspm_combine_markerchannels', 'options.marker_chan_num'); %% Executable branch combine_markerchannels = cfg_exbranch; diff --git a/src/pspm_cfg/pspm_cfg_dcm.m b/src/pspm_cfg/pspm_cfg_dcm.m index 5beb365b..eb507131 100644 --- a/src/pspm_cfg/pspm_cfg_dcm.m +++ b/src/pspm_cfg/pspm_cfg_dcm.m @@ -16,7 +16,7 @@ timingfile.tag = 'timingfile'; timingfile.num = [1 1]; timingfile.filter = '.*\.(mat|MAT)$'; -timingfile.help = {'See general help for this item for more information on how to specify timings'}; +timingfile.help = {'See general DCM help for more information on how to specify timings'}; name = cfg_entry; @@ -44,7 +44,7 @@ timing_man_rep.tag = 'timing_man_rep'; timing_man_rep.values = {timing_man}; timing_man_rep.num = [1 Inf]; -timing_man_rep.help = {'See general help for this item for more information on how to specify timings'}; +timing_man_rep.help = {'See general DCM help for more information on how to specify timings'}; timing = cfg_choice; diff --git a/src/pspm_cfg/pspm_cfg_find_valid_fixations.m b/src/pspm_cfg/pspm_cfg_find_valid_fixations.m index 923bdbb9..5705edc0 100644 --- a/src/pspm_cfg/pspm_cfg_find_valid_fixations.m +++ b/src/pspm_cfg/pspm_cfg_find_valid_fixations.m @@ -55,18 +55,16 @@ FixPtFile.name = 'File'; FixPtFile.tag = 'fixpoint_file'; FixPtFile.num = [1 1]; -FixPtFile.help = {['.mat file containing a variable F with an ', ... - 'n x 2 matrix. N should have the length of ', ... - 'the recorded data and each row should ', ... - 'define the fixation point for the ', ... - 'respective recorded data row.']}; +temphelp = pspm_cfg_help_format('pspm_find_valid_fixations', 'options.fixation_point'); +FixPtFile.help = {['Specify a .mat file containing a variable ''F'': ', ... + temphelp{1}]}; %% Fixation point value FixPtVal = cfg_entry; FixPtVal.name = 'Point'; FixPtVal.tag = 'fixpoint'; FixPtVal.strtype = 'r'; FixPtVal.num = [1 2]; -FixPtVal.help = {['x/y coordinates of constant fixation point.']}; +FixPtVal.help = {'x/y coordinates of constant fixation point.'}; %% Fixation point FixPt = cfg_choice; @@ -87,7 +85,9 @@ bitmap.name = 'Bitmap file'; bitmap.tag = 'bitmap_file'; bitmap.num = [1 1]; -bitmap.help = pspm_cfg_help_format('pspm_find_valid_fixations', 'bitmap'); +temphelp = pspm_cfg_help_format('pspm_find_valid_fixations', 'bitmap'); +bitmap.help = {['Specify a .mat file containing a variable ''bitmap'': ', ... + temphelp{1}]}; %% Validation method val_method = cfg_choice; val_method.name = 'Validation method'; diff --git a/src/pspm_cfg/pspm_cfg_glm.m b/src/pspm_cfg/pspm_cfg_glm.m index 123deaa8..2baa455f 100644 --- a/src/pspm_cfg/pspm_cfg_glm.m +++ b/src/pspm_cfg/pspm_cfg_glm.m @@ -65,16 +65,6 @@ %% Modality dependent items % Basis function -% SCRF -for i=1:3 - scrf{i} = cfg_const; - scrf{i}.name = ['SCRF ' num2str(i-1)]; - scrf{i}.tag = ['scrf' num2str(i-1)]; - scrf{i}.val = {i-1}; -end -scrf{1}.help = {'SCRF without derivatives.'}; -scrf{2}.help = {'SCRF with time derivative (default).'}; -scrf{3}.help = {'SCRF with time and dispersion derivative.'}; %FIR n = cfg_entry; @@ -106,10 +96,9 @@ bf = cfg_choice; bf.name = 'Basis Function'; bf.tag = 'bf'; -bf.val = {scrf{2}}; -bf.values = {scrf{:}, fir}; -bf.help = {['Basis functions. Standard is to use a canonical skin conductance response function ' ... - '(SCRF) with time derivative for later reconstruction of the response peak.']}; +bf.val = {fir}; +bf.values = {fir}; +bf.help = {}; %% Latency time_window = cfg_entry; diff --git a/src/pspm_cfg/pspm_cfg_glm_hp_e.m b/src/pspm_cfg/pspm_cfg_glm_hp_e.m index e6027d43..48613b73 100644 --- a/src/pspm_cfg/pspm_cfg_glm_hp_e.m +++ b/src/pspm_cfg/pspm_cfg_glm_hp_e.m @@ -67,14 +67,9 @@ hprf_e.val = {n_bf}; hprf_e.help = {''}; -bf = cfg_choice; -bf.name = 'Basis Function'; -bf.tag = 'bf'; -bf.val = {hprf_e}; -bf.values = {hprf_e, fir}; -bf.help = {['Basis functions. Standard is to use a canonical evoked heart period response function ' ... - '(HPRF_E) with time derivative for later reconstruction of the response peak.']}; - % look for bf and replace b = cellfun(@(f) strcmpi(f.tag, 'bf'), glm_hp_e.val); -glm_hp_e.val{b} = bf; +glm_hp_e.val{b}.values = [{hprf_e}, glm_hp_e.val{b}.values]; +glm_hp_e.val{b}.val = {hprf_e}; +glm_hp_e.val{b}.help = {['Basis functions. Standard is to use a canonical evoked heart period response function ' ... + '(HPRF_E) with time derivative for later reconstruction of the response peak.']}; diff --git a/src/pspm_cfg/pspm_cfg_glm_scr.m b/src/pspm_cfg/pspm_cfg_glm_scr.m index 113ad8be..c01b4aeb 100644 --- a/src/pspm_cfg/pspm_cfg_glm_scr.m +++ b/src/pspm_cfg/pspm_cfg_glm_scr.m @@ -24,4 +24,21 @@ glm_scr.name = 'GLM for SCR'; glm_scr.tag = 'glm_scr'; +% BF +for i=1:3 + scrf{i} = cfg_const; + scrf{i}.name = ['SCRF ' num2str(i-1)]; + scrf{i}.tag = ['scrf' num2str(i-1)]; + scrf{i}.val = {i-1}; +end +scrf{1}.help = {'SCRF without derivatives.'}; +scrf{2}.help = {'SCRF with time derivative (default).'}; +scrf{3}.help = {'SCRF with time and dispersion derivative.'}; +b = cellfun(@(f) strcmpi(f.tag, 'bf'), glm_scr.val); +glm_scr.val{b}.values = [scrf, glm_scr.val{b}.values]; +glm_scr.val{b}.val = {scrf{2}}; +glm_scr.val{b}.help = {['Basis functions. Standard is to use a canonical skin conductance response function ' ... + '(SCRF) with time derivative for later reconstruction of the response peak.']}; + + diff --git a/src/pspm_combine_markerchannels.m b/src/pspm_combine_markerchannels.m index 6dc1d1d7..465904b1 100644 --- a/src/pspm_combine_markerchannels.m +++ b/src/pspm_combine_markerchannels.m @@ -8,7 +8,7 @@ % ● Format % [sts, outchannel] = pspm_combine_markerchannels(datafile, options) % ● Arguments -% * datafile : data file name(s): char +% * datafile : data file name: char % ┌─────────options % ├─.channel_action : Accepted values: 'add'/'replace' % │ Defines whether the new channel should be added @@ -18,8 +18,9 @@ % │ the first option is used, then use marker channel % │ indexing in further processing which by default % │ takes the first marker channel as input -% └.marker_chan_num: any number of marker channel numbers - if undefined -% or 0, all marker channels of each file are used +% └.marker_chan_num: Choose any number of marker channel numbers to +% combine. If 0 all marker channels are +% used [default: use all channels]. % ● History % Introduced In PsPM 6.1.2 % Written in 2023 by Dominik R Bach (Uni Bonn) From 62772c5057fc4b54c0e2ff9e48e793bf0cd3c9eb Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Tue, 3 Dec 2024 17:47:21 +0100 Subject: [PATCH 18/29] gui updates --- src/pspm_cfg/pspm_cfg_data_preprocessing.m | 4 ++-- src/pspm_cfg/pspm_cfg_first_level.m | 5 +++-- src/pspm_cfg/pspm_cfg_first_level_hp.m | 2 +- src/pspm_cfg/pspm_cfg_first_level_resp.m | 2 +- src/pspm_cfg/pspm_cfg_first_level_sebr.m | 2 +- src/pspm_cfg/pspm_cfg_pp_pupil.m | 9 ++++++++- src/pspm_cfg/pspm_cfg_process_illuminance.m | 5 +---- src/pspm_cfg/pspm_cfg_review1.m | 4 +--- src/pspm_cfg/pspm_cfg_scr_pp.m | 2 +- src/pspm_cfg/pspm_cfg_tools.m | 13 +++++++++---- src/pspm_process_illuminance.m | 5 ++++- src/pspm_scr_pp.m | 5 +++-- 12 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_data_preprocessing.m b/src/pspm_cfg/pspm_cfg_data_preprocessing.m index 0c3f3abb..c877703b 100644 --- a/src/pspm_cfg/pspm_cfg_data_preprocessing.m +++ b/src/pspm_cfg/pspm_cfg_data_preprocessing.m @@ -11,9 +11,9 @@ cfg.name = 'Data Preprocessing'; cfg.tag = 'data_preprocessing'; cfg.values = {pspm_cfg_scr_pp,... - pspm_cfg_pp_cardiac, ... - pspm_cfg_resp_pp, ... pspm_cfg_pp_pupil, ... + pspm_cfg_pp_cardiac, ... + pspm_cfg_resp_pp, ... pspm_cfg_pp_emg}; cfg.forcestruct = true; cfg.help = {'Help: Data preprocessing'}; diff --git a/src/pspm_cfg/pspm_cfg_first_level.m b/src/pspm_cfg/pspm_cfg_first_level.m index d310a745..cd94418b 100644 --- a/src/pspm_cfg/pspm_cfg_first_level.m +++ b/src/pspm_cfg/pspm_cfg_first_level.m @@ -8,10 +8,11 @@ cfg = cfg_repeat; cfg.name = 'First Level'; cfg.tag = 'first_level'; -cfg.values = {pspm_cfg_first_level_scr, pspm_cfg_first_level_hp, ... - pspm_cfg_first_level_resp, ... +cfg.values = {pspm_cfg_first_level_scr, ... pspm_cfg_first_level_ps, ... pspm_cfg_first_level_sebr, ... + pspm_cfg_first_level_hp, ... + pspm_cfg_first_level_resp, ... pspm_cfg_first_level_sps, ... pspm_cfg_review1, ... pspm_cfg_export}; % Values in a cfg_repeat can be any cfg_item objects diff --git a/src/pspm_cfg/pspm_cfg_first_level_hp.m b/src/pspm_cfg/pspm_cfg_first_level_hp.m index b243d7ef..7526e8f4 100644 --- a/src/pspm_cfg/pspm_cfg_first_level_hp.m +++ b/src/pspm_cfg/pspm_cfg_first_level_hp.m @@ -8,6 +8,6 @@ cfg = cfg_repeat; cfg.name = 'Heart period'; cfg.tag = 'hp'; -cfg.values = {pspm_cfg_glm_hp_e, pspm_cfg_glm_hp_fc, pspm_cfg_glm_hp_rew}; % Values in a cfg_repeat can be any cfg_item objects +cfg.values = {pspm_cfg_glm_hp_fc, pspm_cfg_glm_hp_rew, pspm_cfg_glm_hp_e}; % Values in a cfg_repeat can be any cfg_item objects cfg.forcestruct = true; cfg.help = {''}; diff --git a/src/pspm_cfg/pspm_cfg_first_level_resp.m b/src/pspm_cfg/pspm_cfg_first_level_resp.m index 36ee91c2..97807bd3 100644 --- a/src/pspm_cfg/pspm_cfg_first_level_resp.m +++ b/src/pspm_cfg/pspm_cfg_first_level_resp.m @@ -8,7 +8,7 @@ cfg = cfg_repeat; cfg.name = 'Respiration'; cfg.tag = 'resp'; -cfg.values = {pspm_cfg_glm_ra_e, pspm_cfg_glm_ra_fc, pspm_cfg_glm_rfr_e, ... +cfg.values = {pspm_cfg_glm_ra_fc, pspm_cfg_glm_ra_e, pspm_cfg_glm_rfr_e, ... pspm_cfg_glm_rp_e}; % Values in a cfg_repeat can be any cfg_item objects cfg.forcestruct = true; cfg.help = {''}; diff --git a/src/pspm_cfg/pspm_cfg_first_level_sebr.m b/src/pspm_cfg/pspm_cfg_first_level_sebr.m index 328dd6f5..535e6a89 100644 --- a/src/pspm_cfg/pspm_cfg_first_level_sebr.m +++ b/src/pspm_cfg/pspm_cfg_first_level_sebr.m @@ -1,4 +1,4 @@ -function cfg = pspm_cfg_first_level_seb +function cfg = pspm_cfg_first_level_sebr % $Id$ % $Rev$ diff --git a/src/pspm_cfg/pspm_cfg_pp_pupil.m b/src/pspm_cfg/pspm_cfg_pp_pupil.m index 0d06a170..82a87f64 100644 --- a/src/pspm_cfg/pspm_cfg_pp_pupil.m +++ b/src/pspm_cfg/pspm_cfg_pp_pupil.m @@ -13,6 +13,13 @@ cfg = cfg_repeat; cfg.name = 'Pupil & Eye tracking'; cfg.tag = 'pp_pupil'; -cfg.values = {pspm_cfg_process_illuminance, pspm_cfg_find_valid_fixations, pspm_cfg_pupil_correct, pspm_cfg_pupil_preprocess, pspm_cfg_pupil_size_convert, pspm_cfg_gaze_convert, pspm_cfg_gaze_pp}; +cfg.values = {pspm_cfg_pupil_size_convert, ... + pspm_cfg_pupil_preprocess, ... + pspm_cfg_find_valid_fixations, ... + pspm_cfg_pupil_correct, ... + pspm_cfg_gaze_convert, ... + pspm_cfg_gaze_pp, ... + pspm_cfg_process_illuminance}; cfg.forcestruct = true; cfg.help = {'Help: Pupil & Eye tracking preprocessing'}; + \ No newline at end of file diff --git a/src/pspm_cfg/pspm_cfg_process_illuminance.m b/src/pspm_cfg/pspm_cfg_process_illuminance.m index babf0448..1deeb85d 100644 --- a/src/pspm_cfg/pspm_cfg_process_illuminance.m +++ b/src/pspm_cfg/pspm_cfg_process_illuminance.m @@ -15,10 +15,7 @@ lum_file.filter = '.*\.(mat|MAT)$'; lum_file.tag = 'lum_file'; lum_file.num = [1 1]; -lum_file.help = {['Select a file that contains illuminance data. ', ... - 'The file should contain a variable ''Lx'' ', ... - 'which should be an n x 1 numeric ', ... - 'vector containing the illuminance values. ']}; +lum_file.help = pspm_cfg_help_format('pspm_process_illuminance', 'ldata'); %% Sample rate sr = cfg_entry; sr.name = 'Sample rate'; diff --git a/src/pspm_cfg/pspm_cfg_review1.m b/src/pspm_cfg/pspm_cfg_review1.m index edf2e25d..8b96d475 100644 --- a/src/pspm_cfg/pspm_cfg_review1.m +++ b/src/pspm_cfg/pspm_cfg_review1.m @@ -91,6 +91,4 @@ review.tag = 'review'; review.val = {modelfile, modeltype}; review.prog = @pspm_cfg_run_review1; -review.help = {['This module allows you to look at the first-level (within-subject) model to investigate ' ... - 'model fit and potential estimation problems. This is not necessary for standard analyses. Further ' ... - 'processing can be performed directly on the second level after first-level model estimation.']}; +review.help = {}; diff --git a/src/pspm_cfg/pspm_cfg_scr_pp.m b/src/pspm_cfg/pspm_cfg_scr_pp.m index 295b094a..49a42498 100644 --- a/src/pspm_cfg/pspm_cfg_scr_pp.m +++ b/src/pspm_cfg/pspm_cfg_scr_pp.m @@ -100,7 +100,7 @@ scr_data_island_threshold, ... scr_expand_epochs, ... clipping_detection}; -options.help = {['']}; +options.help = {}; % Executable Branch pp_scr = cfg_exbranch; diff --git a/src/pspm_cfg/pspm_cfg_tools.m b/src/pspm_cfg/pspm_cfg_tools.m index d023fc77..b9e97a0e 100644 --- a/src/pspm_cfg/pspm_cfg_tools.m +++ b/src/pspm_cfg/pspm_cfg_tools.m @@ -2,9 +2,14 @@ cfg = cfg_repeat; cfg.name = 'Tools'; cfg.tag = 'tools'; -cfg.values = {pspm_cfg_display, pspm_cfg_rename, pspm_cfg_split_sessions, ... - pspm_cfg_merge, pspm_cfg_filtering, pspm_cfg_interpolate, ... - pspm_cfg_extract_segments, pspm_cfg_get_markerinfo, ... - pspm_cfg_data_editor}; +cfg.values = {pspm_cfg_split_sessions, ... + pspm_cfg_merge, ... + pspm_cfg_filtering, ... + pspm_cfg_interpolate, ... + pspm_cfg_rename, ... + pspm_cfg_extract_segments, ... + pspm_cfg_get_markerinfo, ... + pspm_cfg_display, ... + pspm_cfg_data_editor}; cfg.forcestruct = true; cfg.help = {'Help: Tools...'}; diff --git a/src/pspm_process_illuminance.m b/src/pspm_process_illuminance.m index 1966ad50..f5106faf 100644 --- a/src/pspm_process_illuminance.m +++ b/src/pspm_process_illuminance.m @@ -16,7 +16,10 @@ % ● Format % [sts, out] = pspm_process_illuminance(ldata, sr, options) % ● Arguments -% * ldata: Illuminance data as (cell of) 1x1 double or filename. +% * ldata: Specify illuminance data as name of a file that +% contains a variable Lx, a n x 1 numeric +% vector containing the illuminance values +% [or directly specify ldata as a vector]. % * sr: Sample rate in Hz of the input illuminance data. % ┌────────options % ├────────────.fn: [filename] Ff specified ldata{i,j} will be saved to a file diff --git a/src/pspm_scr_pp.m b/src/pspm_scr_pp.m index 30e1c7f1..36e253bf 100644 --- a/src/pspm_scr_pp.m +++ b/src/pspm_scr_pp.m @@ -1,11 +1,12 @@ function [sts, out] = pspm_scr_pp(datafile, options) % ● Description % pspm_scr_pp implements a simple skin conductance response (SCR) quality -% check according to the following two rules. +% check according to the following steps: % (1) Microsiemens values must be within range (0.05 to 60). % (2) Absolute slope of value change must be less than 10 microsiemens % per second. -% Data points not corresponding to these rules are considered missing. +% (3) Clipping detection: value stays constant at a floor or ceiling. +% (4) Detect and remove data islands neighboured by artefacts. % If a missing epochs filename is specified, the detected epochs % will be written to a missing epochs file to be used for GLM % (recommended). Otherwise, the function will create a channel in the From 93cbbb325fdeb04a96faf6d6fc3f14853e3cd8f0 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Tue, 3 Dec 2024 18:01:38 +0100 Subject: [PATCH 19/29] improve error handling in pspm_export --- src/pspm_export.m | 2 +- src/pspm_load1.m | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pspm_export.m b/src/pspm_export.m index 7835473d..d8b4286b 100644 --- a/src/pspm_export.m +++ b/src/pspm_export.m @@ -122,7 +122,7 @@ excl_stats_contained = false(numel(modelfile),1); for iFile = 1:numel(modelfile) [lsts, data(iFile), modeltype{iFile}] = pspm_load1(modelfile{iFile}, statstype); - if lsts == -1, return; end; + if lsts < 1, return; end; % set flag to indicate if exclude statistics are contained if isfield(data(iFile),'stats_exclude') && isfield(data(iFile),'stats_missing') excl_stats_contained(iFile) = true; diff --git a/src/pspm_load1.m b/src/pspm_load1.m index 0eceb4af..55302b15 100644 --- a/src/pspm_load1.m +++ b/src/pspm_load1.m @@ -289,6 +289,7 @@ end else warning('ID:invalid_input', '%s. ''recon'' option only defined for GLM files', errmsg); + return; end case 'con' if conflag From caaa63fb64bccce0ea6723be9ac19941bcfe8bdd Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Tue, 3 Dec 2024 18:45:02 +0100 Subject: [PATCH 20/29] fix run_find_sounds --- src/pspm_cfg/pspm_cfg_run_find_sounds.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pspm_cfg/pspm_cfg_run_find_sounds.m b/src/pspm_cfg/pspm_cfg_run_find_sounds.m index d6360071..9f33f7d5 100644 --- a/src/pspm_cfg/pspm_cfg_run_find_sounds.m +++ b/src/pspm_cfg/pspm_cfg_run_find_sounds.m @@ -15,7 +15,7 @@ end options.marker_chan_num = pspm_cfg_selector_channel('run', d.chan); if d.n_sounds > 0 - options.expectedSoundCount = job.output.diagnostic.n_sounds; + options.expectedSoundCount = d.n_sounds; end options.maxdelay = d.max_delay; diag_out = fieldnames(d.diag_output); From 76597dc91c746582a23a722f11759e2354ab78d5 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Thu, 5 Dec 2024 15:44:09 +0100 Subject: [PATCH 21/29] updates and small fixes --- src/pspm_cfg/pspm_cfg_find_valid_fixations.m | 2 +- src/pspm_cfg/pspm_cfg_run_export.m | 4 +-- src/pspm_cfg/pspm_cfg_selector_outputfile.m | 7 +++- src/pspm_export.m | 34 ++++++++++---------- src/pspm_load_channel.m | 2 +- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/pspm_cfg/pspm_cfg_find_valid_fixations.m b/src/pspm_cfg/pspm_cfg_find_valid_fixations.m index 5705edc0..366792c8 100644 --- a/src/pspm_cfg/pspm_cfg_find_valid_fixations.m +++ b/src/pspm_cfg/pspm_cfg_find_valid_fixations.m @@ -96,7 +96,7 @@ val_method.help = {}; %% Missing missing = cfg_menu; -missing.name = 'Add channel with information invalid data points'; +missing.name = 'Add channel with information on invalid data points'; missing.tag = 'add_invalid'; missing.labels = {'Yes', 'No'}; missing.values = {1, 0}; diff --git a/src/pspm_cfg/pspm_cfg_run_export.m b/src/pspm_cfg/pspm_cfg_run_export.m index 9c500441..4cf19cb8 100644 --- a/src/pspm_cfg/pspm_cfg_run_export.m +++ b/src/pspm_cfg/pspm_cfg_run_export.m @@ -1,15 +1,15 @@ function out = pspm_cfg_run_export(job) % Updated on 19-12-2023 by Teddy +options = struct(); modelfile = job.datafile; if isfield(job.target, 'screen') target = 'screen'; else - target = pspm_cfg_selector_outputfile('run', job.target); + target = pspm_cfg_selector_outputfile('run', job.target, 'tsv'); options.overwrite = job.target.output.overwrite; end delimfield = fieldnames(job.delim); delim = job.delim.(delimfield{1}); -options = struct(); options.delim = delim; options.target = target; options.statstype = job.datatype; diff --git a/src/pspm_cfg/pspm_cfg_selector_outputfile.m b/src/pspm_cfg/pspm_cfg_selector_outputfile.m index b1952d27..7ac8e5b3 100644 --- a/src/pspm_cfg/pspm_cfg_selector_outputfile.m +++ b/src/pspm_cfg/pspm_cfg_selector_outputfile.m @@ -9,7 +9,12 @@ elseif strcmpi(outtype, 'run') job = varargin{1}; [pth, fn, ext] = fileparts(job.output.file); - output = fullfile(job.output.dir{1}, [fn, '.mat']); + if nargin >= 3 + ext = varargin{2}; + else + ext = 'mat'; + end + output = fullfile(job.output.dir{1}, [fn, '.', ext]); return end diff --git a/src/pspm_export.m b/src/pspm_export.m index d8b4286b..9945bf78 100644 --- a/src/pspm_export.m +++ b/src/pspm_export.m @@ -57,7 +57,6 @@ if options.invalid return end - target = options.target; statstype = options.statstype; delim = options.delim; @@ -73,24 +72,25 @@ % check target -- if ~ischar(target) - warning('ID:invalid_input', 'Target must be a char'); - return; + warning('ID:invalid_input', 'Target must be a char'); + return; elseif strcmp(target, 'screen') - fid = 1; + fid = 1; else - % check file extension - [pth, filename, ext]=fileparts(target); - if isempty(ext) - target=fullfile(pth, [filename, '.txt']); - end; - % check whether file exists - if exist(target, 'file') == 2 - overwrite=menu(sprintf('Output file (%s) already exists. Overwrite?', target), 'yes', 'no'); - if overwrite == 2, warning('Nothing written to file.'); return; end; - end; - % open or create file for reading and writing, discard contents - fid = fopen(target, 'w+'); - if fid == -1, warning('Output file (%s) could not be opened.', target); return; end; + % check file extension + [pth, filename, ext]=fileparts(target); + if isempty(ext) + target=fullfile(pth, [filename, '.tsv']); + end + options.overwrite = pspm_overwrite(target, options); + if ~options.overwrite + warning('ID:invalid_input', 'Output file exists, and overwriting not allowed by user.'); + return + end + + % open or create file for reading and writing, discard contents + fid = fopen(target, 'w+'); + if fid == -1, warning('Output file (%s) could not be opened.', target); return; end; end; % check statstype -- diff --git a/src/pspm_load_channel.m b/src/pspm_load_channel.m index bc202bfe..cc6ab75e 100644 --- a/src/pspm_load_channel.m +++ b/src/pspm_load_channel.m @@ -13,7 +13,7 @@ % * channel : [numeric] / [char] / [struct] % ▶ numeric: returns this channel (or the first of a vector) % ▶ char -% 'marker' returns the first maker channel +% 'marker' returns the first marker channel % (see settings for permissible channel types) % any other channel type (e.g. 'scr') % returns the last channel of the respective type From 446e14de04c1a443dd4277743b54a97e105ba0b4 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Mon, 9 Dec 2024 09:53:17 +0100 Subject: [PATCH 22/29] bugfixes, GUI changes, and improved helps --- src/pspm_cfg/pspm_cfg_data_preprocessing.m | 4 +- src/pspm_cfg/pspm_cfg_pp_emg.m | 2 +- src/pspm_cfg/pspm_cfg_pp_general.m | 16 ++++++ src/pspm_cfg/pspm_cfg_preparation.m | 6 ++- src/pspm_cfg/pspm_cfg_run_find_sounds.m | 5 +- src/pspm_cfg/pspm_cfg_run_glm_sebr.m | 2 +- src/pspm_cfg/pspm_cfg_tools.m | 6 +-- src/pspm_check_model.m | 2 +- src/pspm_find_sounds.m | 61 ++++++++++++++++------ src/pspm_glm.m | 5 +- src/pspm_import.m | 13 +++-- src/pspm_options.m | 2 +- 12 files changed, 87 insertions(+), 37 deletions(-) create mode 100644 src/pspm_cfg/pspm_cfg_pp_general.m diff --git a/src/pspm_cfg/pspm_cfg_data_preprocessing.m b/src/pspm_cfg/pspm_cfg_data_preprocessing.m index c877703b..3be77b45 100644 --- a/src/pspm_cfg/pspm_cfg_data_preprocessing.m +++ b/src/pspm_cfg/pspm_cfg_data_preprocessing.m @@ -14,6 +14,8 @@ pspm_cfg_pp_pupil, ... pspm_cfg_pp_cardiac, ... pspm_cfg_resp_pp, ... - pspm_cfg_pp_emg}; + pspm_cfg_pp_emg, ... + pspm_cfg_combine_markerchannels, ... + pspm_cfg_pp_general}; cfg.forcestruct = true; cfg.help = {'Help: Data preprocessing'}; diff --git a/src/pspm_cfg/pspm_cfg_pp_emg.m b/src/pspm_cfg/pspm_cfg_pp_emg.m index 7d3113c7..e38ac1bc 100644 --- a/src/pspm_cfg/pspm_cfg_pp_emg.m +++ b/src/pspm_cfg/pspm_cfg_pp_emg.m @@ -11,6 +11,6 @@ cfg = cfg_repeat; cfg.name = 'Startle EMG'; cfg.tag = 'pp_emg'; -cfg.values = {pspm_cfg_find_sounds, pspm_cfg_emg_pp}; +cfg.values = {pspm_cfg_emg_pp, pspm_cfg_find_sounds}; cfg.forcestruct = true; cfg.help = {'Help: EMG preprocessing'}; diff --git a/src/pspm_cfg/pspm_cfg_pp_general.m b/src/pspm_cfg/pspm_cfg_pp_general.m new file mode 100644 index 00000000..ec4437a6 --- /dev/null +++ b/src/pspm_cfg/pspm_cfg_pp_general.m @@ -0,0 +1,16 @@ +function [cfg] = pspm_cfg_pp_general +% function [cfg] = pspm_cfg_data_preprocessing +% +% Matlabbatch menu for data preprocessing +%__________________________________________________________________________ +% PsPM 3.1 +% (C) 2016 Tobias Moser (University of Zurich) + +%% Data preprocessing +cfg = cfg_repeat; +cfg.name = 'General Preprocessing'; +cfg.tag = 'general_preprocessing'; +cfg.values = {pspm_cfg_filtering, ... + pspm_cfg_interpolate}; +cfg.forcestruct = true; +cfg.help = {'Help: Data preprocessing'}; diff --git a/src/pspm_cfg/pspm_cfg_preparation.m b/src/pspm_cfg/pspm_cfg_preparation.m index d2fa1dcd..0a16bf09 100644 --- a/src/pspm_cfg/pspm_cfg_preparation.m +++ b/src/pspm_cfg/pspm_cfg_preparation.m @@ -8,6 +8,10 @@ cfg = cfg_repeat; cfg.name = 'Data Preparation'; cfg.tag = 'prep'; -cfg.values = {pspm_cfg_import,pspm_cfg_trim,pspm_cfg_combine_markerchannels}; % Values in a cfg_repeat can be any cfg_item objects +cfg.values = {pspm_cfg_import, ... + pspm_cfg_trim, ... + pspm_cfg_split_sessions, ... + pspm_cfg_merge, ... + pspm_cfg_rename}; % Values in a cfg_repeat can be any cfg_item objects cfg.forcestruct = true; cfg.help = {'Help: Data Preparation...'}; diff --git a/src/pspm_cfg/pspm_cfg_run_find_sounds.m b/src/pspm_cfg/pspm_cfg_run_find_sounds.m index 9f33f7d5..5498fe32 100644 --- a/src/pspm_cfg/pspm_cfg_run_find_sounds.m +++ b/src/pspm_cfg/pspm_cfg_run_find_sounds.m @@ -9,6 +9,7 @@ end options = pspm_update_struct(options, job, 'threshold'); if isfield(job.diagnostic, 'diagnostics') + options.diagnostics = 1; d = job.diagnostic.diagnostics; if isfield(d.create_corrected_chan, 'yes') options.channel_output = 'corrected'; @@ -21,9 +22,9 @@ diag_out = fieldnames(d.diag_output); switch diag_out{1} case 'hist_plot' - options.plot = true; + options.plot = 1; case 'text_only' - options.plot = false; + options.plot = 0; end end [~, out, ~] = pspm_find_sounds(file, options); diff --git a/src/pspm_cfg/pspm_cfg_run_glm_sebr.m b/src/pspm_cfg/pspm_cfg_run_glm_sebr.m index 715601d6..861f054b 100644 --- a/src/pspm_cfg/pspm_cfg_run_glm_sebr.m +++ b/src/pspm_cfg/pspm_cfg_run_glm_sebr.m @@ -5,7 +5,7 @@ options = struct(); % set modality -model.modality = 'sebr'; +model.modality = 'emg_pp'; model.modelspec = 'sebr'; % basis function diff --git a/src/pspm_cfg/pspm_cfg_tools.m b/src/pspm_cfg/pspm_cfg_tools.m index b9e97a0e..70cc3069 100644 --- a/src/pspm_cfg/pspm_cfg_tools.m +++ b/src/pspm_cfg/pspm_cfg_tools.m @@ -2,11 +2,7 @@ cfg = cfg_repeat; cfg.name = 'Tools'; cfg.tag = 'tools'; -cfg.values = {pspm_cfg_split_sessions, ... - pspm_cfg_merge, ... - pspm_cfg_filtering, ... - pspm_cfg_interpolate, ... - pspm_cfg_rename, ... +cfg.values = { ... pspm_cfg_extract_segments, ... pspm_cfg_get_markerinfo, ... pspm_cfg_display, ... diff --git a/src/pspm_check_model.m b/src/pspm_check_model.m index 51768a66..98e7f026 100644 --- a/src/pspm_check_model.m +++ b/src/pspm_check_model.m @@ -258,7 +258,7 @@ model = rmfield(model, 'window'); end - if strcmpi(model.latency, 'free') && diff(model.window < 0) + if strcmpi(model.latency, 'free') && diff(model.window) < 0 warning('ID:invalid_input', 'model.window invalid'); end diff --git a/src/pspm_find_sounds.m b/src/pspm_find_sounds.m index 515f7123..12ac9e1a 100644 --- a/src/pspm_find_sounds.m +++ b/src/pspm_find_sounds.m @@ -22,7 +22,7 @@ % │ the last one will be processed. If you want to preprocess several % │ sound in a PsPM file, call this function multiple times with the % │ index of each channel. In this case, set the option -% │ 'channel_action' to 'add', to store each resulting channel +% │ 'channel_action' to 'add', to store each resulting channel % │ separately. % ├.channel_action : ['add'/'replace'] sound events are written as marker channel to the % │ specified pspm file. Onset times then correspond to marker events @@ -31,25 +31,25 @@ % │ replaced (last found marker channel will be overwritten) or % │ whether the new channel should be added at the end of the data % │ file. Default is 'add'. -% ├.channel_output : ['all'/'corrected'; 'corrected' requires enabled -% │ diagnostics, but does not force it (the option will -% │ otherwise not work).] Defines whether all sound -% │ events or only sound events which were related to an -% │ existing marker should be written into the output -% │ marker channel. Default is all sound events. -% ├───.diagnostics : [TRUE/false] Computes the delay between marker and detected sound, displays the -% │ mean delay and standard deviation, and removes sounds which could -% │ not be assigned to an existing marker. +% ├───.diagnostics : [0 (default) or 1] +% │ Computes the delay between marker and detected sound, displays the +% │ mean delay and standard deviation. % ├──────.maxdelay : [number] Upper limit (in seconds) of the window in which % │ pspm_find_sounds will accept sounds as relating to a marker. % │ Default as 3 s. % ├──────.mindelay : [number] Lower limit (in seconds) of the window in which % │ pspm_find_sounds will accept sounds as relating to a marker. % │ Default is 0 s. -% ├──────────.plot : [true/FALSE] Display a histogramm of the delays found and a plot +% ├──────────.plot : [0(default) or 1] Display a histogramm of the delays found and a plot % │ with the detected sound, the trigger and the onset of the sound % │ events. These are color coded for delay, from green (smallest % │ delay) to red (longest). Forces the 'diagnostics' option to true. +% ├.channel_output : ['all'/'corrected'; 'corrected' requires enabled +% │ diagnostics, but does not force it (the option will +% │ otherwise not work).] Defines whether all sound +% │ events or only sound events which were related to an +% │ existing marker should be written into the output +% │ marker channel. Default is all sound events. % ├──────.resample : [integer] Spline interpolates the sound by the factor specified. % │ (1 for no interpolation, by default). Caution must be used when % │ using this option. It should only be used when following @@ -333,8 +333,21 @@ else histogram(delays*1000, 10) end - title('Trigger to sound delays') - xlabel('t [ms]') + set(get(gca, 'title'), ... + 'String', 'Sound onset delay wrt marker', ... + 'FontSize', 18, ... + 'FontWeight', 'Bold'); + set(get(gca, 'xlabel'), ... + 'String', 'Time [ms]', ... + 'FontSize', 15, ... + 'FontWeight', 'Bold'); + set(get(gca, 'ylabel'), ... + 'String', 'Frequency', ... + 'FontSize', 15, ... + 'FontWeight', 'Bold'); + set(gca, ... + 'FontSize', 12, ... + 'FontWeight', 'Bold'); if options.resample % downsample for plot t = t(1:options.resample:end); @@ -351,12 +364,26 @@ plot(t,snd_pres) hold on scatter(mkr.data,ones(size(mkr.data))*.1,'k') + colormap jet for i = 1:length(delays) - scatter(snd_re(i),.2,500,[(delays(i)-min(delays))/range(delays),1-(delays(i)-min(delays))/range(delays),0],'.') + clr = (delays(i)-min(delays))/range(delays); + scatter(snd_re(i),.2,500,clr,'.') end - xlabel('t [s]') - legend('Detected sound','Trigger','Sound onset') - hold off + set(get(gca, 'xlabel'), ... + 'String', 'Time [s]', ... + 'FontSize', 15, ... + 'FontWeight', 'Bold'); + set(get(gca, 'title'), ... + 'String', 'Markers and sound onsets', ... + 'FontSize', 18, ... + 'FontWeight', 'Bold'); + legend('Detected sound','Marker','Sound onset (color-coded delay)'); + colorbar('Ticks', [0, 1], 'TickLabels', {'Min delay', 'Max delay'}); + set(gca, ... + 'YTick', [], ... + 'FontSize', 12, ... + 'FontWeight', 'Bold'); +hold off end %% Return values diff --git a/src/pspm_glm.m b/src/pspm_glm.m index 6e281eb2..ca853e04 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -519,7 +519,8 @@ tmp.regscale{iCond} = 1; % first process event onset, then pmod if strcmpi(model.latency, 'free') - offset = model.window(1); + offset = round(model.window(1) * glm.infos.sr); + model.infos.offset = offset; else offset = 0; end @@ -708,7 +709,7 @@ % obtain inner product and select max a = D * glm.YM; [~, ind] = max(a); - lat(iCol) = ind/glm.infos.sr; + lat(iCol) = (ind+model.infos.offset)/glm.infos.sr; XMnew(:, iCol) = D(ind, :); % create names glm.names{iCol + ncol} = [glm.names{iCol}, ' Latency']; diff --git a/src/pspm_import.m b/src/pspm_import.m index de119359..9ecd5284 100644 --- a/src/pspm_import.m +++ b/src/pspm_import.m @@ -8,12 +8,15 @@ % [sts, outfile] = pspm_import(datafile, datatype, import, options) % ● Arguments % * datafile : [char] file name -% * datatype : supported datatypes are defined in pspm_init (see manual). -% ┌────────────import -% ├─────────────.type : (mandatory for all data types and each job) not all data +% * datatype : supported datatype as defined in pspm_init (see manual). +% * import : A cell array of struct, with one cell per +% channel to be imported. +% ┌────────────import +% ├─────────────.type : [mandatory for all data types and each job] Channel +% │ type as defined in pspm_init (see manual). Not all data % │ types support all channel types. -% ├───────────────.sr : [mandatory for some data types and each channel] sampling rate -% │ for waveforms or time units in second for event channels, in Hz. +% ├───────────────.sr : [mandatory for some data types and each channel] Sampling rate +% │ for waveforms, or time units in second for event channels, in Hz. % ├──────────.channel : [mandatory for some data types and each channel, % │ positive integer; will search if set to 0 and data type allows] % │ Specify where in the data file to find the channel; diff --git a/src/pspm_options.m b/src/pspm_options.m index ec95ca1e..f53d4a6b 100644 --- a/src/pspm_options.m +++ b/src/pspm_options.m @@ -217,7 +217,7 @@ % 2.23 pspm_find_sounds -- options = autofill_channel_action(options, 'add', {'replace'} ); options = autofill(options, 'channel_output', 'all', 'corrected' ); - options = autofill(options, 'diagnostics', 1, 0 ); + options = autofill(options, 'diagnostics', 0, 1 ); options = autofill(options, 'expectedSoundCount', 0, '*Int' ); options = autofill(options, 'maxdelay', 3, '>=', 0 ); options = autofill(options, 'mindelay', 0, '>=', 0 ); From b1b5e8cfdd3ff60839c96d9d6fb7838f6bbe87a1 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Mon, 9 Dec 2024 16:11:02 +0100 Subject: [PATCH 23/29] bufixes and improvements --- src/helper/PsPM_installer.m | 112 +++++++++++++++++++++++++++++++++++ src/pspm_dcm_inv.m | 4 +- src/pspm_doc_gen.m | 1 + src/pspm_extract_segments.m | 2 +- src/pspm_msg.txt | 4 +- test/pspm_find_sounds_test.m | 4 +- 6 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 src/helper/PsPM_installer.m diff --git a/src/helper/PsPM_installer.m b/src/helper/PsPM_installer.m new file mode 100644 index 00000000..e28e71a7 --- /dev/null +++ b/src/helper/PsPM_installer.m @@ -0,0 +1,112 @@ +% This is a convenience PsPM installer. As an alternative, download the +% lastest release from https://github.com/bachlab/PsPM, or clone the +% develop branch. +% Uzay Gökay & Dominik Bach, 2024 + +fprintf('Welcome to the PsPM installer.\n'); +[indx,tf] = listdlg('ListString',{'Latest PsPM release', 'GLM tutorial dataset', 'DCM tutorial dataset'}, ... + 'PromptString', 'Select items to install.'); + +if tf == 0 + fprintf('PsPM installation aborted.\n'); + return; +end + +fprintf('Please select the parent directory for your PsPM installation.\n'); +destinationFolder = uigetdir('','Select parent directory for your PsPM installation'); + +% ------------------------------------------------------------------------- + +if ismember(1, indx) + + % GitHub release URL for PsPM and the desired version + githubReleaseURL = 'https://github.com/bachlab/PsPM/releases/download'; + version = 'v6.1.2'; + packageName = 'PsPM_v6.1.2'; + + % Create the destination folder if it does not exist + if ~exist(destinationFolder, 'dir') + mkdir(destinationFolder); + end + + % Construct the full URL for the specified version and platform (assuming Windows) + matlabPackageURL = sprintf('%s/%s/%s.zip', githubReleaseURL, version, packageName); + + % Download the PsPM package + disp(['Downloading PsPM version ' version '...']); + websave('temp_package.zip', matlabPackageURL); + + % Unzip the contents to the destination folder + disp('Unzipping package...'); + unzip('temp_package.zip', destinationFolder); + + % Clean up: delete the temporary ZIP file + delete('temp_package.zip'); + + disp(['PsPM package ' version ' download and unzip completed.']); + + %%%%%%%%%%%%%%%%%% add path %%%%%%%%%%%%%%%%%%%%%%%%%% + % Add the unzipped PsPM files to the MATLAB search path + addpath(fullfile(destinationFolder, packageName)); + + disp('PsPM added to MATLAB search path.'); +end + + +%%%%%%%%%%% GLM dataset %%%%%%%%%%%%%% +if ismember(2, indx) + % URL for the PsPM GLM tutorial dataset + glmTutorialDatasetURL = 'https://github.com/bachlab/PsPM-tutorial-datasets/releases/download/tutorial-datasets/Tutorial_dataset_GLM.zip'; + glmTutorialDatasetName = 'Tutorial_dataset_GLM'; + + % Destination folder for the tutorial dataset + tutorialDestinationFolder = fullfile(destinationFolder, glmTutorialDatasetName); + + % Create the destination folder if it does not exist + if ~exist(tutorialDestinationFolder, 'dir') + mkdir(tutorialDestinationFolder); + end + + % Download the PsPM tutorial dataset + disp('Downloading GLM tutorial dataset...'); + websave('temp_tutorial_dataset.zip', glmTutorialDatasetURL); + + % Unzip the tutorial dataset to the destination folder + disp('Unzipping GLM tutorial dataset...'); + unzip('temp_tutorial_dataset.zip', tutorialDestinationFolder); + + % Clean up: delete the temporary ZIP file + delete('temp_tutorial_dataset.zip'); + + disp('GLM tutorial dataset download and unzip completed.'); +end + +%%%%%%%%%%%%%%% DCM Dataset %%%%%%%%%%%%%%% +if ismember(3, indx) + % URL for the PsPM DCM tutorial dataset + dcmTutorialDatasetURL = 'https://github.com/bachlab/PsPM-tutorial-datasets/releases/download/tutorial-datasets/Tutorial_dataset_DCM.zip'; + dcmTutorialDatasetName = 'Tutorial_dataset_DCM'; + + % Destination folder for the DCM tutorial dataset + dcmTutorialDestinationFolder = fullfile(destinationFolder, dcmTutorialDatasetName); + + % Create the destination folder if it does not exist + if ~exist(dcmTutorialDestinationFolder, 'dir') + mkdir(dcmTutorialDestinationFolder); + end + + % Download the PsPM DCM tutorial dataset + disp('Downloading DCM tutorial dataset...'); + websave('temp_dcm_tutorial_dataset.zip', dcmTutorialDatasetURL); + + % Unzip the DCM tutorial dataset to the destination folder + disp('Unzipping DCM tutorial dataset...'); + unzip('temp_dcm_tutorial_dataset.zip', dcmTutorialDestinationFolder); + + % Clean up: delete the temporary ZIP file + delete('temp_dcm_tutorial_dataset.zip'); + + disp('DCM tutorial dataset download and unzip completed.'); + +end + diff --git a/src/pspm_dcm_inv.m b/src/pspm_dcm_inv.m index 377f2189..22e29347 100644 --- a/src/pspm_dcm_inv.m +++ b/src/pspm_dcm_inv.m @@ -536,7 +536,9 @@ win = start:stop; else adepth = trlno - trl + 1; - stop = min([floor((sr * (trlstop(end) + 10))), numel(yscr{sn})]); + % for last trial, if possible use at least 10 s of data, and at + % most min ITI. If this is not possible, use all available data. + stop = min([floor((sr * (trlstop(end) + min([miniti, 10])))), numel(yscr{sn})]); win = start:stop; end diff --git a/src/pspm_doc_gen.m b/src/pspm_doc_gen.m index c72203ef..1c34b760 100644 --- a/src/pspm_doc_gen.m +++ b/src/pspm_doc_gen.m @@ -35,6 +35,7 @@ end if ~exist('list_func', 'var') list_func = { ... + 'pspm_import', ... 'pspm_trim', ... 'pspm_split_sessions', ... 'pspm_merge', ... diff --git a/src/pspm_extract_segments.m b/src/pspm_extract_segments.m index 619c39f6..a204419f 100644 --- a/src/pspm_extract_segments.m +++ b/src/pspm_extract_segments.m @@ -249,7 +249,7 @@ end if options.plot - fg = figure('Name', 'Condition mean per subject', 'Visible', 'off'); + fg = figure('Name', 'Condition means and SEM', 'Visible', 'off'); ax = axes('NextPlot', 'add'); set(fg, 'CurrentAxes', ax); diff --git a/src/pspm_msg.txt b/src/pspm_msg.txt index aacc20a4..218ba2cf 100644 --- a/src/pspm_msg.txt +++ b/src/pspm_msg.txt @@ -3,8 +3,8 @@ Welcome to PsPM (Psychophysiological Modelling) Version 7.0 (xx.xx.2025) $ ------------------------------------------ -(c) 2008--2025 -PsPM team +(c) 2008-2025 +The PsPM development team University of Bonn, DE University College London, UK University of Zurich, CH diff --git a/test/pspm_find_sounds_test.m b/test/pspm_find_sounds_test.m index ca2818af..6f1212d0 100644 --- a/test/pspm_find_sounds_test.m +++ b/test/pspm_find_sounds_test.m @@ -39,7 +39,7 @@ function invalid_input(this) % file does not exist 'ID:invalid_input', 'ID:invalid_input', 'ID:invalid_input'}; for i=1:numel(pos_int_fields) for j=1:numel(invalid_values) - o = struct(pos_int_fields{i}, invalid_values{j}); + o = struct(pos_int_fields{i}, invalid_values{j}, 'diagnostics', 1); this.verifyWarning(@() pspm_find_sounds(fn, o), warning_IDs{i, j}); end end @@ -63,7 +63,7 @@ function invalid_input(this) % file does not exist % invalid channel ids out of range channel_fields = {'channel', 'marker_chan_num'}; for i=1:numel(channel_fields) - o = struct(channel_fields{i}, 5); + o = struct(channel_fields{i}, 5, 'diagnostics', 1); this.verifyWarning(@() pspm_find_sounds(fn, o), 'ID:invalid_input'); end % test with diagnostics and no marker channel in data From 93f44bfa93ac5d95d8a24f2b8ba4e5182ce560ab Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Tue, 10 Dec 2024 15:30:16 +0100 Subject: [PATCH 24/29] update model review plots --- src/pspm_extract_segments.m | 16 ++++++++++++---- src/pspm_rev_dcm.m | 38 +------------------------------------ src/pspm_rev_glm.m | 36 ++--------------------------------- 3 files changed, 15 insertions(+), 75 deletions(-) diff --git a/src/pspm_extract_segments.m b/src/pspm_extract_segments.m index a204419f..1f65bc53 100644 --- a/src/pspm_extract_segments.m +++ b/src/pspm_extract_segments.m @@ -284,9 +284,18 @@ set(p(2), 'Color', color); set(p(3), 'Color', color); - legend_lb{(c-1)*3 + 1} = [names{c} ' AVG']; - legend_lb{(c-1)*3 + 2} = [names{c} ' SEM+']; - legend_lb{(c-1)*3 + 3} = [names{c} ' SEM-']; + legend_lb{(c-1)*3 + 1} = [names{c} ' condition mean']; + legend_lb{(c-1)*3 + 2} = [names{c} ' mean+SEM']; + legend_lb{(c-1)*3 + 3} = [names{c} ' mean-SEM']; + + f.lg = legend('String', legend_lb, 'Interpreter', 'none', 'Location', 'best'); + legend boxoff + + set(ax, 'FontSize', 12, 'FontWeight', 'Bold'); + set(get(ax, 'xlabel'), 'String', 'Time (seconds)', 'FontSize', 15, 'FontWeight', 'Bold'); + set(get(ax, 'ylabel'), 'String', 'Mean Response (data units)', 'FontSize', 15, 'FontWeight', 'Bold'); + set(get(ax, 'title'), 'String', 'Mean Responses for All Segments', 'FontSize', 18, 'FontWeight', 'Bold'); + end end @@ -360,7 +369,6 @@ if options.plot % show plot set(fg, 'Visible', 'on'); - legend(legend_lb); end %% Return values diff --git a/src/pspm_rev_dcm.m b/src/pspm_rev_dcm.m index 376f18b3..44a35891 100644 --- a/src/pspm_rev_dcm.m +++ b/src/pspm_rev_dcm.m @@ -129,45 +129,9 @@ fprintf('---------------------------------------\n'); case 'seg' - options = struct(); + options = struct('plot', 1); [ssts, segments] = pspm_extract_segments('model', dcm, options); - if ssts == -1 - uiwait(msgbox('Error extracting segments from the model.', 'Error')) - else - - - sr = dcm.input.sr; - cmap = lines(numel(segments.segments)); - f.h = figure; - f.a.h = axes(f.h); - hold on; - - legendNames = cell(1, numel(segments.segments)); - - for x = 1:numel(segments.segments) - - plotdata = segments.segments{x}.mean; - t = (1:length(plotdata)) / sr; - f.a.p = plot(f.a.h, t, plotdata, 'Color', cmap(x, :), 'LineWidth', 1); - legendNames{x} = segments.segments{x}.name; - - end - - f.a.l = legend(legendNames, 'Interpreter', 'none', 'Location', 'best'); - legend boxoff - - set(get(f.a.h, 'xlabel'), 'String', 'Time (seconds)'); - set(get(f.a.h, 'ylabel'), 'String', 'Mean Response (data units)'); - set(get(f.a.h, 'title'), 'String', 'Mean Responses for All Segments'); - - hold off; - end - - - - - end; sts = 1; diff --git a/src/pspm_rev_glm.m b/src/pspm_rev_glm.m index 36b8e1f2..dc4e693a 100644 --- a/src/pspm_rev_glm.m +++ b/src/pspm_rev_glm.m @@ -230,41 +230,9 @@ set(fig(5).title, 'String', sprintf('Estimated responses per condition: %s', filename), 'FontWeight', 'Bold', 'FontSize', 14, 'Interpreter', 'none'); case 6 - options = struct(); + options = struct('plot', 1); [ssts, segments] = pspm_extract_segments('model', glm, options); - if ssts == -1 - uiwait(msgbox('Error extracting segments from the model.', 'Error')) - else - sr = glm.input.sr; - cmap = lines(numel(segments.segments)); - f.h = figure; - f.a.h = axes(f.h); - hold on; - - legendNames = cell(1, numel(segments.segments)); - - for x = 1:numel(segments.segments) - - plotdata = segments.segments{x}.mean; - t = (1:length(plotdata)) / sr; - f.a.p = plot(f.a.h, t, plotdata, 'Color', cmap(x, :), 'LineWidth', 1); - legendNames{x} = segments.segments{x}.name; - - end - - f.a.l = legend(legendNames, 'Interpreter', 'none', 'Location', 'best'); - legend boxoff - - set(get(f.a.h, 'xlabel'), 'String', 'Time (seconds)'); - set(get(f.a.h, 'ylabel'), 'String', 'Mean Response (data units)'); - set(get(f.a.h, 'title'), 'String', 'Mean Responses for All Segments'); - - hold off; - end - - - - end + end end end sts = 1; From a78397ba6071f5082347203148ca1f3fb86a0c56 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Tue, 10 Dec 2024 21:17:19 +0100 Subject: [PATCH 25/29] update HPRF_rew based on exps1-2 in Xia et al. 2024 --- src/pspm_bf_hprf_rew.m | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/pspm_bf_hprf_rew.m b/src/pspm_bf_hprf_rew.m index e885431b..1e9f8681 100644 --- a/src/pspm_bf_hprf_rew.m +++ b/src/pspm_bf_hprf_rew.m @@ -1,15 +1,17 @@ function [bs, x] = pspm_bf_hprf_rew(td) % ● Description % This function implements a canonical gamma response function for -% reward-conditioned heart period responses. +% reward-conditioned heart period responses, based on exps 1-2 in Xia*, +% Liu*, et al. (2024). % ● Format % [bs, x] = pspm_bf_hprf_fc(TD) % ● Arguments % * td : time resolution in second. % ● References: % GLM for reward-conditioned bradycardia: -% Xia Y, Liu H, Kälin OK, Gerster S, Bach DR (under review). Measuring +% Xia Y, Liu H, Kälin OK, Gerster S, Bach DR (2024). Measuring % human Pavlovian appetitive conditioning and memory retention. +% Pre-print. % ● History % Introduced in PsPM 7.0 % Written in 2021 by Oliver Keats Kälin and Yanfang Xia (University of Zurich) @@ -28,7 +30,8 @@ % default values duration = 30; % k, theta, c, t0 -p_cs = [1.716239999852250e+02,0.140004209328788,60.095121886556230,-17.607312178043863]; +% p_cs = [1.716239999852250e+02,0.140004209328788,60.095121886556230,-17.607312178043863]; % generated from REW1 data +p_cs = [73.9977984802746, 0.210014413426350, 71.1984486930105, -9.19655727625912]; % generated from the combined data set of REW1 and REW2 x = (0:td:duration-td)'; bs = zeros(numel(x), 1); @@ -51,3 +54,6 @@ elseif t0 < 0 bs(1:sto, 1) = g_cs(sta:end); end + +% normalise +bs = bs/max(bs); From 8729a4cbb270ec3be38a52adf506ac71893c20cb Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Wed, 11 Dec 2024 13:51:38 +0100 Subject: [PATCH 26/29] remove outdated bf --- src/pspm_bf_hprf_f.m | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 src/pspm_bf_hprf_f.m diff --git a/src/pspm_bf_hprf_f.m b/src/pspm_bf_hprf_f.m deleted file mode 100644 index e5a8da27..00000000 --- a/src/pspm_bf_hprf_f.m +++ /dev/null @@ -1,41 +0,0 @@ -function [bs, x] = pspm_bf_hprf_f( td, soa ) -% ● Description -% pspm_bf_hprf_f is the basis function dependent on stimuli onset asynchrony (SOA). -% ● Format -% [bf p] = pspm_bf_hprf_f(td, soa) -% ● Arguments -% * td : time resolution in second. -% * soa : stimuli onset asynchrony. -% ● History -% Introduced in PsPM 4.0 - -%% initialise -global settings -if isempty(settings), pspm_init; end; -%% check input arguments -if nargin < 1 - errmsg='No sampling interval stated'; warning('ID:invalid_input',errmsg); return; -elseif nargin < 2 - soa = 3.5; -end -d = 30; -start = 0; -stop = d + soa; -if td > (stop-start) - warning('ID:invalid_input', 'Time resolution is larger than duration of the function.'); return; -elseif td == 0 - warning('ID:invalid_input', 'Time resolution must be larger than 0.'); return; -elseif soa < 2 - soa = 2; - stop = d + soa; - warning('Changing SOA to 2s to avoid implausible values (<2s).'); -elseif soa > 8 - warning(['SOA longer than 8s is not recommended. ', ... - 'Use at own risk.']); -end -%% Perform operation -x = (start:td:stop-td)'; -% das stimmt nicht --> verstehe nicht wie ich anfang und ende des intervals -% bekomme -bs = rectangularPulse(start, stop,x); -return From 5f01fe04b0f741c8ff298141251a954e939c2f25 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Sat, 14 Dec 2024 13:16:05 +0100 Subject: [PATCH 27/29] update heartpy reference in function help --- src/pspm_convert_ppg2hb.m | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/pspm_convert_ppg2hb.m b/src/pspm_convert_ppg2hb.m index eadc6b3f..c9c21509 100644 --- a/src/pspm_convert_ppg2hb.m +++ b/src/pspm_convert_ppg2hb.m @@ -1,9 +1,11 @@ function [ sts, outchannel ] = pspm_convert_ppg2hb( fn , options ) % ● Description % pspm_convert_ppg2hb converts a pulse oxymeter channel to heartbeats. -% First a template is generated from non-ambiguous heartbeats. The ppg -% signal is then cross-correlated with the template and maxima are -% identified as heartbeats. +% Two methods are available: (1) Template-matching algorithm (method +% "classic"): First a template is generated from non-ambiguous +% heartbeats. The ppg signal is then cross-correlated with the template +% and maxima are identified as heartbeats. (2) HeartPy (see reference +% [1], requires Python installation. % ● Format % [sts, channel_index] = pspm_convert_ppg2hb( fn, options ) % ● Arguments @@ -42,6 +44,11 @@ % python environment is not yet set up % ● Output % * channel_index: index of channel containing the processed data +% ● References +% [1] van Gent, P, Farah, H, van Nes, N, & van Arem, B. (2019) Heartpy: +% A novel heart rate algorithm for the analysis of noisy signals. +% Transportation Research Part F: Traffic Psychology and Behaviour 66, +% 368–378. % ● History % Introduced in PsPM 3.1 % Written in 2016 by Samuel Gerster (University of Zurich) From aec7c03d77266982522c0f8a4096183d386f15bf Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Mon, 23 Dec 2024 10:39:14 +0100 Subject: [PATCH 28/29] unify missing epochs selector and fix overwrite settings in SF --- src/pspm_cfg/pspm_cfg_run_sf.m | 11 ++--- src/pspm_cfg/pspm_cfg_selector_data_design.m | 35 +------------- .../pspm_cfg_selector_missing_epochs.m | 47 +++++++++++++++++++ src/pspm_cfg/pspm_cfg_sf.m | 37 ++------------- src/pspm_glm.m | 10 ++-- src/pspm_sf.m | 1 - 6 files changed, 62 insertions(+), 79 deletions(-) create mode 100644 src/pspm_cfg/pspm_cfg_selector_missing_epochs.m diff --git a/src/pspm_cfg/pspm_cfg_run_sf.m b/src/pspm_cfg/pspm_cfg_run_sf.m index 60bce9c2..007dd6b7 100644 --- a/src/pspm_cfg/pspm_cfg_run_sf.m +++ b/src/pspm_cfg/pspm_cfg_run_sf.m @@ -28,9 +28,13 @@ if ~isfield(job.filter,'def') model.filter = pspm_cfg_selector_filter('run', job.filter); end +% channel number if isfield(job.chan, 'chan_nr') model.channel = job.chan.chan_nr; end +% missing +model.missing = pspm_cfg_selector_missing_epochs('run', job); +% options if strcmp(timeunits, 'markers') options.marker_chan_num = pspm_cfg_selector_channel('run', job.timeunits.markers.chan); end @@ -40,15 +44,10 @@ if ~isempty(job.fresp) options.fresp = job.fresp; end -if ~isempty(job.missing) && isfield(job.missing, 'missingepoch_include') - if ischar(job.missing.missingepoch_include.missingepoch_file{1}) - model.missing = job.missing.missingepoch_include.missingepoch_file{1}; - end -end options = pspm_update_struct(options, job, {'dispwin', ... 'dispsmallwin', ... - 'overwrite', ... 'threshold'}); +options = pspm_update_struct(options, job.output, {'overwrite'}); pspm_sf(model, options); out = {model.modelfile}; diff --git a/src/pspm_cfg/pspm_cfg_selector_data_design.m b/src/pspm_cfg/pspm_cfg_selector_data_design.m index 19b91dc8..585b792f 100644 --- a/src/pspm_cfg/pspm_cfg_selector_data_design.m +++ b/src/pspm_cfg/pspm_cfg_selector_data_design.m @@ -17,11 +17,7 @@ % datafile model.datafile{iSession,1} = job.session(iSession).datafile{1}; % missing epochs - if isfield(job.session(iSession).missing,'epochfile') - model.missing{1,iSession} = job.session(iSession).missing.epochfile{1}; - elseif isfield(job.session(iSession).missing,'epochentry') - model.missing{1,iSession} = job.session(iSession).missing.epochentry; - end + model.missing{1,iSession} = pspm_cfg_selector_missing_epochs('run', job.session(iSession)); % data & design if isfield(job.session(iSession).data_design,'no_condition') model.timing = {}; @@ -91,34 +87,7 @@ % standard items datafile = pspm_cfg_selector_datafile(); -epochfile = pspm_cfg_selector_datafile('epochs'); - -% Missing epochs -no_epochs = cfg_const; -no_epochs.name = 'No Missing Epochs'; -no_epochs.tag = 'no_epochs'; -no_epochs.val = {0}; -no_epochs.help = {'The whole time series will be analyzed.'}; - -epochentry = cfg_entry; -epochentry.name = 'Enter Missing Epochs Manually'; -epochentry.tag = 'epochentry'; -epochentry.strtype = 'i'; -epochentry.num = [Inf 2]; -epochentry.help = {'Enter the start and end points of missing epochs (m) manually.', ... - ['Specify an m x 2 array, where m is the number of missing epochs. The first column marks the ' ... - 'start points of the epochs that are excluded from the analysis and the second column the end points.']}; - -missing = cfg_choice; -missing.name = 'Missing Epochs'; -missing.tag = 'missing'; -missing.val = {no_epochs}; -missing.values = {no_epochs, epochfile, epochentry}; -missing.help = {['Indicate epochs in your data in which the ', ... - ' signal is missing or corrupted (e.g., due to artifacts). Specified missing epochs, as well as NaN values ', ... - 'in the signal, will be interpolated for filtering and downsampling ', ... - 'and later automatically removed from data and design matrix. Epoch start and end points ' ... - 'have to be defined in seconds with respect to the beginning of the session.']}; +missing = pspm_cfg_selector_missing_epochs; % Condition file condfile = cfg_files; diff --git a/src/pspm_cfg/pspm_cfg_selector_missing_epochs.m b/src/pspm_cfg/pspm_cfg_selector_missing_epochs.m new file mode 100644 index 00000000..4e8dbd9c --- /dev/null +++ b/src/pspm_cfg/pspm_cfg_selector_missing_epochs.m @@ -0,0 +1,47 @@ +function missing = pspm_cfg_selector_missing_epochs(varargin) + +% run mode ---------------------------------------------------------------- +if nargin > 1 && strcmpi(varargin{1}, 'run') + job = varargin{2}; + missing = []; + if isfield(job, 'missing') + if isfield(job.missing,'epochfile') + missing = job.missing.epochfile{1}; + elseif isfield(job.missing,'epochentry') + missing = job.missing.epochentry; + end + end + return +end + +% - selector mode --------------------------------------------------------- + +% standard item +epochfile = pspm_cfg_selector_datafile('epochs'); + +% Missing epochs +no_epochs = cfg_const; +no_epochs.name = 'No Missing Epochs'; +no_epochs.tag = 'no_epochs'; +no_epochs.val = {0}; +no_epochs.help = {'The whole time series will be analyzed.'}; + +epochentry = cfg_entry; +epochentry.name = 'Enter Missing Epochs Manually'; +epochentry.tag = 'epochentry'; +epochentry.strtype = 'i'; +epochentry.num = [Inf 2]; +epochentry.help = {'Enter the start and end points of missing epochs (m) manually.', ... + ['Specify an m x 2 array, where m is the number of missing epochs. The first column marks the ' ... + 'start points of the epochs that are excluded from the analysis and the second column the end points.']}; + +missing = cfg_choice; +missing.name = 'Missing Epochs'; +missing.tag = 'missing'; +missing.val = {no_epochs}; +missing.values = {no_epochs, epochfile, epochentry}; +missing.help = {['Indicate epochs in your data in which the ', ... + ' signal is missing or corrupted (e.g., due to artifacts). Specified missing epochs, as well as NaN values ', ... + 'in the signal, will be interpolated for filtering and downsampling ', ... + 'and later automatically removed from data and design matrix. Epoch start and end points ' ... + 'have to be defined in seconds with respect to the beginning of the session.']}; \ No newline at end of file diff --git a/src/pspm_cfg/pspm_cfg_sf.m b/src/pspm_cfg/pspm_cfg_sf.m index b06407cb..c732a7e0 100644 --- a/src/pspm_cfg/pspm_cfg_sf.m +++ b/src/pspm_cfg/pspm_cfg_sf.m @@ -8,6 +8,9 @@ channel = pspm_cfg_selector_channel('SCR'); output = pspm_cfg_selector_outputfile('Model'); filter = pspm_cfg_selector_filter(settings.dcm{2}); +epochfile = pspm_cfg_selector_datafile('epochs'); +missing = pspm_cfg_selector_missing_epochs(); + % (see below for timeunits, requires specification of epochs item first) %% Specific items @@ -21,13 +24,6 @@ %% Epochs -epochfile = cfg_files; -epochfile.name = 'Epoch File'; -epochfile.tag = 'epochfile'; -epochfile.num = [1 1]; -epochfile.filter = '.*\.(mat|MAT|txt|TXT)$'; -epochfile.help = {''}; - epochentry = cfg_entry; epochentry.name = 'Enter Epochs Manually'; epochentry.tag = 'epochentry'; @@ -68,33 +64,6 @@ fresp.hidden = true; - -missingepoch_file = cfg_files; -missingepoch_file.name = 'Missing epoch file'; -missingepoch_file.tag = 'missingepoch_file'; -missingepoch_file.num = [1 1]; -missingepoch_file.filter = '.*\.(mat|MAT)$'; -missingepoch_file.help = {}; - -missingepoch_none = cfg_const; -missingepoch_none.name = 'Do not add'; -missingepoch_none.tag = 'missingepoch_none'; -missingepoch_none.val = {0}; -missingepoch_none.help = {}; - -missingepoch_include = cfg_branch; -missingepoch_include.name = 'Add'; -missingepoch_include.tag = 'missingepoch_include'; -missingepoch_include.val = {missingepoch_file}; -missingepoch_include.help = {}; - -missing = cfg_choice; -missing.name = 'Missing Epoch Settings'; -missing.tag = 'missing'; -missing.val = {missingepoch_none}; -missing.values = {missingepoch_none, missingepoch_include}; -missing.help = pspm_cfg_help_format('pspm_sf', 'model.missing'); - % Show figures dispwin = cfg_menu; dispwin.name = 'Display Progress Window'; diff --git a/src/pspm_glm.m b/src/pspm_glm.m index ca853e04..0e81f064 100644 --- a/src/pspm_glm.m +++ b/src/pspm_glm.m @@ -306,11 +306,11 @@ %% 8 check & get missing values for iSn = 1:nFile -if isempty(model.missing{iSn}) - sts = 1; missing{iSn} = []; -else - [sts, missing{iSn}] = pspm_get_timing('missing', model.missing{iSn}, 'seconds'); -end + if isempty(model.missing{iSn}) + sts = 1; missing{iSn} = []; + else + [sts, missing{iSn}] = pspm_get_timing('missing', model.missing{iSn}, 'seconds'); + end end %% 9 check and get nuisance regressors diff --git a/src/pspm_sf.m b/src/pspm_sf.m index e4a22d85..628fb36e 100644 --- a/src/pspm_sf.m +++ b/src/pspm_sf.m @@ -117,7 +117,6 @@ % 2.4 check files % stop the script if files are not allowed to overwrite if ~pspm_overwrite(model.modelfile, options) - warning('ID:invalid_input', 'Model file exists, and overwriting not allowed by user.'); return end From 9f01ebf8fbc7524c82eb59b377603e6b5e70c353 Mon Sep 17 00:00:00 2001 From: Dominik Bach Date: Tue, 7 Jan 2025 11:21:21 +0100 Subject: [PATCH 29/29] adapt variable names in test of find_valid_fixations --- test/pspm_find_valid_fixations_test.m | 46 +++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/test/pspm_find_valid_fixations_test.m b/test/pspm_find_valid_fixations_test.m index 76a87110..e85d4a66 100644 --- a/test/pspm_find_valid_fixations_test.m +++ b/test/pspm_find_valid_fixations_test.m @@ -122,7 +122,7 @@ function test_work_chans(this, work_chans) % this is to generate channel_l and channel_r, not channel_lr! options = struct(); d = vertcat(degs{:}); - box_degree = d(strcmpi({d.name}, 'some')).deg; + circle_degree = d(strcmpi({d.name}, 'some')).deg; dist = this.distance{1}; dist_unit = this.unit{1}; options.resolution = [1280 1024]; @@ -131,7 +131,7 @@ function test_work_chans(this, work_chans) [~,~, o_data] = pspm_load_data(fn); options.channel = work_chans; [sts, ~] = this.verifyWarningFree(@() ... - pspm_find_valid_fixations(fn, box_degree, dist,dist_unit,options)); + pspm_find_valid_fixations(fn, circle_degree, dist,dist_unit,options)); this.verifyEqual(sts, 1); [~,~, n_data] = pspm_load_data(fn); n_new_chans = numel(n_data); @@ -153,7 +153,7 @@ function test_missing(this, missing) [degs,~] = this.generate_fixation_data(fn, this.distance{1}, 'lr'); options = struct(); d = vertcat(degs{:}); - box_degree = d(strcmpi({d.name}, 'some')).deg; + circle_degree = d(strcmpi({d.name}, 'some')).deg; dist = this.distance{1}; dist_unit = this.unit{1}; options.resolution = [1280 1024]; @@ -161,7 +161,7 @@ function test_missing(this, missing) options.add_invalid = missing; options.channel_action = 'add'; [sts, ~] = this.verifyWarningFree(@() ... - pspm_find_valid_fixations(fn, box_degree, dist,dist_unit,options)); + pspm_find_valid_fixations(fn, circle_degree, dist,dist_unit,options)); this.verifyEqual(sts, 1); [~, ~, n_data] = pspm_load_data(fn); % look for channels with 'missing' in chantype @@ -181,7 +181,7 @@ function test_chan_action(this, channel_action) [degs,~] = this.generate_fixation_data(fn, this.distance{1}, 'lr'); options = struct(); d = vertcat(degs{:}); - box_degree = d(strcmpi({d.name}, 'some')).deg; + circle_degree = d(strcmpi({d.name}, 'some')).deg; dist = this.distance{1}; dist_unit = this.unit{1}; options.resolution = [1280 1024]; @@ -190,7 +190,7 @@ function test_chan_action(this, channel_action) options.channel_action = channel_action; [~, ~, o_data] = pspm_load_data(fn); [sts, ~] = this.verifyWarningFree(@() ... - pspm_find_valid_fixations(fn, box_degree, dist, dist_unit,options)); + pspm_find_valid_fixations(fn, circle_degree, dist, dist_unit,options)); this.verifyEqual(sts, 1); [~, ~, n_data] = pspm_load_data(fn); switch channel_action @@ -212,7 +212,7 @@ function test_gaze_validation(this, distance, ... copyfile(fn, testfn); this.datafiles{end+1} = testfn; d = degs{i}; - box_degree = d.deg; + circle_degree = d.deg; dist = distance; dist_unit = this.unit{1}; options.resolution = resolution; @@ -225,11 +225,11 @@ function test_gaze_validation(this, distance, ... end if d.expect == 1 [~, outfile] = this.verifyWarning(@() ... - pspm_find_valid_fixations(testfn, box_degree, dist, dist_unit, options), ... + pspm_find_valid_fixations(testfn, circle_degree, dist, dist_unit, options), ... 'ID:invalid_input'); else [sts, ~] = this.verifyWarningFree(@() ... - pspm_find_valid_fixations(testfn, box_degree, dist, dist_unit, options)); + pspm_find_valid_fixations(testfn, circle_degree, dist, dist_unit, options)); this.verifyEqual(sts, 1); end [~, ~, data] = pspm_load_data(testfn); @@ -307,55 +307,55 @@ function invalid_input(this) % generate data fn = pspm_find_free_fn(this.testfile_prefix, '.mat'); this.generate_fixation_data(fn, 500, 'lr'); - box_degree = 'a'; + circle_degree = 'a'; dist = '1'; options = []; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist, options), 'ID:invalid_input'); - box_degree = 1; + circle_degree = 1; dist = 'a'; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist, options), 'ID:invalid_input'); dist = 1; dist_unit = 5; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist,dist_unit,options), 'ID:invalid_input'); dist_unit = 'cm'; options2 = [1,2]; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist,dist_unit,options2), 'ID:invalid_input'); % check bitmap option bitmap = 'Hello World!'; this.verifyWarning(@() pspm_find_valid_fixations(fn, bitmap, ... options), 'ID:invalid_input'); options.resolution = 1; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist, dist_unit,options), 'ID:invalid_input'); options.screen_settings.resolution = [1280 1024]; options.fixation_point = 'a'; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist, dist_unit, options), 'ID:invalid_input'); options.fixation_point = [100 500]; options.channel_action = 'bla'; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist, dist_unit, options), 'ID:invalid_input'); options.channel_action = 'add'; options.newfile = 0; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist, dist_unit, options), 'ID:invalid_input'); options.newfile = 'abc'; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist, dist_unit, options), 'ID:invalid_input'); options.invalid = 'abc'; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist, dist_unit, options), 'ID:invalid_input'); options.add_invalid = 0; options.eyes = 'abc'; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist, dist_unit, options), 'ID:invalid_input'); options.eyes = 'combined'; options.channel = 'abc'; - this.verifyWarning(@() pspm_find_valid_fixations(fn, box_degree, ... + this.verifyWarning(@() pspm_find_valid_fixations(fn, circle_degree, ... dist, dist_unit, options), 'ID:invalid_input'); end end