Skip to content

Commit

Permalink
Optional storage of events channels and notes
Browse files Browse the repository at this point in the history
These fields were most of the time empty and could sometimes occupy gigantic amount of disk and memory space (e.g. e-phys with hundreds of thousqnds of events)
Now can be either empty or defined.
  • Loading branch information
ftadel committed Mar 15, 2023
1 parent 8912fd9 commit 427b8d9
Show file tree
Hide file tree
Showing 99 changed files with 562 additions and 382 deletions.
2 changes: 1 addition & 1 deletion doc/license.html
Expand Up @@ -5,7 +5,7 @@
<body alink="#fff000" link="#fff000" vlink="#fff000">
<h4><span style="font-family: Arial Black; color: #ffffff;"><strong>THERE IS NO UNDO BUTTON!<BR>SET UP A <FONT color=red>BACKUP</FONT> OF YOUR DATABASE</strong></span></h4>
<HR>
<!-- LICENCE_START -->Version: 3.230314 (14-Mar-2023)<br>
<!-- LICENCE_START -->Version: 3.230315 (15-Mar-2023)<br>
<span style="font-style: italic;">COPYRIGHT &copy; 2000-2023
USC &amp; McGill University.<br>
</span>
Expand Down
1 change: 1 addition & 0 deletions doc/updates.txt
@@ -1,5 +1,6 @@
March 2023
- Parallel computing: Handle temporary files per process, to avoid conflicts
- Recordings: Optional storage of events channels and notes
--------------------------------------------------------------
February 2023
- Stat: Added FieldTrip threshold-free cluster enhancement (TFCE)
Expand Down
2 changes: 1 addition & 1 deletion doc/version.txt
@@ -1,2 +1,2 @@
% Brainstorm
% v. 3.230314 (14-Mar-2023)
% v. 3.230315 (15-Mar-2023)
10 changes: 4 additions & 6 deletions toolbox/db/db_template.m
Expand Up @@ -471,14 +471,12 @@
template = struct(...
'label', '', ... % str, label of the event group. Should not be empty.
'color', [], ... % array of double (R,G,B): color triplet, size: (1, 3). Values btwn 0 and 1. Cannot be empty.
'epochs', [], ... % array of int (epochs indices), size: (1, nb of event items). Cannot be empty.
'epochs', [], ... % empty, or array of int (epochs indices), size: (1, nb of event items).
'times', [], ... % array of double (time values), size: (1 or 2, nb of event items). Cannot be empty.
'reactTimes', [], ... % array of double (reaction times), size: (1, nb of event items). Can be empty.
'reactTimes', [], ... % empty, or array of double (reaction times), size: (1, nb of event items).
'select', 1, ... % int: display flag (0 or 1).
'channels', [], ... % see below
'notes', []); % see below
template.channels = {}; % cell array of cell arrays of str, size: (1, nb of event items:(1, nb of associated channels)). Cannot be empty.
template.notes = {}; % cell array of str, size: (1, nb of event items). Cannot be empty.
'channels', [], ... % empty, or cell array of cell arrays of str, size: (1, nb of event items:(1, nb of associated channels))
'notes', []); % empty, cell array of str, size: (1, nb of event items)

% ==== EPOCH ====
case 'epoch'
Expand Down
8 changes: 6 additions & 2 deletions toolbox/gui/figure_timeseries.m
Expand Up @@ -4812,7 +4812,11 @@ function PlotEventsDots_EventsBar(hFig)
end
end
iOccChannels = find(~cellfun(@isempty, iLines));
iOccGlobal = find(cellfun(@isempty, iLines) & cellfun(@isempty, events(iEvt).channels));
if ~isempty(events(iEvt).channels)
iOccGlobal = find(cellfun(@isempty, iLines) & cellfun(@isempty, events(iEvt).channels));
else
iOccGlobal = find(cellfun(@isempty, iLines));
end

% === CHANNEL EVENTS ===
% Where to display the notes and events labels by default
Expand Down Expand Up @@ -5006,7 +5010,7 @@ function PlotEventsDots_EventsBar(hFig)
end

