Can one use channel markers to set channel-specific bad epochs in continuous recordings?

Dear Francois,

I am cleaning some SEEG continuous recordings, each with hundreds of channels. At times, a few channels are artifactual for seconds only, and I would like to keep them for the good portion. Can I use channel markers to reject those bad segments, perhaps interpolating their activity or setting them to NaNs for those specific bad segments?
That would be very helpful for 'long' continuous files. Thank you,
Octavian

Dear Octavian,

I've edited the import code so that when some bad segments are attached only to a few channels, the trials are kept as good but with the channels marked as bad:
https://github.com/brainstorm-tools/brainstorm3/commit/de218a1a2bf1a3ada64b5de8676192064516b9ef

When epoching/averaging the trials: the bad trials would be ignored (the segments that are marked as bad for all the channels) and the trials with some bad channels would be included in the averaging.

Does this allow you to do what you had in mind?

Francois

Thank you! That is what I had in mind. Two quick questions:

  1. Is this the correct way to mark bad segments for only a few channels?: add event group> highlight channel(s) to be turned bad, drag cursor over (or use Time selection) to select time window, Add channel event, then Mark group as bad?
  2. When averaging trials or continuous files, if one has 4 files with good segments for a given time window, and one file with a bad segment for the same time window (for a few channels only, which are turned to bad channels for that segment/ window), the denominator of the average of the bad channel activity is 4, or 5? In other words, when averaging, the bad channel time series are ignored, or turned 0? (I believe the former, but please confirm).
    Thank you,
    Octavian

Yes, or start by creating a new group of events including the tag "bad_" at the beginning or "_bad" at the end of the event name.

When averaging trials or continuous files, if one has 4 files with good segments for a given time window, and one file with a bad segment for the same time window (for a few channels only, which are turned to bad channels for that segment/ window), the denominator of the average of the bad channel activity is 4, or 5? In other words, when averaging, the bad channel time series are ignored, or turned 0? (I believe the former, but please confirm).

The bad channels are ignored, not set to zero.
When averaging 10 files, and let's say channel A1 is bad in one trial only: it would compute the average of 10 values (sum of 10 signals divided by 10) for all the channels except for A1 which would average only 9 values (sum of 9 signals divided by 9).

Be careful with this individual marking of bad channels per trial:
It is ok in the number of bad trials is similar for all the channels, but you should avoid having important imbalance between channels: if a channel is good only in 1 or 2 trials instead of 10 for the other channels, you should mark it as bad everywhere instead (either by marking it as bad in the continuous file, or by right-clicking on the group of trials in the database > Good/bad channels > Mark some channels as bad). Otherwise you would have one very noisy signal (with much lower SNR) in the middle of your much smoother averages.

Understood, thank you!

Hi Francois,
Thank you for the development of this feature!
Is there any way to use this functionality in a script? I would like to include the possibility to identify, for each trial, which channels are bad (e.g. trial #1 : Fp1, trial #2 : Fp1, Fz, etc.). I would like to include this in a script, do you have any suggestion to do this?
Thank you in advance!
Best,
AnneSo

Hi AnneSo,

This is possible by reading the ChannelFlag field for each trial, and using it to index the channel data stored in the Channel file, see the script below.

Best,
Raymundo

% File names
fileNames = {...
    'Subject01/S01_AEF_20131218_01_600Hz_notch/data_deviant_trial001.mat', ...
    'Subject01/S01_AEF_20131218_01_600Hz_notch/data_deviant_trial002.mat', ...
    'Subject01/S01_AEF_20131218_01_600Hz_notch/data_deviant_trial003.mat'};

% Get channel file
channelFile = bst_get('ChannelFileForStudy', fileNames{1});
sChannel = in_bst_channel(channelFile);

% List of bad channels per file
for iFile = 1 : length(fileNames)    
    sFile = in_bst_data(fileNames{iFile}, 'ChannelFlag', 'Comment');    
    ixBadChannels = find(sFile.ChannelFlag <= 0);
    if isempty(ixBadChannels)
        fprintf('File: "%s" does not have bad channels\n', sFile.Comment);
    else
        badChannelsNames = {sChannel.Channel(ixBadChannels).Name};        
        fprintf('File: "%s" has bad channels: %s\n',  sFile.Comment, strjoin(badChannelsNames, ','))
    end
end

Result

>> File: "deviant (#1)" has bad channels: MLC54
>> File: "deviant (#2)" does not have bad channels
>> File: "deviant (#3)" does not have bad channels

And if you only want to do some simple reporting, this script is more or less what is implemented if you right-click anywhere in the database explorer (in the "functional data" view) > Good/bad channels > View all bad channels.

Example with the SEEG tutorial, right-clicking on the top-level protocol node:
image

More inspiration for scripting access to bad channels can be found here:

Thank you both! This was very helpful.

In case it help someone else, I adapted my script such that it now reads a table formatted as follow (this is an example) :

image

and gives the following (expected) result in Brainstorm:

image

:grin:

Here is the script :

% Get the electrode(s) to mark as bad in specific trials
    trial_chan = readtable(BadTrialChan) ; 

    % Get channel file
    channelFile = bst_get('ChannelFileForStudy',  sFiles.FileName) ; 
    sChannel = in_bst_channel(channelFile);

    fileNames = {sFilesEpochs.FileName} ; 
    
    % List of bad channels per file
    for iFile = 1 : length(trial_chan.TrialID)    

        % Reads epoch in which there are one or several channels to mark as bad
        sFile = in_bst_data(fileNames{trial_chan.TrialID(iFile)}, 'ChannelFlag', 'Comment');    
       
        % Process: Set bad channels
        chan_sFiles = bst_process('CallProcess', 'process_channel_setbad', sFilesEpochs(trial_chan.TrialID(iFile)), [], ...
        'sensortypes', trial_chan.ChannelList{iFile});

    end

Cheers,
AnneSo

1 Like