Skip to content

Commit 62491c1

Browse files
committed
Fix TAM sample timing and fitting time grids
Convert sample onsets and extraction windows to seconds. Validate common window durations and effective sample rates. Rebuild time axes and fitting grids from the effective sample rate. Reject unavailable baseline samples and store duration in seconds. Keep the optimizer exit flag separate from the overall status.
1 parent ace5414 commit 62491c1

1 file changed

Lines changed: 48 additions & 14 deletions

File tree

‎src/pspm_tam.m‎

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,25 @@
190190
else
191191
window_seconds = model.window;
192192
end
193-
194193
if any(model.baseline >= window_seconds) % Reject a baseline outside the window in any session.
195194
warning('ID:invalid_input', 'Baseline must be smaller than the window duration in seconds.');
196195
return;
197196
end
198197

198+
% Check for inconsistent window durations across sessions.
199+
if any(abs(window_seconds - window_seconds(1)) > 1e-10)
200+
warning('ID:invalid_input', 'Sessions must have the same window duration in seconds.');
201+
return;
202+
end
203+
204+
% Convert one-based sample onsets to seconds for each session.
205+
if strcmp(model.timeunits, 'samples')
206+
for k = 1:n_file
207+
timing{k}.onsets = cellfun( @(onsets) (onsets - 1) / oldsr(k), timing{k}.onsets, 'UniformOutput', false);
208+
end
209+
end
210+
211+
199212
% Checking if the sampling rate is the same for all samples.
200213
if n_file > 1 && any(diff(sr) ~= 0) % not any(diff(sr) > 0)
201214
if (ischar(model.filter.down) && strcmpi(model.filter.down, 'none')) || ... % if filter.down is none
@@ -224,10 +237,21 @@
224237
% temporary structure which is deleted after extracting segments
225238
extrsgopt.timeunits = model.timeunits;
226239
extrsgopt.length = model.window; % segments of 'model.window' time unit long
240+
241+
% Use seconds for sample onsets that have already been converted.
242+
if strcmp(model.timeunits, 'samples')
243+
extrsgopt.timeunits = 'seconds';
244+
end
245+
227246
extrsgopt.plot = 0; % do not plot mean value and std
228247
extrsgopt.norm = model.norm;
229248

230249
for k=1:n_file
250+
% Pass the converted window duration for this session.
251+
if strcmp(model.timeunits, 'samples')
252+
extrsgopt.length = window_seconds(k);
253+
end
254+
231255
if strcmpi(model.timeunits, 'markers')
232256
% marker channel for this session
233257
if iscell(options.marker_chan)
@@ -281,7 +305,7 @@
281305
for i = 1:n_exp_cond
282306
for k = 1:n_file
283307

284-
if isempty(segm{k,i}); continue; end % eg. missing condition!
308+
if isempty(segm{k,i}); continue; end % e.g. missing condition!
285309

286310
model.filter.sr = sr(k); % adds the corresponding sr to the filer
287311

@@ -290,13 +314,18 @@
290314
[lsts, segm{k,i}.(field), new_sr(k)] = pspm_prepdata(segm{k,i}.(field), model.filter);
291315
if lsts < 1; warning('ID:error_prepdata', 'An error occured in pspm_prepdata.'); return; end
292316
end
293-
[llsts, segm{k,i}.t , new_sr(k) ] = pspm_downsample(segm{k,i}.t, sr(k), new_sr(k) ); % does not need to be filtered!
294-
if llsts < 1; warning('ID:error_downsample', 'An error occured in pspm_downsample.'); return; end
295-
317+
318+
% Rebuild the time axis from the effective sample rate.
319+
segm{k,i}.t = (1:numel(segm{k,i}.mean))' / new_sr(k);
296320
end
297321
end
298322

299-
sr = new_sr; % maybe test upstream if all new_sr are the same!
323+
% Check for inconsistent effective sample rates across sessions.
324+
if any(abs(new_sr - new_sr(1)) > 1e-10)
325+
warning('ID:invalid_input', 'Sessions must have a common effective sample rate.');
326+
return;
327+
end
328+
sr = new_sr;
300329
% % changing the sampling rate
301330
% if isnumeric(model.filter.down)
302331
% sr = model.filter.down * ones(size(new_sr)); % needs to be tested why not the values of prepdata?
@@ -342,6 +371,9 @@
342371
end
343372

344373
% Baselining data
374+
if baseline_index > numel(tmp_data_new.data) || ~isfinite(tmp_data_new.data(baseline_index))
375+
warning('ID:invalid_input', 'Baseline sample must be observed and inside the extracted window.'); return;
376+
end
345377
tmp_data_new.data = tmp_data_new.data - tmp_data_new.data(baseline_index);
346378

347379
% Dividing by the max value
@@ -364,8 +396,10 @@
364396
for i=1:n_exp_cond
365397
raw_y = mean_data{1,i}.data;
366398

367-
n = model.window;
368-
td = n / length(raw_y);
399+
% Use the effective sample rate to define the fitting time grid.
400+
td = 1 / sr(1); % seconds per sample
401+
n = numel(raw_y) * td; % length of the window in seconds
402+
369403

370404
% Extending the size of the data vector in order to do the fitting,
371405
% because if size(raw_y)=[n 1], the convolution would produce a vector of
@@ -380,19 +414,19 @@
380414

381415
% Minimization of RSS
382416
warning off all
383-
[~, fitted{1,i}.optargs, fitted{1,i}.fval, sts, fmincon_output] = ...
417+
[~, fitted{1,i}.optargs, fitted{1,i}.fval, fsts, fmincon_output] = ...
384418
evalc('fmincon(RSS,model.if.args,[],[],[],[],model.if.lb,model.if.ub)'); % lb ub for opt. why not Nelder–Mead / fminsearch.!!?? like in the paper?
385419
warning on all
386-
if sts == 0
420+
if fsts == 0
387421
warning('ID:fmincon',['During the fitting process, ''fmincon'' exceeded', ...
388422
' the number of iterations or the number of function evaluations.', ...
389423
' Try to change the initial arguments and bounds to improve the fitting.'])
390-
elseif sts == -1
424+
elseif fsts == -1
391425
warning('ID:fmincon',['During the fitting process, ''fmincon''', ...
392426
' was terminated by an output function or a plot.']);
393427
fprintf('Here is the output of fmincon:\n');
394428
disp(fmincon_output);
395-
elseif sts == -2
429+
elseif fsts == -2
396430
warning('ID:fmincon',['During the fitting process, ''fmincon''', ...
397431
' hasn''t found any feasible point.']);
398432
fprintf('Here is the output of fmincon:\n');
@@ -450,8 +484,8 @@
450484
tam.fit.args = {tmp_fitted.optargs};
451485
tam.fit.sr = num2cell(sr(:).');
452486

453-
tam.infos.duration = model.window;
454-
tam.infos.durationinfo = 'duration in seconds'; % not always true! -> timeunits='samples'
487+
tam.infos.duration = window_seconds(1);
488+
tam.infos.durationinfo = 'duration in seconds';
455489

456490
tam.timing = model.timing;
457491

0 commit comments

Comments
 (0)