% === EVENT NOTES ===
if ~strcmpi(TsInfo.ShowEventsMode, 'none')
if ~strcmpi(TsInfo.ShowEventsMode, 'none') && ~isempty(events(iEvt).notes)
for iOcc = 1:nOccur
% No notes attached to this event, skip
if isempty(events(iEvt).notes{iOcc})
Expand Down
8 changes: 6 additions & 2 deletions toolbox/gui/panel_import_data.m
Expand Up @@ -1083,8 +1083,12 @@ function SaveOptions()
for iEvent = 1:length(s.events)
s.events(iEvent).epochs = s.events(iEvent).epochs(iSelSmp{iEvent});
s.events(iEvent).times = s.events(iEvent).times(:, iSelSmp{iEvent});
s.events(iEvent).channels = s.events(iEvent).channels(iSelSmp{iEvent});
s.events(iEvent).notes = s.events(iEvent).notes(iSelSmp{iEvent});
if ~isempty(s.events(iEvent).channels)
s.events(iEvent).channels = s.events(iEvent).channels(iSelSmp{iEvent});
end
if ~isempty(s.events(iEvent).notes)
s.events(iEvent).notes = s.events(iEvent).notes(iSelSmp{iEvent});
end
end
% Import mode
s.ImportMode = 'Event';
Expand Down
99 changes: 73 additions & 26 deletions toolbox/gui/panel_record.m
Expand Up @@ -1290,7 +1290,7 @@ function UpdateEventsOccur()
strOcc = sprintf(' %1.3f-%1.3f', evtTimes(1,i), evtTimes(2,i));
end
% Add list of channels
if (i <= length(event.channels)) && ~isempty(event.channels{i})
if ~isempty(event.channels) && (i <= length(event.channels)) && ~isempty(event.channels{i})
strOcc = [strOcc, ' ' sprintf(' %s', event.channels{i}{:})];
end
listModel.addElement(strOcc);
Expand Down Expand Up @@ -1476,8 +1476,12 @@ function SetSelectedEvent(iEvent, iOccur)
% Else keep only the occurrences in time window
events(iEvt).times = events(iEvt).times(:,iOccur);
events(iEvt).epochs = events(iEvt).epochs(iOccur);
events(iEvt).channels = events(iEvt).channels(iOccur);
events(iEvt).notes = events(iEvt).notes(iOccur);
if ~isempty(events(iEvt).channels)
events(iEvt).channels = events(iEvt).channels(iOccur);
end
if ~isempty(events(iEvt).notes)
events(iEvt).notes = events(iEvt).notes(iOccur);
end
if ~isempty(events(iEvt).reactTimes)
events(iEvt).reactTimes = events(iEvt).reactTimes(iOccur);
end
Expand Down Expand Up @@ -1608,8 +1612,12 @@ function JumpToEvent(iEvent, iOccur)
iOkEpochs = (events(i).epochs == GlobalData.FullTimeWindow.CurrentEpoch);
events(i).times = events(i).times(:,iOkEpochs);
events(i).epochs = events(i).epochs(iOkEpochs);
events(i).channels = events(i).channels(iOkEpochs);
events(i).notes = events(i).notes(iOkEpochs);
if ~isempty(events(i).channels)
events(i).channels = events(i).channels(iOkEpochs);
end
if ~isempty(events(i).reactTimes)
events(i).notes = events(i).notes(iOkEpochs);
end
if ~isempty(events(i).reactTimes)
events(i).reactTimes = events(i).reactTimes(iOkEpochs);
end
Expand Down Expand Up @@ -1968,9 +1976,17 @@ function EventTypesMerge()
newEvent.label = newLabel;
newEvent.times = [events(iEvents).times];
newEvent.epochs = [events(iEvents).epochs];
newEvent.channels = [events(iEvents).channels];
newEvent.notes = [events(iEvents).notes];
% Reaction time, notes, channels: only if all the events have them
if all(~cellfun(@isempty, {events(iEvents).channels}))
newEvent.channels = [events(iEvents).channels];
else
newEvent.channels = [];
end
if all(~cellfun(@isempty, {events(iEvents).notes}))
newEvent.notes = [events(iEvents).notes];
else
newEvent.notes = [];
end
if all(~cellfun(@isempty, {events(iEvents).reactTimes}))
newEvent.reactTimes = [events(iEvents).reactTimes];
else
Expand All @@ -1980,8 +1996,12 @@ function EventTypesMerge()
[tmp__, iSort] = unique(bst_round(newEvent.times(1,:), 9));
newEvent.times = newEvent.times(:,iSort);
newEvent.epochs = newEvent.epochs(iSort);
newEvent.channels = newEvent.channels(iSort);
newEvent.notes = newEvent.notes(iSort);
if ~isempty(newEvent.channels)
newEvent.channels = newEvent.channels(iSort);
end
if ~isempty(newEvent.notes)
newEvent.notes = newEvent.notes(iSort);
end
if ~isempty(newEvent.reactTimes)
newEvent.reactTimes = newEvent.reactTimes(iSort);
end
Expand Down Expand Up @@ -2263,19 +2283,26 @@ function EventTypesSort(SortMode)
end
channelNames = unique(chanMontage);
end

