... 5003
+%
+% Since the number and content of actual info regions and number of data
+% channels differs by trace modality and logfile version, we have to cut
+% data out carefully:
+%
+% 1. Cut away header
+% 2. Cut away logfile version
+% 3. Cut away optional training trace
+% 4. Remove all infoRegions can be interleaved with trace data, e.g.,
+% cushionGain for RESP trace)
+% 5. Remove all trigger markers (5000, 6000), but remmember position
+% 6. Sort remaining trace into coresponding channels (number of channels is
+% logfile-version dependent)
+% 7. Re-insert trigger markers as extra column
-if ~isempty(iTrigger)
- % crop string after trigger
- lineData = lineData((iTrigger(end)+6):end);
- doCropLater = false;
-else
- % crop first 4 values as in UPenn manual after conversion
- doCropLater = true;
-end
-data = textscan(lineData, '%d', 'Delimiter', ' ', 'MultipleDelimsAsOne',1);
-if doCropLater
- % crop first 4 values;
- data{1} = data{1}(5:end);
+%% 1. Header goes from start of line to first occurence of 5002
+[iStartHeader, iEndHeader, logHeader] = regexp(lineData, '^(.*?) (?=\<5002\>)', 'start', 'end', 'tokens' );
+logHeader = logHeader{1}{1};
+lineData(iStartHeader:iEndHeader) = []; % remove header, but keep 5002 for next step
+
+
+%% 2. Logfile version (which alters no of channels etc.)
+% stored in first 5002/6002 info region
+% 5002 LOGVERSION 1 6002%
+% 5002 LOGVERSION_RESP 3 6002%
+% 5002 LOGVERSION_PULS 3 6002%
+[iStartVersionInfo, iEndVersionInfo, logVersion] = regexp(lineData, '^\<5002\> LOGVERSION[^0-9]*(\d+)\s\<6002\>', 'start', 'end', 'tokens' );
+logVersion = str2double(logVersion{1}{1});
+lineData(iStartVersionInfo:iEndVersionInfo) = []; % remove version info region incl. 5002/6002 markers
+
+
+%% 3. Optional training data (for Siemens own peak detection) is after
+% "6002" of Logversion info and "5002 uiHwRevisionPeru"
+[iStartTraining, iEndTraining, dataTraining] = regexp(lineData, '^\s*(.*?) (?=\<5002\> uiHwRevisionPeru)', 'start', 'end', 'tokens');
+if ~isempty(iStartTraining) % training trace does not always exist
+ dataTraining = dataTraining{1}{1};
+ lineData(iStartTraining:iEndTraining) = []; % remove training trace, but keep following 5002 for next step
end
+
+%% 4. Identify and remove info regions between 5002 and 6002 (may be
+% interleaved with trace data (e.g., messages or cushion Gain for RESP)
+[iStartInfoRegion, iEndInfoRegion, logInfoRegion] = regexp(lineData, '\<5002\>(.*?)\<6002\>', 'start', 'end', 'tokens' );
+logInfoRegion = cellfun(@(x) x{1,1}, logInfoRegion, 'UniformOutput', false)';
+traceData = regexprep(lineData, '\<5002\>(.*?)\<6002\>\s', '');
+traceData = regexprep(traceData, '\<5003\>$', ''); % remove 5003 mark of trace end
+
+log_parts.logHeader = logHeader;
+log_parts.logInfoRegion = logInfoRegion;
+log_parts.logVersion = logVersion;
+
+% convert remaining data (all numbers string) to number (int32)
+data = textscan(traceData, '%d', 'Delimiter', ' ', 'MultipleDelimsAsOne', true);
+
+
+%% 5. Remove all trigger markers (5000, 6000), but remmember position
% Remove the systems own evaluation of triggers.
cpulse = find(data{1} == 5000); % System uses identifier 5000 as trigger ON
cpulse_off = find(data{1} == 6000); % System uses identifier 5000 as trigger OFF
-recording_on = find(data{1} == 6002);% Scanner trigger to Stim PC?
-recording_off = find(data{1} == 5003);
-
-
% Filter the trigger markers from the ECG data
% Note: depending on when the scan ends, the last size(t_off)~=size(t_on).
-iNonEcgSignals = [cpulse; cpulse_off; recording_on; recording_off];
-codeNonEcgSignals = [5000*ones(size(cpulse)); ...
- 6000*ones(size(cpulse_off)); ...
- 6002*ones(size(recording_on))
- 5003*ones(size(recording_off))];
-
-% data_stream contains only the 2 ECG-channel time courses (with
-% interleaved samples
+iTriggerMarker = [cpulse; cpulse_off];
+codeTriggerMarker = [5000*ones(size(cpulse)); ...
+ 6000*ones(size(cpulse_off))];
+
+% data_stream contains only the time courses (with
+% interleaved samples for each channel)
data_stream = data{1};
-data_stream(iNonEcgSignals) = [];
+data_stream(iTriggerMarker) = [];
-%iDataStream contains the indices of all true ECG signals in the full
-%data{1}-Array that contains also non-ECG-signals
+%iDataStream contains the indices of all true trace signals in the full
+%data{1}-Array that contains also the trigger markers
iDataStream = 1:numel(data{1});
-iDataStream(iNonEcgSignals) = [];
+iDataStream(iTriggerMarker) = [];
-nSamples = numel(data_stream);
+%% 6. Sort remaining trace into coresponding channels (number of channels is
+% logfile-version dependent)
+nSamples = numel(data_stream);
switch upper(cardiacModality) % ecg has two channels, resp and puls only one
case 'ECG'
- nRows = ceil(nSamples/2);
-
- % create a table with channel_1, channels_AVF and trigger signal in
- % different Columns
- % - iData_table is a helper table that maps the original indices of the
- % ECG signals in data{1} onto their new positions
- data_table = zeros(nRows,3);
- iData_table = zeros(nRows,3);
-
- data_table(1:nRows,1) = data_stream(1:2:end);
- iData_table(1:nRows,1) = iDataStream(1:2:end);
-
- if mod(nSamples,2) == 1
- data_table(1:nRows-1,2) = data_stream(2:2:end);
- iData_table(1:nRows-1,2) = iDataStream(2:2:end);
- else
- data_table(1:nRows,2) = data_stream(2:2:end);
- iData_table(1:nRows,2) = iDataStream(2:2:end);
+ switch logVersion
+ case 1
+ nChannels = 2;
+ case 3
+ nChannels = 4;
end
-
- % now fill up 3rd column with trigger data
- % - for each trigger index in data{1}, check where ECG data with closest
- % smaller index ended up in the data_table ... and put trigger code in
- % same row of that table
- nTriggers = numel(iNonEcgSignals);
-
- for iTrigger = 1:nTriggers
- % find index before trigger event in data stream and
- % detect it in table
- iRow = find(iData_table(:,2) == iNonEcgSignals(iTrigger)-1);
-
- % look in 1st column as well whether maybe signal detected there
- if isempty(iRow)
- iRow = find(iData_table(:,1) == iNonEcgSignals(iTrigger)-1);
- end
-
- data_table(iRow,3) = codeNonEcgSignals(iTrigger);
+ case 'PPU'
+ nChannels = 1;
+ case 'RESP'
+ switch logVersion
+ case 1
+ nChannels = 1;
+ case 3
+ nChannels = 5; % breathing belt plus 4 channel biomatrix
end
- case {'RESP', 'PPU'} % only one channel available, fill second row with zeros
- nRows = nSamples;
-
- % create a table with channel_1 and trigger signal in
- % different Columns
- % - iData_table is a helper table that maps the original indices of the
- % ECG signals in data{1} onto their new positions
- data_table = zeros(nRows,3);
- iData_table = zeros(nRows,3);
-
- data_table(1:nRows,1) = data_stream;
- iData_table(1:nRows,1) = iDataStream;
-
- % now fill up 3rd column with trigger data
- % - for each trigger index in data{1}, check where ECG data with closest
- % smaller index ended up in the data_table ... and put trigger code in
- % same row of that table
- nTriggers = numel(iNonEcgSignals);
-
- for iTrigger = 1:nTriggers
- % find index before trigger event in data stream and
- % detect it in table
- iRow = find(iData_table(:,1) == iNonEcgSignals(iTrigger)-1);
- if ~isempty(iRow)
- data_table(iRow,3) = codeNonEcgSignals(iTrigger);
- end
- end
otherwise
error('unknown cardiac/respiratory logging modality: %s', cardiacModality);
+end
+
+nRows = ceil(nSamples/nChannels);
+
+% create a table with channel_1, channels_AVF and trigger signal in
+% different Columns
+% - iData_table is a helper table that maps the original indices of the
+% ECG signals in data{1} onto their new positions
+data_table = zeros(nRows,nChannels+1);
+iData_table = zeros(nRows,nChannels+1);
+
+for iChannel = 1:nChannels
+ data_table(1:nRows,iChannel) = data_stream(iChannel:nChannels:end);
+ iData_table(1:nRows,iChannel) = iDataStream(iChannel:nChannels:end);
+end
+
+% TODO: deal with mod(nSamples, nChannels) > 0 (incomplete data?)
+
+
+%% 7. Re-insert trigger markers as extra column
+% now fill up nChannel+1. column with trigger data
+% - for each trigger index in data{1}, check where ECG data with closest
+% smaller index ended up in the data_table ... and put trigger code in
+% same row of that table
+nTriggers = numel(iTriggerMarker);
+
+for iTrigger = 1:nTriggers
+ % find index before trigger event in data stream and
+ % detect it in table, look in last columns first, then go
+ % backwards
+ iRow = [];
+ iChannel = nChannels;
+ while isempty(iRow)
+
+ iRow = find(iData_table(:,iChannel) == iTriggerMarker(iTrigger)-1);
+ iChannel = iChannel - 1;
+ end
+
+ data_table(iRow,nChannels+1) = codeTriggerMarker(iTrigger);
end
\ No newline at end of file
diff --git a/PhysIO/code/readin/tapas_physio_siemens_table2cardiac.m b/PhysIO/code/readin/tapas_physio_siemens_table2cardiac.m
index 7f07762a..9ae6cfea 100644
--- a/PhysIO/code/readin/tapas_physio_siemens_table2cardiac.m
+++ b/PhysIO/code/readin/tapas_physio_siemens_table2cardiac.m
@@ -18,19 +18,24 @@
% onset of first scan (t=0)
%
% OUT
+%
% dataCardiac = struct(...
% 'cpulse_on', cpulse_on, ...
% 'cpulse_off', cpulse_off, ...
% 'recording_on', recording_on, ...
% 'recording_off', recording_off, ...
-% 'channel_1', channel_1, ...
-% 'channel_AVF', channel_AVF, ...
+% 'recordingChannels', recordingChannels, ...
% 'meanChannel', meanChannel, ...
% 'c', c, ...
% 't', t, ...
% 'ampl', ampl, ...
% 'stopSample', stopSample ...
% );
+%
+% AVF means "Augmented Vector Foot" and is a label for a unipolar lead
+% of the ECG electrode setup. For .ecg- LOGVERSIOM one, this was the 2nd
+% column ("channel") in the data (based on Siemens' Physioload function),
+% but for later versions, it's not clear
%
% EXAMPLE
% tapas_physio_siemens_table2cardiac
@@ -49,27 +54,32 @@
% COPYING or .
-% set new indices to actual
-cpulse_on = find(data_table(:,3) == 5000);
-cpulse_off = find(data_table(:,3) == 6000);
-recording_on = find(data_table(:,3) == 6002);
-recording_off = find(data_table(:,3) == 5003);
+% set new indices to actual, last column contains
+cpulse_on = find(data_table(:,end) == 5000);
+cpulse_off = find(data_table(:,end) == 6000);
+recording_on = find(data_table(:,end) == 6002);
+recording_off = find(data_table(:,end) == 5003);
% Split a single stream of ECG data into channel 1 and channel 2.
-channel_1 = data_table(:,1);
-channel_AVF = data_table(:,2);
-meanChannel = mean([channel_1(:) channel_AVF(:)],2);
+nChannels = size(data_table,2) - 1;
+
+for iChannel = 1:nChannels
+ recordingChannels(:,iChannel) = data_table(:,iChannel);
+end
+
+meanChannel = mean(recordingChannels, 2);
% Make them the same length and get time estimates.
-switch ecgChannel
+switch lower(ecgChannel)
case 'mean'
c = meanChannel - mean(meanChannel);
- case 'v1'
- c = channel_1 - mean(channel_1);
-
- case 'v2'
- c = channel_AVF - mean(channel_AVF);
+ case {'v1', 'v2', 'v3', 'v4'}
+ iChannel = str2double(ecgChannel(2));
+ c = recordingChannels(:,iChannel) - mean(recordingChannels(:,iChannel));
+ case {'avf'}
+ iChannel =2;
+ c = recordingChannels(:,iChannel) - mean(recordingChannels(:,iChannel));
end
% compute timing vector and time of detected trigger/cpulse events
@@ -94,8 +104,7 @@
'cpulse_off', cpulse_off, ...
'recording_on', recording_on, ...
'recording_off', recording_off, ...
- 'channel_1', channel_1, ...
- 'channel_AVF', channel_AVF, ...
+ 'recordingChannels', recordingChannels, ...
'meanChannel', meanChannel, ...
'c', c, ...
't', t, ...
diff --git a/PhysIO/code/sync/tapas_physio_create_scan_timing.m b/PhysIO/code/sync/tapas_physio_create_scan_timing.m
index a374b4ac..078a51ca 100644
--- a/PhysIO/code/sync/tapas_physio_create_scan_timing.m
+++ b/PhysIO/code/sync/tapas_physio_create_scan_timing.m
@@ -101,7 +101,7 @@
[VOLLOCS, LOCS, verbose] = ...
tapas_physio_create_scan_timing_from_tics_siemens( ...
ons_secs.t, ons_secs.t_start, log_files, verbose);
- case {'biopac_mat', 'biopac_txt', 'bids'}
+ case {'adinstruments_txt', 'labchart_txt', 'biopac_mat', 'biopac_txt', 'bids'}
[VOLLOCS, LOCS, verbose] = ...
tapas_physio_create_scan_timing_from_acq_codes( ...
ons_secs.t + ons_secs.t_start, ons_secs.acq_codes, ...
diff --git a/PhysIO/code/sync/tapas_physio_create_scan_timing_from_acq_codes.m b/PhysIO/code/sync/tapas_physio_create_scan_timing_from_acq_codes.m
index e1c57d1b..b2ca03b3 100644
--- a/PhysIO/code/sync/tapas_physio_create_scan_timing_from_acq_codes.m
+++ b/PhysIO/code/sync/tapas_physio_create_scan_timing_from_acq_codes.m
@@ -56,6 +56,12 @@
VOLLOCS = find(acq_codes == 8);
end
+% bugfix if slice-wise triggers were mistaken for volume triggers
+if isempty(LOCS) && numel(VOLLOCS) >= nTotalSlices
+ LOCS = VOLLOCS;
+ VOLLOCS = [];
+end
+
isValidVOLLOCS = numel(VOLLOCS) >= nTotalVolumes;
isValidLOCS = numel(LOCS) >= nTotalSlices;
diff --git a/PhysIO/code/sync/tapas_physio_create_scan_timing_from_end_marker_philips.m b/PhysIO/code/sync/tapas_physio_create_scan_timing_from_end_marker_philips.m
index 17680a45..8924984b 100644
--- a/PhysIO/code/sync/tapas_physio_create_scan_timing_from_end_marker_philips.m
+++ b/PhysIO/code/sync/tapas_physio_create_scan_timing_from_end_marker_philips.m
@@ -144,22 +144,8 @@
if verbose.level>=1
- verbose.fig_handles(end+1) = tapas_physio_get_default_fig_params();
- set(gcf,'Name', 'Sync: Thresholding Gradient for slice acq start detection');
- fs(1) = subplot(1,1,1);
- plot(t, y(:,7:9));
- legend('gradient x', 'gradient y', 'gradient z');
- title('Raw Gradient Time-courses');
- hold on,
- ylims = ylim;
-
- plot( [(VOLLOCS(1)-1)/TA (VOLLOCS(1)-1)/TA] , ylims, 'k' )
- plot( [(VOLLOCS(1+Ndummies)-1)/TA (VOLLOCS(1+Ndummies)-1)/TA] , ylims, 'g' )
- plot( [(VOLLOCS(end)-1)/TA (VOLLOCS(end)-1)/TA], ylims, 'k' )
- plot( [(LOCS(end)-1)/TA (LOCS(end)-1)/TA] , ylims, 'k' )
- plot( [(VOLLOCS(end-1)-1)/TA (VOLLOCS(end-1)-1)/TA] , ylims, 'k' )
-
- plot( [(LOC_END_MARKER-1)/TA (LOC_END_MARKER-1)/TA], ylims, 'g' )
+ [verbose] = tapas_physio_plot_create_scan_timing_philips(t, y, VOLLOCS, Ndummies, LOCS, ...
+ LOC_END_MARKER)
end
% VOLLOCS = find(abs(diff(z2))>thresh.vol);
diff --git a/PhysIO/code/sync/tapas_physio_get_onsets_from_locs.m b/PhysIO/code/sync/tapas_physio_get_onsets_from_locs.m
index 629d07f9..49bda6b3 100644
--- a/PhysIO/code/sync/tapas_physio_get_onsets_from_locs.m
+++ b/PhysIO/code/sync/tapas_physio_get_onsets_from_locs.m
@@ -88,12 +88,13 @@
end
if verbose.level >= 3
- stringTitle = 'Sync: Slice bundles belonging to 1 volume';
- verbose.fig_handles(end+1) = tapas_physio_get_default_fig_params();
- set(gcf, 'Name', stringTitle);
- for v=1:Nallvols-1, stem(t(SLICELOCS{v}),ones(size(SLICELOCS{v})));hold all;end
- title(stringTitle);
- xlabel('t (seconds since SCANPHYSLOG-start)');
+ verbose = tapas_physio_plot_sync_bundles(Nallvols, t,SLICELOCS, verbose)
+
+ % save relevant parameters
+ verbose.review.sync_bundles.Nallvols = Nallvols;
+ verbose.review.sync_bundles.t = t;
+ verbose.review.sync_bundles.SLICELOCS =SLICELOCS;
+
end
try
diff --git a/PhysIO/code/tapas_physio_cfg_matlabbatch.m b/PhysIO/code/tapas_physio_cfg_matlabbatch.m
index dca37a3f..9d7d732a 100644
--- a/PhysIO/code/tapas_physio_cfg_matlabbatch.m
+++ b/PhysIO/code/tapas_physio_cfg_matlabbatch.m
@@ -39,6 +39,7 @@
vendor.tag = 'vendor';
vendor.name = 'vendor';
vendor.help = {' Vendor Name depending on your MR Scanner/Physiological recording system'
+ ' ''ADInstruments/LabChart_Txt'' - Text file (.txt) export of .adinst files, for recordings from AD Instruments devices, typically viewed and processed in LabChart'
' ''BIDS'' - Brain Imaging Data Structure (https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/06-physiological-and-other-continous-recordings.html)'
' ''Biopac_Txt'' - exported txt files from Biopac system (4 columns, [Resp PPU GSR Trigger]'
' ''Biopac_Mat'' - exported mat files from Biopac system'
@@ -70,14 +71,16 @@
' HCP-downloaded files of name format *_Physio_log.txt '
' are already preprocessed into this simple 3-column text format'
};
-vendor.labels = {'BIDS (Brain Imaging Data Structure)', 'Biopac_Txt', 'Biopac_Mat', ...
+vendor.labels = { 'ADInstruments/LabChart_Txt', ...
+ 'BIDS (Brain Imaging Data Structure)', 'Biopac_Txt', 'Biopac_Mat', ...
'BrainProducts', 'Custom', ...
'GE', 'Philips', ...
'Siemens (VB, *.puls/*.ecg/*.resp)', ...
'Siemens_Tics (VD: *_PULS.log/*_ECG1.log/*_RESP.log/*_AcquisitionInfo*.log)', ...
'Siemens_HCP (Human Connectome Project, *Physio_log.txt, 3 column format', ...
};
-vendor.values = {'BIDS', 'Biopac_Txt','Biopac_Mat', 'BrainProducts', 'Custom', ...
+vendor.values = {'ADInstruments_Txt', 'BIDS', 'Biopac_Txt','Biopac_Mat', ...
+ 'BrainProducts', 'Custom', ...
'GE', 'Philips', 'Siemens', 'Siemens_Tics', 'Siemens_HCP', ...
};
vendor.val = {'Philips'};
diff --git a/PhysIO/code/tapas_physio_init.m b/PhysIO/code/tapas_physio_init.m
index fd8e5a41..9a34919e 100644
--- a/PhysIO/code/tapas_physio_init.m
+++ b/PhysIO/code/tapas_physio_init.m
@@ -46,6 +46,12 @@
fprintf('OK.\n');
end
+% Adding test paths as well to run tapas_physio_test via tapas_test
+% TODO: does not work, if code folder in SPM/toolbox
+if ~exist('tapas_physio_test')
+ pathPhysIOTest = fullfile(fileparts(pathPhysIO), 'tests');
+ addpath(genpath(pathPhysIOTest));
+end
%% Check and add SPM path
fprintf('Checking Matlab SPM path now...');
diff --git a/PhysIO/code/tapas_physio_new.m b/PhysIO/code/tapas_physio_new.m
index d0d65627..1704fac6 100644
--- a/PhysIO/code/tapas_physio_new.m
+++ b/PhysIO/code/tapas_physio_new.m
@@ -101,7 +101,12 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
log_files = [];
- % vendor Name depending on your MR Scanner system
+ % vendor Name depending on your MR Scanner /
+ % physiological recording system
+ % 'ADInstruments/LabChart_Txt'' - Text file (.txt)
+ % export of .adinst files, for recordings
+ % from AD Instruments devices, typically
+ % viewed and processed in LabChart
% 'BIDS' - Brain Imaging Data Structure (http://bids.neuroimaging.io/bids_spec.pdf, section 8.6)'
% 'Biopac_Txt' - exported txt files from Biopac system (4 columns, [Resp PPU GSR Trigger]'
% 'Biopac_Mat' - exported mat files from Biopac system'
diff --git a/PhysIO/code/tapas_physio_version.m b/PhysIO/code/tapas_physio_version.m
index a6db2ee0..512e1d63 100644
--- a/PhysIO/code/tapas_physio_version.m
+++ b/PhysIO/code/tapas_physio_version.m
@@ -23,4 +23,4 @@
% version 3 or, at your option, any later version). For further details,
% see the file COPYING or .
%
-versionPhysio = 'R2022a-v8.1.0';
+versionPhysio = 'R2022a-v8.2.0-beta';
diff --git a/PhysIO/code/utils/tapas_physio_check_spm_batch_editor_integration.m b/PhysIO/code/utils/tapas_physio_check_spm_batch_editor_integration.m
index 642f4ba3..1425914e 100644
--- a/PhysIO/code/utils/tapas_physio_check_spm_batch_editor_integration.m
+++ b/PhysIO/code/utils/tapas_physio_check_spm_batch_editor_integration.m
@@ -32,14 +32,22 @@
[isSpmOnPath, pathSpm] = tapas_physio_check_spm();
-isPhysioVisibleForSpmBatchEditor = isSpmOnPath; % minimum requirement for integration: SPM works!
+isPhysioVisibleForSpmBatchEditor = false; % checked below, need SPM visible for that
% check for config matlabbatch file
if isSpmOnPath
- filePhysioCfgMatlabbatch = ...
- dir(fullfile(pathSpm, 'toolbox', '**/tapas_physio_cfg_matlabbatch.m'));
- isPhysioVisibleForSpmBatchEditor = ~isempty(filePhysioCfgMatlabbatch);
+ % check all possible SPM toolbox locations, as listed in its defaults:
+ tbx = spm_get_defaults('tbx'); % SPM toolbox parameter struct
+
+ iDir = 1;
+ while (iDir <= numel(tbx.dir)) && ~isPhysioVisibleForSpmBatchEditor
+ filePhysioCfgMatlabbatch = ...
+ dir(fullfile(tbx.dir{iDir}, '**/tapas_physio_cfg_matlabbatch.m'));
+
+ isPhysioVisibleForSpmBatchEditor = ~isempty(filePhysioCfgMatlabbatch);
+ iDir = iDir + 1;
+ end
% also important to set default modality of spm to fMRI and
% initialize batch editor, if not done before
diff --git a/PhysIO/code/utils/tapas_physio_fill_empty_parameters.m b/PhysIO/code/utils/tapas_physio_fill_empty_parameters.m
index 3ad5be5d..a8f159e6 100644
--- a/PhysIO/code/utils/tapas_physio_fill_empty_parameters.m
+++ b/PhysIO/code/utils/tapas_physio_fill_empty_parameters.m
@@ -52,7 +52,7 @@
switch lower(physio.log_files.vendor)
case {'bids', 'biopac_mat', 'brainproducts', 'siemens'} % will be read from file later
physio.log_files.sampling_interval = [];
- case 'biopac_txt'
+ case {'adinstruments_txt', 'labchart_txt', 'biopac_txt'}
physio.log_files.sampling_interval = 1/1000;
case 'ge'
physio.log_files.sampling_interval = 25e-3;
diff --git a/PhysIO/code/utils/tapas_physio_pca.m b/PhysIO/code/utils/tapas_physio_pca.m
index 34d996a9..a1a313e1 100644
--- a/PhysIO/code/utils/tapas_physio_pca.m
+++ b/PhysIO/code/utils/tapas_physio_pca.m
@@ -29,14 +29,17 @@
[nVoxels,nVolumes] = size(timeseries);
-if nVoxels <= nVolumes
- error([mfilename ':NotEnoughVoxels'], 'nVoxels <= nVolumes')
-end
-
if nargin < 2
method = 'svd';
end
+if nVoxels <= nVolumes
+ % TODO: reimplement using PCA of COV((X-mu)'*(X-mu))
+ method = 'stats-pca';
+ % error([mfilename ':NotEnoughVoxels'], 'nVoxels <= nVolumes')
+end
+
+
switch lower(method)
case 'svd'
@@ -56,8 +59,8 @@
LATENT = eigen_values; % [nPCs, 1]
% Eigen_values -> Variance explained
- vairance_explained = 100*eigen_values/sum(eigen_values); % in percent (%)
- EXPLAINED = vairance_explained; % [nPCs, 1]
+ variance_explained = 100*eigen_values/sum(eigen_values); % in percent (%)
+ EXPLAINED = variance_explained; % [nPCs, 1]
% Sign convention : the max(abs(PCs)) is positive
[~,maxabs_idx] = max(abs(v));
diff --git a/PhysIO/docs/documentation.html b/PhysIO/docs/documentation.html
index 8e66b2aa..da41aad7 100644
--- a/PhysIO/docs/documentation.html
+++ b/PhysIO/docs/documentation.html
@@ -825,9 +825,9 @@ Troubleshoot
Readme
-Current version: Release 2022a, v8.1.0
+Current version: Release 2022b, v8.2.0
-Copyright (C) 2012-2022
Lars Kasper
kasper@biomed.ee.ethz.ch
+Copyright (C) 2012-2022
Lars Kasper
kasper@biomed.ee.ethz.ch
Translational Neuromodeling Unit (TNU)
Institute for Biomedical Engineering
University of Zurich and ETH Zurich
Download
@@ -1523,29 +1523,21 @@ Example Datasets for PhysIO
The following datasets are available to explore the read-in and modeling capabilities of PhysIO. They can be downloaded by running the function tapas_download_example_data()
in Matlab, which is located in the misc
subfolder of the TAPAS software release you downloaded (probably here).
Afterwards, the examples can be found in tapas/examples/<tapasVersion>/PhysIO
as different subfolders (vendor/device
) and shall be run directly from within these individual folders.
-Besides the raw physiological logfiles, each example contains example scripts to run PhysIO as
+Besides the raw physiological logfiles, each example contains example scripts to run PhysIO as
-- SPM job (
*spm_job.mat
)
-- editable SPM job (
*spm_job.m
)
-- plain matlab script (
*matlab_script.m
)
+- SPM job (
\\\*spm_job.mat
)
+- editable SPM job (
\\\*spm_job.m
)
+- plain matlab script (
\\\*matlab_script.m
)
Brain Imaging Data Structure (BIDS)
CPULSE 3T
-Courtesy of Hrvoje Stojic, Max Planck UCL Centre for Computational Psychiatry
-and Ageing Research, University College London
-Vendor-computed (software: Spike2) cardiac pulse events from PPU (finger
-plethysmograph) data, Siemens 3T scanner, Multiband CMRR sequence
-Description: This datasets contains the (compressed) tab-separated value
-(.tsv.gz
) files as well as the meta-file (.json
) holding sampling rate of
-the physiological recording, and its relative onset to scanning, in adherence
-with the BIDS standard for peripheral recordings
-files.
+Courtesy of Hrvoje Stojic, Max Planck UCL Centre for Computational Psychiatry and Ageing Research, University College London
+Vendor-computed (software: Spike2) cardiac pulse events from PPU (finger plethysmograph) data, Siemens 3T scanner, Multiband CMRR sequence
+Description: This datasets contains the (compressed) tab-separated value (.tsv.gz
) files as well as the meta-file (.json
) holding sampling rate of the physiological recording, and its relative onset to scanning, in adherence with the BIDS standard for peripheral recordings files.
PPU 3T
-Courtesy of Hrvoje Stojic, Max Planck UCL Centre for Computational Psychiatry
-and Ageing Research, University College London
+Courtesy of Hrvoje Stojic, Max Planck UCL Centre for Computational Psychiatry and Ageing Research, University College London
PPU (finger plethysmograph) and breathing belt, Siemens 3T scanner, Multiband CMRR sequence
-Description: Similar to CPULSE3T (same acquisition system), but now with analog
-data instead of vendor-detected pulses, data from different subject
+Description: Similar to CPULSE3T (same acquisition system), but now with analog data instead of vendor-detected pulses, data from different subject
PPU 3T Separate Files
Courtesy of Alexandre Sayal CIBIT, University of Coimbra
PPU (finger plethysmograph) and breathing belt, Siemens 3T scanner, Multiband CMRR sequence
@@ -1553,70 +1545,67 @@ PPU 3T Separate Files
General Electric
PPU 3T
Courtesy of Steffen Bollmann, Kinderspital Zurich and ETH Zurich
-PPU (finger plethysmograph) and breathing belt, General Electric 3T
-scanner
-Description: Similar to PPU, but acquired on a GE system with two
-separate output logfiles for pulse oximetry and breathing amplitude,
-sampled with 40 Hz. The quality of the signal is particularly
-challenging, stemming from a patient population.
+PPU (finger plethysmograph) and breathing belt, General Electric 3T scanner
+Description: Similar to PPU, but acquired on a GE system with two separate output logfiles for pulse oximetry and breathing amplitude, sampled with 40 Hz. The quality of the signal is particularly challenging, stemming from a patient population.
Philips
ECG 3T
-Courtesy of Sandra Iglesias, Translational Neuromodeling Unit, ETH &
-University of Zurich
+Courtesy of Sandra Iglesias, Translational Neuromodeling Unit, ETH & University of Zurich
4-electrode ECG and breathing belt, Philips 3T Achieva scanner
-Description: Standard example; shows how to use volume counting either
-from beginning or end of run to synchronize physiological logfile with
-acquisition onsets of fMRI scans.
+Description: Standard example; shows how to use volume counting either from beginning or end of run to synchronize physiological logfile with acquisition onsets of fMRI scans.
ECG 7T
Courtesy of Zina-Mary Manjaly, University Hospital Zurich
4-electrode ECG and breathing belt, Philips 7T Achieva scanner
-Description: The ECG data for ultra-high field data is typically much
-noisier than at 3 Tesla. Therefore, R-wave peaks are frequently missed
-by prospective trigger detection and not marked correctly in the
-logfile. This example shows how to select typical R-wave-peaks manually
-and let the algorithm find the heartbeat events.
+Description: The ECG data for ultra-high field data is typically much noisier than at 3 Tesla. Therefore, R-wave peaks are frequently missed by prospective trigger detection and not marked correctly in the logfile. This example shows how to select typical R-wave-peaks manually and let the algorithm find the heartbeat events.
PPU 3T
Courtesy of Diana Wotruba, University and University Hospital of Zurich
-PPU (finger plethysmograph) and breathing belt, Philips 3T Achieva
-scanner
-Description: Similar to ECG3T, but a plethysmograph instead of an ECG
-was used to monitor the cardiac pulsation. Example shows how to extract
-heart and breathing rate.
+PPU (finger plethysmograph) and breathing belt, Philips 3T Achieva scanner
+Description: Similar to ECG3T, but a plethysmograph instead of an ECG was used to monitor the cardiac pulsation. Example shows how to extract heart and breathing rate.
PPU 7T
Courtesy of Jakob Heinzle and Lars Kasper, TNU, University Zurich and ETH Zurich
-PPU (finger plethysmograph) and breathing belt, Philips 7T Achieva
-scanner
+PPU (finger plethysmograph) and breathing belt, Philips 7T Achieva scanner
Description: Challenging cardiac data that requires bandpass-filtering during preprocessing, since it is compromised by both high frequency noise (from the scanner, modulated at every slice TR) and low frequency noise (breathing modulation).
Siemens - VB
Siemens has different physiological logfile formats, for which examples are provided here. A detailed description of these formats is on a different wiki page.
-This is the older Siemens log file format (also available via manual recording), which is part of software release VB, and can be determined by the file extensions .resp
, .ecg
, .puls
, in combination with an optional .dcm
DICOM header file for the first (or last) acquired volume.
-A lot of 7T scanners still use this format.
+This is the older Siemens log file format (also available via manual recording), which is part of software release _VB_, and can be determined by the file extensions .resp
, .ecg
, .puls
, in combination with an optional .dcm
DICOM header file for the first (or last) acquired volume.
+A lot of 7T scanners still use this format, but it is also the default on modern 3T systems, if you don't have C2P sequences for fMRI (e.g., from CMRR) or WIPs from Siemens (see below).
ECG 3T
Courtesy of Miriam Sebold, Charite Berlin, and Quentin Huys, TNU Zurich
-4-electrode ECG data, Siemens 3T scanner
+4-electrode ECG data, Siemens 3T scanner, logfile version 1
Description: Similar to ECG 3T, but acquired on a Siemens system with only one logfile for ECG data. The quality of the signal is challenging, stemming from a patient population.
PPU3T (Sync First and Sync Last)
Courtesy of Alexander Ritter, University of Jena, Germany
-Siemens 3T pulse oximetry and respiratory bellows data from a complete scan session of a healthy volunteer, plus the DICOM header file of the first and last (382nd) volume of an fMRI run, respectively.
-This showcases scan timing synchronization using the DICOM timestamps in an intricate case, where the physiological logfile spans the whole scan session (and not only the fMRI run). See TAPAS github issue #55 for further details.
+Siemens 3T pulse oximetry and respiratory bellows data, logfile version 1 DICOM header file of first and last (382nd) volume of an fMRI run, respectively.
+Description: This data covering a complete scan session of a healthy volunteer showcases scan timing synchronization using the DICOM timestamps in an intricate case, where the physiological logfile spans the whole scan session (and not only the fMRI run). See TAPAS github issue #55 for further details.
+ECG 3T - Logversion 3
+Courtesy of Shahin Safa, see TAPAS GitHub issue 204
+4-electrode ECG data, Siemens scanner, logfile version 3 corresponding respiratory data: Resp 3T - Logversion 1
+Description: This is an fMRI study on the auditory system of the brain, which explains the long TR (10 s), to put scanning gaps when presenting the sound to the subject.
+Resp 3T - Logversion 1
+Courtesy of Shahin Safa, see TAPAS GitHub issue 204
+Respiratory bellows data, Siemens scanner, logfile version 1 corresponding cardiac data: ECG 3T - Logversion 3
+Description: This is an fMRI study on the auditory system of the brain, which explains the long TR (10 s), to put scanning gaps when presenting the sound to the subject.
+Resp 3T - Logversion 3
+Courtesy of Lars Kasper, University Health Network Toronto, Canada
+Respiratory bellows data, Siemens Prisma 3T, logfile version 3
+Description: Short fingertapping run with logging automatically switched off after about 2 minutes (nominally 5) due to ECG channels not connected, but requested for recording. Biomatrix sensors were not available, but are logged as 4 extra channels with constant values here.
Siemens - HCP
-The Human Connectome Project uses Siemens scanners, and the logfile format that comes with their published data seems to be pre-converted and custom (even though the documentation desribes the VB format). We have implemented an own reader for that and written a little tutorial for a single subject dataset of the HCP.
+The Human Connectome Project uses Siemens scanners, and the logfile format that comes with their published data seems to be pre-converted and custom (even though the documentation desribes the VB format). We have implemented an own reader for that and written a little tutorial for a single subject dataset of the HCP.
https://github.com/translationalneuromodeling/tapas/issues/6#issuecomment-361001716
If you download the whole dataset (including functional image files), this example with the additional batches mentioned below also demonstrates how to use the toolbox for model assessment using statistical maps (F-contrasts).
HCP (Subject 178748)
You will have to download the dataset from the HCP yourself, we just provide the matlab batches and the physiological logfile tfMRI_MOTOR_LR_Physio_log.txt
here.
For consistency with the other example files, the batch files have been renamed compared to the blog entry:
-batch_preproc.m
-> batch_preproc.m
-batch_physio.m
-> siemens_hcp_ppu3t_spm_job.m
-batch_glm.m
-> batch_glm.m
+batch_preproc.m
-> batch_preproc.m
+batch_physio.m
-> siemens_hcp_ppu3t_spm_job.m
+batch_glm.m
-> batch_glm.m
If you want to run the preproc and glm batch, place them on the same level as the subject folder 178748
for the downloaded data. The physio-batch shall reside in the same folder as the physiological logfile tfMRI_MOTOR_LR_Physio_log.txt
.
Siemens - VD/VE Tics
-This is the most recent logfile format of Siemens, included in Software releases VD, VE and sometimes referred to as the Tics format, because all time stamps in all files refer to the same reference point (start of the day) and count in the same intervals or "tics" of 2.5 ms from there.
-You will recognize this file format via the extensions _Info.log
(or _AcquisitionInfo.log
), _RESP.log
, _ECG.log
and _PULS.log
. Sometimes, it is also written into the DICOM header (.dcm
) file of your functional data directly. In this case, use extractCMRRPhysio.m to convert it to the above separate files before using PhysIO.
+This is the most recent logfile format of Siemens, included in Software releases _VD_, _VE_ and sometimes referred to as the Tics format, because all time stamps in all files refer to the same reference point (start of the day) and count in the same intervals or "tics" of 2.5 ms from there.
+You will recognize this file format via the extensions \\\_Info.log
(or \\\_AcquisitionInfo.log
), \\\_RESP.log
, \\\_ECG.log
and \\\_PULS.log
. Sometimes, it is also written into the DICOM header (.dcm
) file of your functional data directly. In this case, use extractCMRRPhysio.m to convert it to the above separate files before using PhysIO.
Most modern Siemens scanners, such as the Prisma or 7T Terra, use this format.
-There are a couple of variants for this format around (e.g., with the WIP Multiband Protocol that is distributed to multiple sites), and PhysIO tries to support all of them.
+There are a couple of variants for this format around (e.g., with the WIP Multiband Protocol that is distributed to multiple sites), and PhysIO tries to support all of them.
PPU 3T
Courtesy of Saskia Bollmann, Centre for Advanced Imaging, University of Queensland, Brisbane, Australia
Pulse oximetry and breathing belt data, Siemens Prisma 3T, logfile version EJA_1
, multi-echo fMRI (3 echoes)
@@ -1624,45 +1613,41 @@ PPU 3T
PPU 3T Separate Files
Courtesy of Alexandre Sayal CIBIT, University of Coimbra
PPU (finger plethysmograph) and breathing belt, Siemens 3T scanner, Multiband CMRR sequence
-Description: Raw data that was used to convert to two separate BIDS files above (BIDS/PPU3T_Separate_Files
) for cardiac and respiratory recordings, because of differing sampling rate (5 vs 20 ms).
+Description: Raw data that was used to convert to two separate BIDS files above (BIDS/PPU3T_Separate_Files
) for cardiac and respiratory recordings, because of differing sampling rate (5 vs 20 ms).
The UUID and date/time stamps were altered for anonymization.
Technical Documentation: Read-in
Brain Imaging Data Structure (BIDS)
PhysIO supports physiological logfiles prepared according to the BIDS standard
-- In brief, BIDS files are (optionally compressed) tab-separated values
-(
*.tsv[.gz]
) files that contain raw traces of peripheral recordings from
-cardiac and respiratory sources, as well as scan trigger events
-- The header of the columns of this
*.tsv
file, as well as meta-information,
-such as sampling rate and relative onset of physiological logging to MRI scan
-onset is described in an accompanying *.json
file
-- It is assumed to have the that this
*.json
file has the same name
-(apart from the extension) as the *.tsv
file
-- If PhysIO does not find this file, you can manually enter the timing
-information in the
log_files
structure, and a default column order of
-(cardiac, respiratory, trigger) is assumed
-
-
-- Example
*.tsv
file (with cardiac, respiratory, trigger column
-: -0.949402 -0.00610382 0
- -0.949402 -0.00610382 0
- -0.951233 -0.00915558 0
- -0.951233 -0.00915558 0
- -0.953064 -0.0122073 0
- -0.953064 -0.0122073 0
- -0.95459 -0.0076297 1
- -0.95459 -0.0076297 0
-- Example
*.json
file:{
- "SamplingFrequency": 50.0,
- "Columns": [
- "cardiac",
- "respiratory",
- "trigger"
- ],
- "StartTime": -255.45
-}
-- See
tapas_physio_read_physlogfiles_bids.m
for more details and technical
-documentation.
+- In brief, BIDS files are (optionally compressed) tab-separated values (
\*.tsv\[.gz\]
) files that contain raw traces of peripheral recordings from cardiac and respiratory sources, as well as scan trigger events
+- The header of the columns of this
\*.tsv
file, as well as meta-information, such as sampling rate and relative onset of physiological logging to MRI scan onset is described in an accompanying \*.json
file
+- It is assumed to have the that this
\*.json
file has the same name (apart from the extension) as the \*.tsv
file
+- If PhysIO does not find this file, you can manually enter the timing information in the
log_files
structure, and a default column order of (cardiac, respiratory, trigger) is assumed
+
+
+- Example
\*.tsv
file (with cardiac, respiratory, trigger column :
+
+ -0.949402 -0.00610382 0
+ -0.949402 -0.00610382 0
+ -0.951233 -0.00915558 0
+ -0.951233 -0.00915558 0
+ -0.953064 -0.0122073 0
+ -0.953064 -0.0122073 0
+ -0.95459 -0.0076297 1
+ -0.95459 -0.0076297 0
+{
+ "SamplingFrequency": 50.0,
+ "Columns": [
+ "cardiac",
+ "respiratory",
+ "trigger"
+ ],
+ "StartTime": -255.45
+}
+- Note that
StartTime
refers to when the physiological recording started relative to the first scan volume of the fMRI run, which means that typically this value is negative, because one starts the recording before the onset of scan volumes.
+- See
tapas_physio_read_physlogfiles_bids.m
for more details and technical documentation.
BioPac
Mat-file Export (.mat
)
@@ -1684,13 +1669,12 @@ Single Text File Export (.txt
Export your traces from cardiac and breathing recording devices into 2 text files and select log_files.vendor = 'Custom'
. The format is explained in tapas_physio_new
or the help window of the Batch Editor:
0.2 0
0.4 1 <- cardiac pulse event
0.2 0
-0.3 0
-
-
You have to specify the sampling intervals for these log files (in seconds), via log_files.sampling_interval
, e.g. [0.01 0.02] if you have 10 ms (100 Hz) and 20 ms (50 Hz) sampling intervals (frequencies) for cardiac and respiratory data, respectively
You will probably have to change log_files.relative_start_acquisition
, if logging of your physiological recording device does not start synchronized to the first fMRI volume.
@@ -1698,17 +1682,19 @@ General Electric (GE)
+ 2626
+ 2649
+ 2673
+ 2699
+ 2727
+ 2755
- sampling rate is determined as a setting beforehand, has to be noted manually (not in log file)
Philips
-- Physiology automatically recorded into
SCANPHYSLOG_<Date>_<Time>.log
(one file per scan) as soon as ECG is connected to scanner, and scan is started
+- Physiology automatically recorded into
SCANPHYSLOG\_<Date>\_<Time>.log
(one file per scan) as soon as ECG is connected to scanner, and scan is started
- tabular text (ascii) format, different columns for ECG, pulse oximetry and breathing data
- additionally, trigger events and gradient timecourses are logged, and can be used for synchronization by the toolbox
@@ -1729,8 +1715,10 @@ Philips
Siemens
Manual Recording
Physiological data collection on the Siemens scanners uses the physiological monitoring unit (PMU). The initial sampling is performed at 400 Hz, but through the PMU buffer the effective sampling intervals are ECG: 2.5 ms, RESP: 20 ms, PULS: 20 ms and EXT: 5 ms.
-There are several ways to control the physiological data collection. The 'manual' version is available on all platforms. It uses the telnet mpcu/ideacmdtool to manually start and stop the log file acquisition. The log files (logFileName.ecg, logFileName.resp, logFileName.puls, logFileName.ext) are stored in \MedCom\log
. More details on how to record these data can be found here or in the "Other Miscellaneous Topics" slides from the IDEA course.
-An example of a .puls logfile is given below. The data are stored in one long line. The text between 5002 and 6002 forms the header, and the text between 5003 and 6003 the footer. Important information in the footer is the LogStartMDHTime and the LogStopMDHTime (in ms since midnight), which can be used to synchronize the logfiles with the dicom images using the AcquisitionTime in the dicom header (in hhmmss.ms). The values 5000 and 6000 are inserted into the signal trace and indicate trigger events. Note that only the modality which is selected to be displayed during the acquisition will have triggers.
+There are several ways to control the physiological data collection. The 'manual' version is available on all platforms. It uses the telnet mpcu/ideacmdtool to manually start and stop the log file acquisition. The log files (logFileName.ecg
, logFileName.resp
, logFileName.puls
, logFileName.ext
) are stored in \\MedCom\\log
. More details on how to record these data can be found here or in the "Other Miscellaneous Topics" slides from the IDEA course.
+General Properties
+An example of a .puls
logfile is given below. The data are stored in one long line. The text between 5002 and 6002 forms the header, and the text between 5003 and 6003 the footer. Important information in the footer is the LogStartMDHTime
and the LogStopMDHTime
(in ms since midnight), which can be used to synchronize the logfiles with the DICOM images using the AcquisitionTime
in the DICOM header (in hhmmss.ms). The values 5000 and 6000 are inserted into the signal trace and indicate trigger events. Note that only the modality which is selected to be displayed during the acquisition will have triggers.
+We use the time stamp of the clock of the Measurement Data Header (MDH), i.e., computer that controls the scanner, to synchronize with the DICOMs, because this computer also controls the creation of the scan data, i.e., reconstructed DICOM images. This is in accordance to other packages reading Siemens physiological logfile data, e.g., Chris Rorden's PART, with a detailed explanation on the DICOM timestamp in AcquisitionTime
found here.
1 2 40 280 5002 Logging PULSE signal: reduction factor = 1, PULS_SAMPLES_PER_SECOND = 50; PULS_SAMPLE_INTERVAL = 20000 6002 1653 1593 1545 1510 1484 ...
ACQ FINISHED
6002 3093 3096 3064 5000 3016 2926 5003
@@ -1747,9 +1735,25 @@ Manual Recording
LogStopMDHTime: 47654452
LogStartMPCUTime: 47030087
LogStopMPCUTime: 47652240
-6003
CMRR Sequence
+6003Updates Logversion 3
+The above specification for physiological Siemens logfiles has been changed by the new logfile version 3 for both cardiac and respiratory data:
+
+- There may be multiple info regions between
5002
and 6002
tags, interleaved with the physiological trace, e.g., to log an update in the respiratory cushion gain.
+- The
.ecg
file has now 4 instead of 2 channels.
+- The
.resp
file now contains not only the respiratory bellows signal, but also 4 datapoints per sampling interval of the biomatrix signals (integrated sensors in patient bed for breathing detection, e.g., in Siemens Vida series).
+
+A logfile in version 3 therefore adheres to the following template (note the logfile version indicated by keyword LOGVERSION
at the start):
+<Header> 5002 <LOGVERSION XX> 6002
+<[optional] training trace data> 5002 uiHwRevisionPeru ... [optional] 6002
+5002 <infoRegion3> 6002 5002 <infoRegion4> 6002 ... 5002 <infoRegionN> 6002
+<trace data 1 (all channels, arbitrary number of samples, trigger markers 5000, 6000)> ...
+5002 <infoRegionN+1> 6002
+<trace data 2 (all channels, arbitrary number of samples, trigger markers 5000, 6000)> ...
+5002 <infoRegionN+2> 6002 ...
+<trace data M> ... 5003
PhysIO automatically reads the Siemens logfiles correctly for all modalities and logfile versions 1,2 and 3.
+CMRR Sequence / WIP Advanced Physio Logging
The CMRR sequence on VD/VE also allows the automatic recording of physiological log files (to be selected in the sequence special card). For more information have a look at the manual. The physiological traces are stored in logFileName_PULS.log, logFileName_RESP, logFileName_ECG.log. Timing information is stored in logFileName_Info.log and external trigger events in logFileName_EXT.log.
-An example of the current format (December 2017, Release 016a) for the logFileName_Info.log is given below:
+An example of the current format (December 2017, Release 016a) for the logFileName_Info.log is given below:
UUID = 7a16ea95-ac36-4ee3-9b76-bbb686ac07ca
ScanDate = 20171206_150609
LogVersion = EJA_1
@@ -1798,7 +1802,7 @@ Manual Recording
21747877 PULS 1962
PhysIO uses the logFileName_Info.log to synchronize the physiological traces with the data acquisition. Note that the reference slice does not yet take into account the multiband slice ordering, but just assumes an even distribution. Older version of the CMRR sequence produced slightly different output files, which might work. Please log an issue if you have a very different format that is not supported.
Human Connectome Project
Disclaimer: Most of the information below is a best guess from the developers, but without any guarantee of accuracy.
-The physiological log file (*paradigm*_Physio_log.txt
) distributed with the Human Connectome Project data contains respiratory and puls-oximeter data in one file. The first column marks when data acquisition is performed, the second and third contain the respiratory and puls-oximeter traces, respectively. The files are written at a sampling rate of 400Hz and start and end with the scan. PhysIO does provide a reader, you just need to select the appropriate option in the file format tab. An example is provided below:
+The physiological log file (\*paradigm\*\_Physio_log.txt
) distributed with the Human Connectome Project data contains respiratory and puls-oximeter data in one file. The first column marks when data acquisition is performed, the second and third contain the respiratory and puls-oximeter traces, respectively. The files are written at a sampling rate of 400Hz and start and end with the scan. PhysIO does provide a reader, you just need to select the appropriate option in the file format tab. An example is provided below:
1 1904 1756
1 1904 1756
1 1907 1754
@@ -1826,8 +1830,27 @@ Human Connectome Project
1 1904 1780
Version History (Changelog)
Current Release
-Current version: PhysIO Toolbox Release R2022a, v8.1.0
-April 5th, 2022
+Current version: PhysIO Toolbox Release R2022b, v8.2.0
+September 12th, 2022
+Minor Release Notes (v8.2.0)
+Added
+
+- Interface
tapas_physio_test
to TAPAS-generic tapas_test
function
+- Added suport for logfile version 3 of Siemens physio recordings
+- multi ECG/Resp channels and interleaved status messages
+- new integration test for Siemens VB Logversion 3
Fixed
+
+
+
+- Removed dependence on
nanmean
(Statistics Toolbox)
+
+- Compatibility with multiple SPM toolbox locations for
lmod
(GitHUb issue #211)
+- as listed in
spm_get_defaults('tbx')
+
+
+
Minor Release Notes (v8.1.0)
Added
diff --git a/PhysIO/docs/documentation.pdf b/PhysIO/docs/documentation.pdf
index d1f151e1..4e213c0c 100644
Binary files a/PhysIO/docs/documentation.pdf and b/PhysIO/docs/documentation.pdf differ
diff --git a/PhysIO/tests/integration/tapas_physio_examples_test.m b/PhysIO/tests/integration/tapas_physio_examples_test.m
index e2c1aec4..0a445f96 100644
--- a/PhysIO/tests/integration/tapas_physio_examples_test.m
+++ b/PhysIO/tests/integration/tapas_physio_examples_test.m
@@ -41,10 +41,9 @@
% path to examples, needed for all test cases
function setupOnce(testCase)
-% run GE example and extract physio
+% Get PhysIO public repo base folder from this file's location
testCase.TestData.pathPhysioPublic = fullfile(fileparts(mfilename('fullpath')), '..', '..');
-% TODO: Make generic!
-testCase.TestData.pathExamples = fullfile(testCase.TestData.pathPhysioPublic, '..', 'examples');
+testCase.TestData.pathExamples = tapas_physio_get_path_examples(testCase.TestData.pathPhysioPublic);
end
@@ -82,6 +81,7 @@ function test_bids_ppu3t_separate_files_matlab_only(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
+
function test_biopac_txt_ppu3t_matlab_only(testCase)
%% Compares previously saved physio-structure and multiple regressors file
% to current output of re-run of BioPac_txt/PPU3T example using matlab only
@@ -108,7 +108,6 @@ function test_philips_ecg3t_matlab_only(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
-
function test_philips_ecg3t_v2_matlab_only(testCase)
%% Compares previously saved physio-structure and multiple regressors file
% to current output of re-run of Philips/ECG3T_V2 example using matlab only
@@ -117,7 +116,6 @@ function test_philips_ecg3t_v2_matlab_only(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
-
function test_philips_ecg7t_matlab_only(testCase)
%% Compares previously saved physio-structure and multiple regressors file
% to current output of re-run of Philips/ECG7T example using matlab only
@@ -126,7 +124,6 @@ function test_philips_ecg7t_matlab_only(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
-
function test_philips_ppu3t_matlab_only(testCase)
%% Compares previously saved physio-structure and multiple regressors file
% to current output of re-run of Philips/PPU3T example using matlab only
@@ -153,6 +150,13 @@ function test_siemens_vb_ecg3t_matlab_only(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
+function test_siemens_vb_ecg3t_logversion_3_matlab_only(testCase)
+%% Compares previously saved physio-structure and multiple regressors file
+% to current output of re-run of Siemens_VB/ECG3T example using matlab only
+dirExample = 'Siemens_VB/ECG3T_Logversion_3';
+doUseSpm = false;
+run_example_and_compare_reference(testCase, dirExample, doUseSpm)
+end
function test_siemens_vb_ppu3t_sync_first_matlab_only(testCase)
%% Compares previously saved physio-structure and multiple regressors file
@@ -163,7 +167,6 @@ function test_siemens_vb_ppu3t_sync_first_matlab_only(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
-
function test_siemens_vb_ppu3t_sync_last_matlab_only(testCase)
%% Compares previously saved physio-structure and multiple regressors file
% to current output of re-run of Siemens_VB/PPU3T example using matlab only
@@ -173,6 +176,22 @@ function test_siemens_vb_ppu3t_sync_last_matlab_only(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
+function test_siemens_vb_resp3t_logversion_1_matlab_only(testCase)
+%% Compares previously saved physio-structure and multiple regressors file
+% to current output of re-run of Siemens_VB/ECG3T example using matlab only
+dirExample = 'Siemens_VB/RESP3T_Logversion_1';
+doUseSpm = false;
+run_example_and_compare_reference(testCase, dirExample, doUseSpm)
+end
+
+function test_siemens_vb_resp3t_logversion_3_matlab_only(testCase)
+%% Compares previously saved physio-structure and multiple regressors file
+% to current output of re-run of Siemens_VB/ECG3T example using matlab only
+dirExample = 'Siemens_VB/RESP3T_Logversion_3';
+doUseSpm = false;
+run_example_and_compare_reference(testCase, dirExample, doUseSpm)
+end
+
function test_siemens_vd_ppu3t_matlab_only(testCase)
%% Compares previously saved physio-structure and multiple regressors file
@@ -232,6 +251,7 @@ function test_bids_ppu3t_separate_files_with_spm(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
+
function test_biopac_txt_ppu3t_with_spm(testCase)
%% Compares previously saved physio-structure and multiple regressors file
% to current output of re-run of BioPac_txt/PPU3T example using SPM Batch Editor
@@ -258,7 +278,6 @@ function test_philips_ecg3t_with_spm(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
-
function test_philips_ecg3t_v2_with_spm(testCase)
%% Compares previously saved physio-structure and multiple regressors file
% to current output of re-run of Philips/ECG3T_V2 example using SPM Batch Editor
@@ -267,7 +286,6 @@ function test_philips_ecg3t_v2_with_spm(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
-
function test_philips_ecg7t_with_spm(testCase)
%% Compares previously saved physio-structure and multiple regressors file
% to current output of re-run of Philips/ECG7T example using SPM Batch Editor
@@ -276,7 +294,6 @@ function test_philips_ecg7t_with_spm(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
-
function test_philips_ppu3t_with_spm(testCase)
%% Compares previously saved physio-structure and multiple regressors file
% to current output of re-run of Philips/PPU3T example using SPM Batch Editor
@@ -303,6 +320,14 @@ function test_siemens_vb_ecg3t_with_spm(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
+function test_siemens_vb_ecg3t_logversion_3_with_spm(testCase)
+%% Compares previously saved physio-structure and multiple regressors file
+% to current output of re-run of Siemens_VB/ECG3T example using SPM Batch Editor
+dirExample = 'Siemens_VB/ECG3T_Logversion_3';
+doUseSpm = true;
+run_example_and_compare_reference(testCase, dirExample, doUseSpm)
+end
+
function test_siemens_vb_ppu3t_sync_first_with_spm(testCase)
%% Compares previously saved physio-structure and multiple regressors file
% to current output of re-run of Siemens_VB/PPU3T example
@@ -321,6 +346,22 @@ function test_siemens_vb_ppu3t_sync_last_with_spm(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
+function test_siemens_vb_resp3t_logversion_1_with_spm(testCase)
+%% Compares previously saved physio-structure and multiple regressors file
+% to current output of re-run of Siemens_VB/ECG3T example using SPM Batch Editor
+dirExample = 'Siemens_VB/RESP3T_Logversion_1';
+doUseSpm = true;
+run_example_and_compare_reference(testCase, dirExample, doUseSpm)
+end
+
+function test_siemens_vb_resp3t_logversion_3_with_spm(testCase)
+%% Compares previously saved physio-structure and multiple regressors file
+% to current output of re-run of Siemens_VB/ECG3T example using SPM Batch Editor
+dirExample = 'Siemens_VB/RESP3T_Logversion_3';
+doUseSpm = true;
+run_example_and_compare_reference(testCase, dirExample, doUseSpm)
+end
+
function test_siemens_vd_ppu3t_with_spm(testCase)
%% Compares previously saved physio-structure and multiple regressors file
@@ -330,7 +371,6 @@ function test_siemens_vd_ppu3t_with_spm(testCase)
run_example_and_compare_reference(testCase, dirExample, doUseSpm)
end
-
function test_siemens_vd_ppu3t_for_bids_with_spm(testCase)
%% Compares previously saved physio-structure and multiple regressors file
% to current output of re-run of Siemens_VD/PPU3T_For_BIDS example using SPM Batch Editor
@@ -398,6 +438,7 @@ function run_example_and_compare_reference(testCase, dirExample, doUseSpm, ...
% hard-coded relative tolerance
relTol = 0.01; % 0.01 means 1 percent deviation from expected value allowed
+absTol = 1e-6; % for time courses (e.g., breathing) that reach close to 0, relative tolerance can be misleading, use relative value to max instead
%% Generic settings
% methods for recursively comparing structures, see
@@ -487,8 +528,21 @@ function run_example_and_compare_reference(testCase, dirExample, doUseSpm, ...
IsEqualTo(expPhysio.ons_secs.raw, ...
'Using', StructComparator(NumericComparator, 'Recursively', true), ...
'Within', RelativeTolerance(relTol), ...
- 'IgnoringFields', {'spulse_per_vol'}...
+ 'IgnoringFields', {'spulse_per_vol', 'fr'}...
), 'Comparing all numeric subfields of ons_secs.raw to check read-in and basic filtering of phys recordings');
+
+ % test filtered respiratory trace separetely, because of values close
+ % to zero in trace (relative errors can be huge even if 1e-6)
+ % if isempty, set arbitrary tolerance
+ absTolFr = absTol*max(abs(expPhysio.ons_secs.raw.fr));
+ if isempty(absTolFr)
+ absTolFr = absTol;
+ end
+
+ verifyEqual(testCase, actPhysio.ons_secs.raw.fr, expPhysio.ons_secs.raw.fr, ...
+ 'AbsTol', absTolFr, ...
+ 'Comparing ons_secs.raw.fr (raw filtered respiratory trace)');
+
end
% 2. Check some crucial timing parameters more vigorously
diff --git a/PhysIO/tests/tapas_physio_get_path_examples.m b/PhysIO/tests/tapas_physio_get_path_examples.m
new file mode 100644
index 00000000..5b6aac1e
--- /dev/null
+++ b/PhysIO/tests/tapas_physio_get_path_examples.m
@@ -0,0 +1,45 @@
+function pathExamples = tapas_physio_get_path_examples(pathPhysioPublic)
+% Returns GitLab-internal or TAPAS-public PhysIO Examples folder, based on
+% location of public PhysIO directory
+%
+% pathExamples = tapas_physio_get_path_examples(pathPhysioPublic)
+%
+% IN
+% pathPhysioPublic location of public PhysIO folder, e.g.,
+% 'tapas/PhysIO'
+% OUT
+%
+% EXAMPLE
+% tapas_physio_get_path_examples('tapas/PhysIO')
+%
+% See also
+
+% Author: Lars Kasper
+% Created: 2022-09-05
+% Copyright (C) 2022 TNU, Institute for Biomedical Engineering,
+% University of Zurich and ETH Zurich.
+%
+% This file is part of the TAPAS PhysIO Toolbox, which is released under
+% the terms of the GNU General Public License (GPL), version 3. You can
+% redistribute it and/or modify it under the terms of the GPL (either
+% version 3 or, at your option, any later version). For further details,
+% see the file COPYING or .
+
+if nargin < 1
+ pathPhysioPublic = fullfile(fileparts(mfilename('fullpath')), '..');
+end
+
+% try PhysIO-internal GitLab examples first
+pathExamples = fullfile(pathPhysioPublic, '..', 'examples');
+
+% otherwise use public TAPAS examples
+if ~isfolder(fullfile(pathExamples, 'BIDS'))
+ pathExamples = fullfile(pathPhysioPublic, ...
+ '..', 'examples', tapas_get_current_version(), 'PhysIO');
+end
+
+% If no examples folder found, suggest to download them via tapas-function
+if ~isfolder(fullfile(pathExamples, 'BIDS'))
+ physio = tapas_physio_new();
+ tapas_physio_log('No PhysIO examples data found. Please download via tapas_download_example_data()', physio.verbose, 2);
+end
diff --git a/PhysIO/tests/tapas_physio_test.m b/PhysIO/tests/tapas_physio_test.m
new file mode 100644
index 00000000..e5a30ffa
--- /dev/null
+++ b/PhysIO/tests/tapas_physio_test.m
@@ -0,0 +1,81 @@
+function [nTestFailed, nTestTotal, testResults] = tapas_physio_test(level)
+% Interface of PhysIO-Toolbox-specific test functions to global tapas_test
+%
+% [nTestFailed, nTestTotal] = tapas_physio_test(level)
+%
+% IN
+% level Depth of testing required
+% Approximate run time per level
+% 0: around a minute
+% here: unit tests only
+% 1: around 5 minutes (a coffee)
+% here: matlab-only integration tests, no SPM integration
+% 2: around an hour (time for lunch)
+% here: matlab AND SPM integration tests, takes less
+% than 10 minutes
+% 3: overnight (time to freakout [deadline])
+% OUT
+% nTestFailed number of failed tests
+% nTestTotal total number of executed tests
+% testResults detailed test results object (matlab.unittest.TestResult)
+% which may be queried to retrieve error messages or
+% rerun tests
+%
+% EXAMPLE
+% tapas_physio_test(0)
+%
+% See also tapas_test matlab.unittest.TestResult
+
+% Author: Lars Kasper
+% Created: 2022-09-04
+% Copyright (C) 2022 TNU, Institute for Biomedical Engineering,
+% University of Zurich and ETH Zurich.
+%
+% This file is part of the TAPAS PhysIO Toolbox, which is released under
+% the terms of the GNU General Public License (GPL), version 3. You can
+% redistribute it and/or modify it under the terms of the GPL (either
+% version 3 or, at your option, any later version). For further details,
+% see the file COPYING or .
+
+if nargin < 1
+ level = 0;
+end
+
+tic
+
+testResults = [];
+
+% Returns an error message, if no example data found
+tapas_physio_get_path_examples();
+
+if level >= 0
+ testResults = [testResults tapas_physio_run_unit_tests()];
+else
+ nTestTotal = 0;
+ nTestFailed = 0;
+ return
+end
+
+if level == 1
+ % code adapted from run_integration_tests, but chooses only tests with
+ % 'matlab' in the name (SPM GUI tests have SPM in the name)
+ import matlab.unittest.TestSuite;
+
+ pathTests = fullfile(fileparts(mfilename('fullpath')), 'integration');
+ suiteFolder = TestSuite.fromFolder(pathTests, ...
+ 'IncludingSubfolders', true, 'Name', '*matlab_only*');
+ testResults = [testResults run(suiteFolder)];
+end
+
+if level >= 2
+ testResults = [testResults tapas_physio_run_integration_tests()];
+end
+
+nTestTotal = numel(testResults);
+nTestFailed = sum([testResults.Failed]);
+
+% pretty summary output
+fprintf('\n\n\n\tTable of all executed PhysIO Tests:\n\n');
+disp(testResults.table);
+
+toc
diff --git a/PhysIO/tests/unit/preproc/tapas_physio_filter_cardiac_test.m b/PhysIO/tests/unit/preproc/tapas_physio_filter_cardiac_test.m
index 4d932985..c7f6794f 100644
--- a/PhysIO/tests/unit/preproc/tapas_physio_filter_cardiac_test.m
+++ b/PhysIO/tests/unit/preproc/tapas_physio_filter_cardiac_test.m
@@ -26,15 +26,26 @@
tests = functiontests(localfunctions);
end
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Auxiliary functions for automation of code folder structure
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% path to examples, needed for all test cases
+function setupOnce(testCase)
+% Get PhysIO public repo base folder from this file's location
+testCase.TestData.pathPhysioPublic = fullfile(fileparts(mfilename('fullpath')), '..', '..', '..');
+testCase.TestData.pathExamples = tapas_physio_get_path_examples(testCase.TestData.pathPhysioPublic);
+% for time courses (e.g., breathing) that reach close to 0, relative
+% tolerance can be misleading, use relative value to max instead
+testCase.TestData.absTol = 1e-6;
+end
+
+
function test_philips_ppu7t_filter_cheby2(testCase)
%% Compares previously saved Chebychev Type 2 IIR-filtered cropped cardiac
% time course with current re-run of same batch from Philips 7T PPU data
-% run GE example and extract physio
-pathPhysioPublic = fullfile(fileparts(mfilename('fullpath')), '..', '..', '..');
-pathExamples = fullfile(pathPhysioPublic, '..', 'examples');
-
-pathCurrentExample = fullfile(pathExamples, 'Philips/PPU7T');
+pathCurrentExample = fullfile(testCase.TestData.pathExamples, 'Philips/PPU7T');
cd(pathCurrentExample); % for prepending absolute paths correctly
fileExample = fullfile(pathCurrentExample, 'philips_ppu7t_spm_job.m');
run(fileExample); % retrieve matlabbatch
@@ -53,7 +64,7 @@ function test_philips_ppu7t_filter_cheby2(testCase)
actPhysio = tapas_physio_main_create_regressors(physio);
% load physio from reference data
-fileReferenceData = fullfile(pathExamples, 'TestReferenceResults', 'preproc', ...
+fileReferenceData = fullfile(testCase.TestData.pathExamples, 'TestReferenceResults', 'preproc', ...
'physio_filter_cardiac_cheby2.mat');
load(fileReferenceData, 'physio');
expPhysio = physio;
@@ -62,19 +73,17 @@ function test_philips_ppu7t_filter_cheby2(testCase)
actSolution = actPhysio.ons_secs.c;
expSolution = expPhysio.ons_secs.c;
-verifyEqual(testCase, actSolution, expSolution);
+% RelTol would be too conservative, because values close to 0 in raw
+% timeseries
+verifyEqual(testCase, actSolution, expSolution, ...
+ 'AbsTol', testCase.TestData.absTol*max(abs(expSolution)));
end
function test_philips_ppu7t_filter_butter(testCase)
%% Compares previously saved butterworth-filtered cropped cardiac time course
% with current re-run of same batch from Philips 7T PPU data
-% run GE example and extract physio
-pathPhysioPublic = fullfile(fileparts(mfilename('fullpath')), '..', '..', '..');
-% TODO: Make generic!
-pathExamples = fullfile(pathPhysioPublic, '..', 'examples');
-
-pathCurrentExample = fullfile(pathExamples, 'Philips/PPU7T');
+pathCurrentExample = fullfile(testCase.TestData.pathExamples, 'Philips/PPU7T');
cd(pathCurrentExample); % for prepending absolute paths correctly
fileExample = fullfile(pathCurrentExample, 'philips_ppu7t_spm_job.m');
run(fileExample); % retrieve matlabbatch
@@ -94,7 +103,7 @@ function test_philips_ppu7t_filter_butter(testCase)
actPhysio = tapas_physio_main_create_regressors(physio);
% load physio from reference data
-fileReferenceData = fullfile(pathExamples, 'TestReferenceResults', 'preproc', ...
+fileReferenceData = fullfile(testCase.TestData.pathExamples, 'TestReferenceResults', 'preproc', ...
'physio_filter_cardiac_butter.mat');
load(fileReferenceData, 'physio');
expPhysio = physio;
@@ -103,5 +112,8 @@ function test_philips_ppu7t_filter_butter(testCase)
actSolution = actPhysio.ons_secs.c;
expSolution = expPhysio.ons_secs.c;
-verifyEqual(testCase, actSolution, expSolution);
+% RelTol would be too conservative, because values close to 0 in raw
+% timeseries
+verifyEqual(testCase, actSolution, expSolution, ...
+ 'AbsTol', testCase.TestData.absTol*max(abs(expSolution)));
end
diff --git a/PhysIO/tests/unit/readin/tapas_physio_readin_bids_test.m b/PhysIO/tests/unit/readin/tapas_physio_readin_bids_test.m
index 4688a394..3ee96ca2 100644
--- a/PhysIO/tests/unit/readin/tapas_physio_readin_bids_test.m
+++ b/PhysIO/tests/unit/readin/tapas_physio_readin_bids_test.m
@@ -31,10 +31,10 @@
% results
function test_readin_bids_ppu3t(testCase)
-% run BIDS PPU example and extract physio
+% Get PhysIO public repo base folder from this file's location
pathPhysioPublic = fullfile(fileparts(mfilename('fullpath')), '..', '..', '..');
-% TODO: Make generic!
-pathExamples = fullfile(pathPhysioPublic, '..', 'examples');
+pathExamples = tapas_physio_get_path_examples(pathPhysioPublic);
+
% load SPM matlabbatch, but convert to pure script before executing
% remove unnecessary (beyond read-in) part from job exeuction (e.g.
@@ -79,8 +79,7 @@ function test_readin_bids_cpulse3t(testCase)
% run BIDS cpulse3t example and extract physio
pathPhysioPublic = fullfile(fileparts(mfilename('fullpath')), '..', '..', '..');
-% TODO: Make generic!
-pathExamples = fullfile(pathPhysioPublic, '..', 'examples');
+pathExamples = tapas_physio_get_path_examples(pathPhysioPublic);
% load SPM matlabbatch, but convert to pure script before executing
% remove unnecessary (beyond read-in) part from job exeuction (e.g.
diff --git a/PhysIO/tests/unit/utils/tapas_physio_findpeaks_test.m b/PhysIO/tests/unit/utils/tapas_physio_findpeaks_test.m
index 826bffa2..889f3889 100644
--- a/PhysIO/tests/unit/utils/tapas_physio_findpeaks_test.m
+++ b/PhysIO/tests/unit/utils/tapas_physio_findpeaks_test.m
@@ -41,8 +41,7 @@ function test_ge_ppu3t_peaks(testCase)
% run GE example and extract physio
pathPhysioPublic = fullfile(fileparts(mfilename('fullpath')), '..', '..', '..');
-% TODO: Make generic!
-pathExamples = fullfile(pathPhysioPublic, '..', 'examples');
+pathExamples = tapas_physio_get_path_examples(pathPhysioPublic);
if doUseSpm
pathCurrentExample = fullfile(pathExamples, 'GE/PPU3T');
diff --git a/PhysIO/tests/unit/utils/tapas_physio_pca_test.m b/PhysIO/tests/unit/utils/tapas_physio_pca_test.m
index 45414df5..2a519084 100644
--- a/PhysIO/tests/unit/utils/tapas_physio_pca_test.m
+++ b/PhysIO/tests/unit/utils/tapas_physio_pca_test.m
@@ -46,6 +46,9 @@ function test_pca_more_voxels_than_volumes(testCase)
end % function
function test_pca_more_volumes_than_voxels(testCase)
+% NOTE: This test is trivially true for now, because tapas_physio_pca falls
+% back on the stats-pca implementation for nVolumes >= nVoxels
+
% Compare the outputs of tapas_physio_pca(X,'svd') and
% tapas_physio_pca(X,'stats-pca') and check is they are close enough
% numericaly, within a certain tolerence.
@@ -58,8 +61,15 @@ function test_pca_more_volumes_than_voxels(testCase)
NVolumes = 300;
timeseries = randn(nVoxels,NVolumes);
-% Perform PCA with both methods
-verifyError(testCase,@() tapas_physio_pca( timeseries, 'svd' ) ,'tapas_physio_pca:NotEnoughVoxels')
-verifyError(testCase,@() tapas_physio_pca( timeseries, 'stats-pca' ) ,'tapas_physio_pca:NotEnoughVoxels')
+[svd. COEFF, svd. SCORE, svd. LATENT, svd. EXPLAINED, svd. MU] = tapas_physio_pca( timeseries, 'svd' );
+[stats.COEFF, stats.SCORE, stats.LATENT, stats.EXPLAINED, stats.MU] = tapas_physio_pca( timeseries, 'stats-pca' );
+
+% Compare both methods
+verifyEqual(testCase,svd.COEFF ,stats.COEFF )
+verifyEqual(testCase,svd.SCORE ,stats.SCORE )
+verifyEqual(testCase,svd.LATENT ,stats.LATENT )
+verifyEqual(testCase,svd.EXPLAINED,stats.EXPLAINED)
+verifyEqual(testCase,svd.MU ,stats.MU )
+
end % function
diff --git a/README.md b/README.md
index 4f6fc2ce..762325c1 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
![TAPAS Logo](misc/TapasLogo.png?raw=true "TAPAS Logo")
-*Version 6.0.1*
+*Version 6.1.0*
T A P A S - Translational Algorithms for Psychiatry-Advancing Science.
========================================================================
diff --git a/misc/log_tapas.txt b/misc/log_tapas.txt
index d80976b3..deaea13d 100644
--- a/misc/log_tapas.txt
+++ b/misc/log_tapas.txt
@@ -1,3 +1,4 @@
+6.1.0 https://www.tapas.tnu-zurich.com/examples_v6.1.0.zip ab01a2082cee20b5bb576b48d7f4e66c
6.0.1 https://www.tapas.tnu-zurich.com/examples_v6.0.0.zip b4bf7601b9a521159af00b00019989b6
6.0.0 https://www.tapas.tnu-zurich.com/examples_v6.0.0.zip b4bf7601b9a521159af00b00019989b6
5.1.2 https://www.tapas.tnu-zurich.com/examples_v5.0.0.zip 24c1248d3054f025b853b5ab7ce08a1a
diff --git a/misc/tapas_get_toolbox_infos.m b/misc/tapas_get_toolbox_infos.m
index 9ea13b3f..8b5e64ba 100644
--- a/misc/tapas_get_toolbox_infos.m
+++ b/misc/tapas_get_toolbox_infos.m
@@ -41,9 +41,11 @@
infos.physio.init_dir = strcat('PhysIO',filesep,'code');
infos.physio.init_function = 'tapas_physio_init';
- infos.physio.dependencies = [];
+ infos.physio.dependencies = {'Signal Processing Toolbox', ...
+ 'Image Processing Toolbox', ...
+ 'Statistics and Machine Learning Toolbox'};
infos.physio.diagnose_files = 'tapas_physio_main_create_regressors';
- infos.physio.test_function_name = '';
+ infos.physio.test_function_name = 'tapas_physio_test';
infos = tapas_default_toolbox_info(infos,'rDCM');