Skip to content

Commit 3747310

Browse files
authored
Add feature to export and load stim to and from BIDS events TSV files. (#147)
* v1.55.0 -- Change to stimulus loading to load from events .tsv file if it is present. It would override SNIRF stim field and StimEditGUI. If not present then default to SNIRF stim and StimEditGUI for stim loading and editing. * v1.55.1 -- Fix some bugs with stim export to events TSV files. -- Add documentation to DesignNotes.ppt describing the new stim loading (from events TSV files) and exporting (to events TSV files) feature. -- Add option to Snirf2Tsv.m to find and delete all events TSV files * v1.56.0 -- Fix issue in Snirf.GetDataTimeSeriesMatrix() with incorrect conversion to aux matrix when aux sample rate is different than data sample rate. -- Move events TSV loading menu option from StimEditGUI.fig to MainGUI.fig per Davis's request -- Fix bug in moving old output derived data folder homerOutput to new BIDS style derivative/homer. * v1.56.1 -- One more bug fix in SnirfClass.GetAuxDataMatrix() where the case of if aux has same time base as data is not being handles properly. -- Added config option "Quiet Mode" to be able to run processing without progress bars being displayed so it doesn't constantly take control of the mouse and one could work in parallel in other windows without interference. * v1.56.2 -- Added automatic stim loading from TSV file (if changes were made or new file created) when clicking on run.  -- Change to SnirfFile2Tsv.m: if snirf stim field is empty then generate empty TSV file with only the default column names instead not generating any file at all as was done before. -- Change the way we detect if stim changes were made to acquisition file. -- Minor change to readTsv.m to automatically complete input file name without an extention with a '.tsv' extension. -- Add DeleteTsv.m for diagnostic purposes. * v1.57.0 -- Make config changes made through Settings-->Edit App Configuration menu take effect immediately after saving so that one does not have to restart Homer3. -- Fix bug loading stims: Change order at init time when DataTreeClass.SetConditions() is called; before or after loading groupResult.mat. This order was changed several months ago (I believe in version 1.38.2) to be SetConditions then load groupResults. The problem with this is that it groupResults then overwrites the correct condition initialization for the whole group if groupResults happened to have unsaved condition changes that differ from SetConditions. This may be inconsistent with the correct condition set generated by SetConditions. One solution could be that TreeNode.Copy() groupResults method SHOULD NOT BE copying conditions - i.e, TreeNode.CondNames field. The other solution - the one used here - is change the order back to calling SetConditions() after copying groupResults. Although I have to try to remember why that change to caLL SetConditions first was made in the first place IN v1.38.2. It seems like a mistaken solution to address some sort of issue which the commit message does not explain. -- Clean up: delete unused and obsolete files. * v1.58.0 -- Add back showing pruned channels in PlotProbeGUI. Also there's a new ability to display non-HRF time course in PlotProbeGUI. If user is displaying non-HRF data then a warning pops up informing about this. You can opt out of receiving with every element selection. -- Standardized datatype names in methods GetDataTimeSeries() and GetMeasurementList(), across different levels in DataTreeClass, to be better able to use these methods in scripts. -- Made small change to hmrR_PreprocessIntensity_Negative per Meryem's request. -- Fix display issue in manually pruning channels with it not always being displayed correctly with a dotted line in the probe axes. This is because of lack of clarity with currently selected wavelength with only the currently selected wavelength measurement being toggled. To make things simple when selecting a channel for pruning measurements from ALL of the wavelengths are toggled simultaneously. Also to clarify, the meaning of manual pruning is always to the raw/OD timecourse data even when it is NOT selected in the Plot Select Data -- Fix small issue in syncSubmodules when copying peerless files in non-existent folders in the destination the folder needs to be created first (that is, copyfile won't do it for you).
1 parent 9cce6bf commit 3747310

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1453
-439
lines changed

AppSettings.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@ don't ask again
1010
% Export HRF Mean Output Style # all child processing elements in one file, one processing element per file
1111
one processing element per file
1212
13+
% Load Stim From TSV File # Yes, No
14+
Yes
1315
1416
% END

DataTree/AcquiredData/AcqDataClass.m

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
classdef AcqDataClass < matlab.mixin.Copyable
22

3+
properties (Access = public)
4+
bids
5+
end
36
properties (Access = private)
47
logger
58
end
@@ -76,19 +79,39 @@
7679

7780
methods
7881

82+
% -------------------------------------------------------
83+
function obj = AcqDataClass(fileobj)
84+
if nargin == 0
85+
return
86+
end
87+
if iscell(fileobj)
88+
if ~isempty(fileobj)
89+
fileobj = fileobj{1};
90+
end
91+
end
92+
if ~ischar(fileobj)
93+
fileobj = '';
94+
end
95+
obj.LoadBids(fileobj);
96+
end
97+
98+
99+
79100
% -------------------------------------------------------
80101
function Initialize(obj)
81102
global logger
82103
obj.logger = InitLogger(logger);
83104
end
84105

85106

107+
86108
% -------------------------------------------------------
87109
function err = Error(obj)
88110
err = obj.GetError();
89111
end
90112

91113

114+
92115
% ---------------------------------------------------------
93116
function msg = GetErrorMsg(obj)
94117
msg = '';
@@ -106,6 +129,77 @@ function Initialize(obj)
106129
end
107130

108131

132+
133+
% -------------------------------------------------------
134+
function err = LoadBids(obj, fileobj)
135+
err = obj.LoadStimOverride(fileobj);
136+
end
137+
138+
139+
140+
% -------------------------------------------------------
141+
function status = LoadStimOverride(obj, fileobj)
142+
global cfg
143+
status = false;
144+
cfg = InitConfig(cfg);
145+
if strcmpi(cfg.GetValue('Load Stim From TSV File'), 'no')
146+
return
147+
end
148+
obj.bids = struct('stim',{{}});
149+
if isempty(fileobj)
150+
return
151+
end
152+
[p,f] = fileparts(fileobj);
153+
if isempty(p)
154+
p = filesepStandard(pwd);
155+
end
156+
k = strfind(f, '_nirs');
157+
if isempty(k)
158+
k = length(f)+1;
159+
end
160+
fnameTsv = [filesepStandard(p), f(1:k-1), '_events.tsv'];
161+
file = mydir(fnameTsv);
162+
if isempty(file)
163+
return
164+
end
165+
obj.bids.stim = readTsv([filesepStandard(p), file(1).name],'numstr2num');
166+
if isempty(obj.bids.stim)
167+
return
168+
end
169+
s = TsvFile2Snirf(obj.bids.stim);
170+
obj.stim = s.stim.copy();
171+
status = true;
172+
end
173+
174+
175+
176+
% -------------------------------------------------------
177+
function err = ReloadStim(obj, fileobj)
178+
err = 0;
179+
obj.bids = struct('stim',{{}});
180+
if isempty(fileobj)
181+
return
182+
end
183+
[p,f] = fileparts(fileobj);
184+
if isempty(p)
185+
p = filesepStandard(pwd);
186+
end
187+
k = strfind(f, '_nirs');
188+
if isempty(k)
189+
k = length(f)+1;
190+
end
191+
fnameTsv = [filesepStandard(p), f(1:k-1), '_events.tsv'];
192+
file = mydir(fnameTsv);
193+
if isempty(file)
194+
return
195+
end
196+
obj.bids.stim = readTsv([filesepStandard(p), file(1).name],'numstr2num');
197+
s = TsvFile2Snirf(obj.bids.stim);
198+
obj.stim = s.stim.copy();
199+
end
200+
201+
202+
109203
% -------------------------------------------------------
110204
function FreeMemory(obj, filename)
111205
if ~exist('filename','var')
@@ -118,6 +212,7 @@ function FreeMemory(obj, filename)
118212
end
119213

120214

215+
121216
% ---------------------------------------------------------
122217
function bbox = GetSdgBbox(obj)
123218
bbox = [];
@@ -150,6 +245,7 @@ function FreeMemory(obj, filename)
150245
end
151246

152247

248+
153249
% ----------------------------------------------------------------------------------
154250
function varval = GetVar(obj, varname)
155251
if ismethod(obj,['Get_', varname])
@@ -179,6 +275,7 @@ function FreeMemory(obj, filename)
179275
end
180276

181277

278+
182279
% ----------------------------------------------------------------------------------
183280
function t = GetTimeCombined(obj)
184281
% Function combines the time vectors for all data blocks into one time vectors.
@@ -204,6 +301,7 @@ function FreeMemory(obj, filename)
204301
end
205302

206303

304+
207305
% ----------------------------------------------------------------------------------
208306
function data = GetStimData(~, ~)
209307
data = [];
@@ -250,6 +348,20 @@ function FreeMemory(obj, filename)
250348

251349

252350

351+
% ----------------------------------------------------------------------------------
352+
function fnameTsv = GetStimTsvFilename(obj)
353+
fnameTsv = '';
354+
[pname, fname] = fileparts(obj.GetFilename());
355+
k = strfind(fname, '_nirs');
356+
if isempty(k)
357+
k = length(fname)+1;
358+
end
359+
if isempty(fname)
360+
return;
361+
end
362+
fnameTsv = [filesepStandard(pname), fname(1:k-1), '_events.tsv'];
363+
end
364+
253365
end
254366

255367
end

DataTree/AcquiredData/DataFiles/DeleteDataFiles.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ function DeleteDataFiles(varargin)
8888
if strcmp(options, 'delete')
8989
fprintf('Deleting %s\n', [datafiles(ii).rootdir, '/', datafiles(ii).name]);
9090
delete([datafiles(ii).rootdir, '/', datafiles(ii).name]);
91+
delete([datafiles(ii).rootdir, '/*_events.tsv']);
9192
elseif strcmp(options, 'move')
9293
fprintf('Moving %s to %s\n', [datafiles(ii).rootdir, '/', datafiles(ii).name], [datafiles(ii).rootdir, '/', datafiles(ii).name, '.old']);
9394
movefile([datafiles(ii).rootdir, '/', datafiles(ii).name], [datafiles(ii).rootdir, '/', datafiles(ii).name, '.old']);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function DeleteTsv(rootdir)
2+
if ~exist('rootdir','var')
3+
rootdir = pwd;
4+
end
5+
rootdir = filesepStandard(rootdir);
6+
Snirf2Tsv(rootdir, 'remove');
7+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
function Snirf2Tsv(rootdir, options)
2+
if ~exist('rootdir','var')
3+
rootdir = pwd;
4+
end
5+
if ~exist('options','var')
6+
options = pwd;
7+
end
8+
rootdir = filesepStandard(rootdir);
9+
files = DataFilesClass(rootdir, 'snirf');
10+
files = files.files;
11+
for ii = 1:length(files)
12+
if files(ii).IsDir()
13+
continue
14+
end
15+
fname = filesepStandard([files(ii).rootdir, files(ii).name]);
16+
[pname, fname, ext1] = fileparts(fname);
17+
k = strfind(fname, '_nirs');
18+
if isempty(k)
19+
k = length(fname)+1;
20+
end
21+
fnameNew = [filesepStandard(pname), fname(1:k-1), '_events.tsv'];
22+
src = [filesepStandard(pname), fname, ext1];
23+
dst = fnameNew;
24+
if optionExists(options, 'delete') || optionExists(options, 'remove')
25+
if ispathvalid(dst)
26+
fprintf('Deleting %s\n', dst);
27+
delete(dst);
28+
end
29+
else
30+
fprintf('Converting %s to %s\n', src, dst);
31+
SnirfFile2Tsv(src, dst, 'removeStim');
32+
end
33+
end
34+
35+
36+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
function tsv = SnirfFile2Tsv(src, dst, options)
2+
tsv = {};
3+
4+
% Parse args
5+
if nargin==0
6+
return
7+
end
8+
if ~isa(src, 'SnirfClass')
9+
s = SnirfClass(src);
10+
else
11+
s = src;
12+
end
13+
if s.IsEmpty()
14+
s.LoadStim(s.GetFilename());
15+
end
16+
if ~exist('dst','var')
17+
dst = '';
18+
end
19+
20+
if isempty(dst)
21+
dst = s.GetStimTsvFilename();
22+
end
23+
if ~exist('options','var')
24+
options = '';
25+
end
26+
if isempty(s.stim)
27+
s0 = StimClass();
28+
else
29+
s0 = s.stim(1);
30+
end
31+
tsv = [s0.dataLabels(:)', 'trial_type'];
32+
data = [];
33+
for ii = 1:length(s.stim)
34+
iS = size(tsv,1)+1;
35+
iE = size(tsv,1)+size(s.stim(ii).data,1);
36+
tsv(iS:iE,:) = [num2cell(s.stim(ii).data), repmat({s.stim(ii).name}, size(s.stim(ii).data,1),1)]; %#ok<*AGROW>
37+
data = [data; s.stim(ii).data];
38+
end
39+
if ~isempty(data)
40+
[~, order] = sortrows(data,1);
41+
tsv(2:end,:) = tsv(order+1,:);
42+
end
43+
if ispathvalid(dst)
44+
if optionExists(options, 'regenerate')
45+
writeTsv(dst, tsv);
46+
end
47+
else
48+
writeTsv(dst, tsv);
49+
end
50+
51+
% Remove stim from
52+
% if optionExists(options, 'removeStim')
53+
% s.Load();
54+
% s.stim = StimClass().empty();
55+
% s.Save();
56+
% end
57+
58+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
function snirf = TsvFile2Snirf(src, dst, option)
2+
snirf = SnirfClass();
3+
4+
% Parse args
5+
if nargin==0
6+
return
7+
end
8+
if ~exist('dst','var')
9+
dst = '';
10+
end
11+
if ~exist('option','var')
12+
option = 'nofile';
13+
end
14+
15+
if ischar(src)
16+
tsv = readTsv(src);
17+
else
18+
tsv = src;
19+
src = '';
20+
end
21+
if isempty(tsv)
22+
return;
23+
end
24+
25+
if isempty(dst) && ispathvalid(src)
26+
[pname, fname] = fileparts(src);
27+
k = strfind(fname, '_events');
28+
if isempty(k)
29+
k = length(fname)+1;
30+
end
31+
dst = [filesepStandard(pname), fname(1:k-1), '_nirs.snirf'];
32+
end
33+
34+
snirf.SetFilename(dst);
35+
36+
fields = tsv(1,:);
37+
k = find(strcmp(fields, 'trial_type'));
38+
if isempty(k)
39+
return
40+
end
41+
for ii = 2:size(tsv,1)
42+
jj = findStim(snirf.stim, tsv{ii,k});
43+
if jj == 0
44+
snirf.stim(end+1) = StimClass(tsv([1,ii],:));
45+
else
46+
snirf.stim(jj).AddTsvData(tsv([1,ii],:));
47+
end
48+
end
49+
if strcmp(option,'nofile')
50+
return
51+
end
52+
snirf.Save();
53+
54+
55+
% -----------------------------------------------------------
56+
function jj = findStim(stim, tsv)
57+
jj = 0;
58+
for ii = 1:length(stim)
59+
if isnumeric(tsv)
60+
tsv = num2str(tsv);
61+
end
62+
if strcmp(stim(ii).name, tsv)
63+
jj = ii;
64+
break;
65+
end
66+
end
67+
68+

0 commit comments

Comments
 (0)