% Add event: time
sEvent.epochs = [sEvent.epochs, iEpoch];
sEvent.times = [sEvent.times, newTime'];
sEvent.channels = [sEvent.channels, {channelNames(:)'}];
sEvent.notes = [sEvent.notes, {[]}];
sEvent.epochs = [sEvent.epochs, iEpoch];
sEvent.times = [sEvent.times, newTime'];
% Sort based on the beginning of each event
[tmp__, indSort] = sortrows([sEvent.epochs; sEvent.times(1,:)]');
sEvent.times = sEvent.times(:,indSort);
sEvent.epochs = sEvent.epochs(indSort);
sEvent.channels = sEvent.channels(indSort);
sEvent.notes = sEvent.notes(indSort);
% Add event: reactTime (only if there are already reaction times)
% Add list of channels (if already defined, or if adding channel-defined event)
if ~isempty(sEvent.channels) || ~isempty(channelNames)
if isempty(sEvent.channels)
sEvent.channels = cell(1, size(sEvent.times,2) - 1);
end
sEvent.channels = [sEvent.channels, {channelNames(:)'}];
sEvent.channels = sEvent.channels(indSort);
end
% Add event: notes, reactTime (only if there are already defined)
if ~isempty(sEvent.notes)
sEvent.notes = [sEvent.notes, {[]}];
sEvent.notes = sEvent.notes(indSort);
end
if ~isempty(sEvent.reactTimes)
sEvent.reactTimes = [sEvent.reactTimes, 0];
sEvent.reactTimes = sEvent.reactTimes(indSort);
Expand Down Expand Up @@ -2328,8 +2355,12 @@ function EventOccurDel(iEvent, iOccursEpoch)
% Remove event occurrences
sEvent.times(:,iOccurs) = [];
sEvent.epochs(iOccurs) = [];
sEvent.channels(iOccurs) = [];
sEvent.notes(iOccurs) = [];
if ~isempty(sEvent.channels)
sEvent.channels(iOccurs) = [];
end
if ~isempty(sEvent.notes)
sEvent.notes(iOccurs) = [];
end
if ~isempty(sEvent.reactTimes)
sEvent.reactTimes(iOccurs) = [];
end
Expand All @@ -2355,9 +2386,13 @@ function EventEditNotes()
end
% Get event (ignore current epoch)
sEvent = GetEvents(iEvent, 1);
% Add notes structure
if isempty(sEvent.notes)
sEvent.notes = cell(1, size(sEvent.times,2));
end
% Format event name
if (size(sEvent.times, 1) == 1)
strOcc = sprintf('"%s" (%1.3fs)', sEvent.label, sEvent.times(iOccur));
strOcc = sprintf('"%s" (%1.3fs)', sEvent.label, sEvent.times(1, iOccur));
else
strOcc = sprintf('"%s" (%1.3f-%1.3fs)', sEvent.label, sEvent.times(1,iOccur), sEvent.times(2,iOccur));
end
Expand Down Expand Up @@ -2507,8 +2542,12 @@ function ExportSelectedEvents()
sFileTmp.events = sFileTmp.events(iEvt);
sFileTmp.events.times = sFileTmp.events.times(:,iOcc);
sFileTmp.events.epochs = sFileTmp.events.epochs(:,iOcc);
sFileTmp.events.channels = sFileTmp.events.channels(iOcc);
sFileTmp.events.notes = sFileTmp.events.notes(iOcc);
if ~isempty(sFileTmp.events.channels)
sFileTmp.events.channels = sFileTmp.events.channels(iOcc);
end
if ~isempty(sFileTmp.events.notes)
sFileTmp.events.notes = sFileTmp.events.notes(iOcc);
end
if ~isempty(sFileTmp.events.reactTimes)
sFileTmp.events.reactTimes = sFileTmp.events.reactTimes(iOcc);
end
Expand Down Expand Up @@ -2622,7 +2661,7 @@ function SaveModifications(iDS)
% Consider only the non-empty events that have the "bad" string in them
if IsEventBad(events(iEvt).label) && ~isempty(events(iEvt).times)
% Exclude all the channel-specific events
if ~isChannelEvtBad
if ~isChannelEvtBad && ~isempty(events(iEvt).channels)
iOccBad = find(cellfun(@isempty, events(iEvt).channels));
if isempty(iOccBad)
continue;
Expand All @@ -2639,7 +2678,11 @@ function SaveModifications(iDS)
end
badEpochs = [badEpochs, events(iEvt).epochs(iOccBad)];
% Get channel events
badChan = [badChan, events(iEvt).channels(iOccBad)];
if ~isempty(events(iEvt).channels)
badChan = [badChan, events(iEvt).channels(iOccBad)];
else
badChan = [badChan, cell(1, length(iOccBad))];
end
end
end
badSeg = round(badTimes .* sFile.prop.sfreq);
Expand Down Expand Up @@ -3021,8 +3064,12 @@ function SetAcquisitionDate(iStudy, newDate) %#ok<DEFNU>
if ~isempty(iOut)
events(iEvt).times(:,iOut) = [];
events(iEvt).epochs(iOut) = [];
events(iEvt).channels(iOut) = [];
events(iEvt).notes(iOut) = [];
if ~isempty(events(iEvt).channels)
events(iEvt).channels(iOut) = [];
end
if ~isempty(events(iEvt).notes)
events(iEvt).notes(iOut) = [];
end
if ~isempty(events(iEvt).reactTimes)
events(iEvt).reactTimes(iOut) = [];
end
Expand Down
8 changes: 4 additions & 4 deletions toolbox/gui/panel_spikes.m
Expand Up @@ -587,7 +587,7 @@ function SaveElectrode()
newEvents(1).times = tmpEvents(1).times;
newEvents(1).reactTimes = [];
newEvents(1).select = 1;
newEvents(1).notes = cell(1, size(newEvents(1).times, 2));
newEvents(1).notes = [];
newEvents(1).channels = repmat({{electrodeName}}, 1, size(newEvents(1).times, 2));


Expand All @@ -599,7 +599,7 @@ function SaveElectrode()
newEvents(iNeuron).times = tmpEvents(iNeuron).times;
newEvents(iNeuron).reactTimes = [];
newEvents(iNeuron).select = 1;
newEvents(iNeuron).notes = cell(1, size(newEvents(iNeuron).times, 2));
newEvents(iNeuron).notes = [];
newEvents(iNeuron).channels = repmat({{electrodeName}}, 1, size(newEvents(iNeuron).times, 2));

end
Expand All @@ -611,8 +611,8 @@ function SaveElectrode()
newEvents(1).times = [];
newEvents(1).reactTimes = [];
newEvents(1).select = 1;
newEvents(1).channels = cell(1, size(newEvents(1).times, 2));
newEvents(1).notes = cell(1, size(newEvents(1).times, 2));
newEvents(1).channels = [];
newEvents(1).notes = [];
end
end

Expand Down
8 changes: 6 additions & 2 deletions toolbox/io/import_events.m
Expand Up @@ -238,8 +238,12 @@
if ~isempty(sFile.events(iEvt).reactTimes)
sFile.events(iEvt).reactTimes = sFile.events(iEvt).reactTimes(iSort);
end
sFile.events(iEvt).channels = sFile.events(iEvt).channels(iSort);
sFile.events(iEvt).notes = sFile.events(iEvt).notes(iSort);
if ~isempty(sFile.events(iEvt).channels)
sFile.events(iEvt).channels = sFile.events(iEvt).channels(iSort);
end
if ~isempty(sFile.events(iEvt).notes)
sFile.events(iEvt).notes = sFile.events(iEvt).notes(iSort);
end
end
end
% Add color if does not exist yet
Expand Down
8 changes: 6 additions & 2 deletions toolbox/io/in_data.m
Expand Up @@ -475,8 +475,12 @@
DataMat.Events(iEvtData).color = sFile.events(iEvt).color;
DataMat.Events(iEvtData).times = newEvtTimes;
DataMat.Events(iEvtData).epochs = sFile.events(iEvt).epochs(iOccur);
DataMat.Events(iEvtData).channels = sFile.events(iEvt).channels(iOccur);
DataMat.Events(iEvtData).notes = sFile.events(iEvt).notes(iOccur);
if ~isempty(sFile.events(iEvt).channels)
DataMat.Events(iEvtData).channels = sFile.events(iEvt).channels(iOccur);
end
if ~isempty(sFile.events(iEvt).notes)
DataMat.Events(iEvtData).notes = sFile.events(iEvt).notes(iOccur);
end
if ~isempty(sFile.events(iEvt).reactTimes)
DataMat.Events(iEvtData).reactTimes = sFile.events(iEvt).reactTimes(iOccur);
end
Expand Down
2 changes: 0 additions & 2 deletions toolbox/io/in_data_muse_csv.m
Expand Up @@ -249,8 +249,6 @@
DataMat.Events(iEvt).times = unique(round(evtTime(iOcc) .* sfreq)) ./ sfreq;
DataMat.Events(iEvt).epochs = 1 + 0*DataMat.Events(iEvt).times;
DataMat.Events(iEvt).select = 1;
DataMat.Events(iEvt).channels = cell(1, size(DataMat.Events(iEvt).times, 2));
DataMat.Events(iEvt).notes = cell(1, size(DataMat.Events(iEvt).times, 2));
end
end

Expand Down
4 changes: 2 additions & 2 deletions toolbox/io/in_data_snirf.m
Expand Up @@ -244,8 +244,8 @@

DataMat.Events(iEvt).times = evtTime;
DataMat.Events(iEvt).epochs = ones(1, size(evtTime,2));
DataMat.Events(iEvt).channels = cell(1, size(evtTime,2));
DataMat.Events(iEvt).notes = cell(1, size(evtTime,2));
DataMat.Events(iEvt).channels = [];
DataMat.Events(iEvt).notes = [];
DataMat.Events(iEvt).reactTimes = [];
end
end
Expand Down
4 changes: 2 additions & 2 deletions toolbox/io/in_data_tobii_tsv.m
Expand Up @@ -269,12 +269,12 @@
DataMat(iFile).Events(iEvt).times = round([tNew(iOcc) ; tNew(iOcc+1)-T] .* sfreq) ./ sfreq; % Extended events
DataMat(iFile).Events(iEvt).epochs = ones(1, length(iOcc));
DataMat(iFile).Events(iEvt).select = 1;
DataMat(iFile).Events(iEvt).channels = cell(1, length(iOcc));
DataMat(iFile).Events(iEvt).channels = [];
% Fixation event: Save fixation point
if (uniqueVal(iEvt) == sum('Fixation'))
DataMat(iFile).Events(iEvt).notes = cellfun(@(c1,c2)sprintf('%dx%d', c1, c2), sesTsv(iRows(iNew(iOcc)), iColFixX), sesTsv(iRows(iNew(iOcc)), iColFixY), 'UniformOutput', 0)';
else
DataMat(iFile).Events(iEvt).notes = cell(1, size(DataMat(iFile).Events(iEvt).times, 2));
DataMat(iFile).Events(iEvt).notes = [];
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions toolbox/io/in_data_ws_csv.m
Expand Up @@ -154,8 +154,8 @@
% DataMat.Events(iEvt).times = unique(round(evtTime(iOcc) .* sfreq)) ./ sfreq;
% DataMat.Events(iEvt).epochs = 1 + 0*DataMat.Events(iEvt).times;
% DataMat.Events(iEvt).select = 1;
% DataMat.Events(iEvt).channels = cell(1, size(DataMat.Events(iEvt).times, 2));
% DataMat.Events(iEvt).notes = cell(1, size(DataMat.Events(iEvt).times, 2));
% DataMat.Events(iEvt).channels = [];
% DataMat.Events(iEvt).notes = [];
% end
% end

Expand Down
4 changes: 2 additions & 2 deletions toolbox/io/in_data_xdf.m
Expand Up @@ -150,8 +150,8 @@
events(iEvt).times = samples ./ maxRate;
events(iEvt).reactTimes = [];
events(iEvt).select = 1;
events(iEvt).channels = cell(1, size(events(iEvt).times, 2));
events(iEvt).notes = cell(1, size(events(iEvt).times, 2));
events(iEvt).channels = [];
events(iEvt).notes = [];
end
end
if ~isempty(events)
Expand Down
4 changes: 2 additions & 2 deletions toolbox/io/in_events_ant.m
Expand Up @@ -55,8 +55,8 @@
events(iEvt).times = round(mrk{1}(iMrk)' .* sFile.prop.sfreq) ./ sFile.prop.sfreq;
events(iEvt).reactTimes = [];
events(iEvt).select = 1;
events(iEvt).channels = cell(1, size(events(iEvt).times, 2));
events(iEvt).notes = cell(1, size(events(iEvt).times, 2));
events(iEvt).channels = [];
events(iEvt).notes = [];
end


Expand Down

0 comments on commit 427b8d9

Please sign in to comment.